Moving WPF control with its templates to a new parent - c#

Let's jump right in and let the code explain:
FrameworkElement par = list;
while((par = par.Parent as FrameworkElement) != null) {
grid.Resources.MergedDictionaries.Add(par.Resources);
}
grid.DataContext = list.DataContext;
if(rootparent is ContentControl) {
(rootparent as ContentControl).Content = null;
} else if(rootparent is Decorator) {
(rootparent as Decorator).Child = null;
} else if(rootparent is Panel) {
rootindex = (rootparent as Panel).Children.IndexOf(list);
(rootparent as Panel).Children.RemoveAt(rootindex);
}
grid.Children.Add(list);
So, basically, the templated control is moved out of its original window and into an instantiated grid in the background. Its datacontext successfully transfers (I watched it go to null when it disconnected, and back to the original object when it joined the grid), but the templates don't. I don't get why, because up there at the top I'm copying all the resource dictionaries all the way to the top-level parent and merging them into the new grid.
So I'm missing something in making it re-apply the templates.

The resources needed to be duplicated into the new container, not just referenced.
FrameworkElement par = list;
while((par = par.Parent as FrameworkElement) != null) {
DictionaryEntry[] resources = new DictionaryEntry[par.Resources.Count];
par.Resources.CopyTo(resources, 0);
var res = new ResourceDictionary();
foreach(DictionaryEntry ent in resources)
res.Add(ent.Key, ent.Value);
grid.Resources.MergedDictionaries.Add(res);
}

Related

Avalondock - Delayed layout restore for some panels

I'm using Load/Save layout similar way described on CodeProject. Catching LayoutSerializationCallback event and trying to find the corresponding viewModel for LayoutItem
private void LayoutSerializer_LayoutSerializationCallback(object sender, LayoutSerializationCallbackEventArgs e)
{
// This can happen if the previous session was loading a file
// but was unable to initialize the view ...
if (string.IsNullOrWhiteSpace(e.Model.ContentId) || (e.Content = ReloadItem(e.Model)) == null)
{
e.Cancel = true;
return;
}
}
private object ReloadItem(object item)
{
object ret = null;
switch (item)
{
case LayoutAnchorable anchorable:
//list of tools windows
ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
{
MapPanels.Add(anchorable);
}
break;
case LayoutDocument document:
// list of restored documents
ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
break;
default:
throw new NotImplementedException("Not implemented type of AD item ");
}
return ret;
}
This works fine when I have all ViewModels available when deserializing/restoring layout.
But I'm thinking about something like delayed layout restore. In my case, I have some documents and some panels available at the start. But there can be some panels (call them MapPanel) that are loaded later (viewModels are loaded somewhere in future). And I can't figure out, how to restore layout for these panels.
For this case, I have List MapPanels to store anchorable that are loaded at avalondock layout load and trying to restore them in BeforeInsertAnchorable in ILayoutUpdateStrategy. But when I debug it, stored LayoutAnchorable has different parents that stored one. So I assume that after canceling (e.Cancel = true) in LayoutSerializationCallback somehow modifies not restored anchorable.
public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer)
{
if (anchorableToShow.Content is ToolPanel tool)
{
if(tool is MapPanel)
{
anchorableToShow = LayoutSaveLoadUtil.Instance.MapPanels.FirstOrDefault(mp => mp.ContentId == anchorableToShow.ContentId);
}
var destPane = destinationContainer as LayoutAnchorablePane;
if (destinationContainer != null && destinationContainer.FindParent<LayoutFloatingWindow>() != null)
return false;
var dockLeftPane = layout.Descendents().OfType<LayoutAnchorablePane>().FirstOrDefault(d => d.Name == tool.PreferredLocation + "Pane");
if (dockLeftPane != null)
{
dockLeftPane.Children.Add(anchorableToShow);
return true;
}
return false;
}
return false;
}
So I'm curious what is the right approach to achieve this. I was also thinking about restoring layout (again) after MapPanel is loaded, but I don't know how to skip all other LayoutItems. So is there any possibility of how to restore a single Anchorable position, floating parent, docking, size, etc...?
So i probably figured out solution.
I have some panels (call them MapPanel) and theyr content isnt loaded when layout is restored from XML. In my case i have app, that has some documents and tabs, and in aditional, user can load aditional data to show maps.
And i needed to restore layout of this maps when user load them. (click button, selec where to load maps etc)
I have static class called LayoutSaveLoadUtil (as described on codeproject) when i have aditional list MapPanelsStorage of type LayoutAnchorable. To this list i store all layouts that missing content on layout restore and ContentId has specific prefix. This tells me that is MapPanel. Then i create a dummy content, assign it to this panel and set its visibility to false (so panel is invisible)
(this is called in layoutSerializationCallback)
private object ReloadItem(object item)
{
object ret = null;
switch (item)
{
case LayoutAnchorable anchorable:
//list of tools windows
ret = Manager.Tools.FirstOrDefault(i => i.ContentId == anchorable.ContentId);
if(ret == null && anchorable.ContentId.StartsWith(MapPanel.MapPanelPrefix))
{
//when layoutAnchorable is MapPanel (has MapPanel prefix) and its content is loaded yet
//store its layout into list to restore it later (when content is loaded)
MapPanelsStorage.Add(anchorable);
//Set anchorable visibility to false
anchorable.IsVisible = false;
//return dummy model for avalondock layout serialization
ret = new EmptyMapViewModel();
}
break;
case LayoutDocument document:
// list of restored documents
ret = Manager.Documents.FirstOrDefault(i => i.ContentId == document.ContentId);
break;
default:
throw new NotImplementedException("Not implemented type of AD item ");
}
return ret;
}
Then in BeforeInsertAnchorable (called when new panel is added into layout) i check if panel content is MapPanel, look for stored layout, try to find parent (LayoutAnchorablePane/LayoutDocumentPane) and then add it instead of DummyHidden panel, and just remove it from storage.
//hacky hacky to restore map panel layout when map is opened after layout is loaded
//in layout deserialization, deserialize layout for MapPanels that hasnt DataModels yet with DummyModel to preserve their layout
if (anchorableToShow.Content is MapPanel mappanel)
{
var storedMapsLayout = LayoutSaveLoadUtil.Instance.MapPanelsStorage;
//check if Map panel has stored layout from previous layout deserialization
var matchingAnchorable = storedMapsLayout.FirstOrDefault(m => m.ContentId == mappanel.ContentId);
if (matchingAnchorable != null)
{
//make preserved layout visible, so its parent and etc is restored. Without this, correct parent LayoutGroup isnt found
matchingAnchorable.IsVisible = true;
LayoutAnchorablePane matchingAnchorablePane;
LayoutDocumentPane matchingDocumentPane;
//find parent layoutGroup. This can be LayoutAnchorablePane or LayoutDocumentPane
if ((matchingAnchorablePane = matchingAnchorable.FindParent<LayoutAnchorablePane>()) != null)
{
//add new panel into layoutGroup with correct layout
matchingAnchorablePane.Children.Add(anchorableToShow);
//remove old dummy panel
matchingAnchorablePane.RemoveChild(matchingAnchorable);
//remove restored layout from storage
storedMapsLayout.Remove(matchingAnchorable);
return true;
}
else if ((matchingDocumentPane = matchingAnchorable.FindParent<LayoutDocumentPane>()) != null)
{
//add new panel into layoutGroup with correct layout
matchingDocumentPane.Children.Add(anchorableToShow);
//remove old dummy panel
matchingDocumentPane.RemoveChild(matchingAnchorable);
//remove restored layout from storage
storedMapsLayout.Remove(matchingAnchorable);
return true;
}
else
{
matchingAnchorable.IsVisible = false;
}
}
}

Find all TextBox controls in UWP Page

I need to find all TextBox(es) that are on a UWP Page but having no luck. I thought it would be a simple foreach on Page.Controls but this does not exist.
Using DEBUG I am able to see, for example, a Grid. But I have to first cast the Page.Content to Grid before I can see the Children collection. I do not want to do this as it may not be a Grid at the root of the page.
Thank you in advance.
UPDATE: This is not the same as 'Find all controls in WPF Window by type'. That is WPF. This is UWP. They are different.
You're almost there! Cast the Page.Content to UIElementCollection, that way you can get the Children collection and be generic.
You'll have to make your method recurse and look either for Content property if element is a UIElement or Children if element is UIElementCollection.
Here's an example:
void FindTextBoxex(object uiElement, IList<TextBox> foundOnes)
{
if (uiElement is TextBox)
{
foundOnes.Add((TextBox)uiElement);
}
else if (uiElement is Panel)
{
var uiElementAsCollection = (Panel)uiElement;
foreach (var element in uiElementAsCollection.Children)
{
FindTextBoxex(element, foundOnes);
}
}
else if (uiElement is UserControl)
{
var uiElementAsUserControl = (UserControl)uiElement;
FindTextBoxex(uiElementAsUserControl.Content, foundOnes);
}
else if (uiElement is ContentControl)
{
var uiElementAsContentControl = (ContentControl)uiElement;
FindTextBoxex(uiElementAsContentControl.Content, foundOnes);
}
else if (uiElement is Decorator)
{
var uiElementAsBorder = (Decorator)uiElement;
FindTextBoxex(uiElementAsBorder.Child, foundOnes);
}
}
Then you call that method with:
var tb = new List<TextBox>();
FindTextBoxex(this, tb);
// now you got your textboxes in tb!
You can also use the following generic method from the VisualTreeHelper documentation to get all your child controls of a given type:
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
It basically recursively get the children for the current item and add any item matching the requested type to the provided list.
Then, you just have to do the following somewhere to get your elements:
var allTextBoxes = new List<TextBox>();
FindChildren(allTextBoxes, this);
To my mind, you could do it in the same way as in WPF. Because UWP uses mostly the same XAML that WPF.
So, please check out answer for the same question about WPF

Programatically create a Popup with a Child element initialised by Caliburn Micro

Windows.Media.Captures has a handy CameraCaptureUI class that can be instantiated as follows to show a dialog to the user to capture photos or videos:
// Create dialog to Capture Video
CameraCaptureUI dialog = new CameraCaptureUI();
dialog.VideoSettings.Format = CameraCaptureUIVideoFormat.Mp4;
StorageFile file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Video);
if (file != null)
{
// Do something with file...
}
I would like to create my own custom audio capture class that works in a very similar way:
// Create dialog to Capture Audio
AudioCaptureUI dialog = new AudioCaptureUI();
StorageFile file = await dialog.CaptureFileAsync();
if (file != null)
{
// Do something with file...
}
To do the above, I created the following three files:
AudioCaptureUI - The class that a user instantiates to show the audio capture dialog
AudioCaptureView - UI View for the audio capture experience
AudioCaptureViewModel - ViewModel that contains all the audio capture logic
To create a full screen audio capture dialog, I have figured out that the best way is to use a Popup and set its child to the AudioCaptureView. The problem I have with this approach is that it is pushing me use a View-First pattern. Since I am using Caliburn Micro, I wanted to be able to use CM to instantiate a View by creating the ViewModel first.
What I currently have is something on the following lines:
public class AudioCaptureUI
{
private Popup _popup;
private TaskCompletionSource<StorageFile> _taskCompletionSource;
public IAsyncOperation<StorageFile> CaptureFileAsync()
{
// Force my View to be full screen
AudioCaptureView audioCaptureView = new AudioCaptureView
{
Width = Window.Current.Bounds.Width,
Height = Window.Current.Bounds.Height
};
// Creating View, instead of a ViewModel. Renders Caliburn Micro useless!
_popup = new Popup { Child = audioCaptureView };
if (_popup.Child != null)
{
SubscribeEvents();
_popup.IsOpen = true;
}
return AsyncInfo.Run(WaitForInput);
}
...
}
The above pattern works. However, I am forced to wire all my actions manually and cannot leverage Caliburn Micro's MVVM goodness.
How else should I instantiate a ViewModel programatically from my AudioCaptureUI class?
It is also important to highlight that I am working on a Windows Store app and using the WinRT CM port.
You could always port the WindowManager to WinRT in your own project. Looking at the source I don't think too much will need to change. https://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/net40/WindowManager.cs
You could bring over the Interface as well and use DI but for the sake of time here is the stand alone class. The main part for Model first binding is the ViewLocator.LocateForModel which returns the View from the ViewModel (aka the magic)
using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls.Primitives;
namespace Caliburn.Micro
{
public class WindowManager
{
public virtual void ShowPopup(object rootModel, object context = null, IDictionary<string, object> settings = null)
{
var popup = CreatePopup(rootModel, settings);
var view = ViewLocator.LocateForModel(rootModel, popup, context);
popup.Child = view;
//popup.SetValue(View.IsGeneratedProperty, true);
ViewModelBinder.Bind(rootModel, popup, null);
Caliburn.Micro.Action.SetTargetWithoutContext(view, rootModel);
var activatable = rootModel as IActivate;
if (activatable != null)
{
activatable.Activate();
}
var deactivator = rootModel as IDeactivate;
if (deactivator != null)
{
popup.Closed += delegate { deactivator.Deactivate(true); };
}
popup.IsOpen = true;
//popup.CaptureMouse();
}
protected virtual Popup CreatePopup(object rootModel, IDictionary<string, object> settings)
{
var popup = new Popup();
ApplySettings(popup, settings);
return popup;
}
bool ApplySettings(object target, IEnumerable<KeyValuePair<string, object>> settings)
{
if (settings != null)
{
var type = target.GetType();
foreach (var pair in settings)
{
var propertyInfo = type.GetPropertyCaseInsensitive(pair.Key);
if (propertyInfo != null)
{
propertyInfo.SetValue(target, pair.Value, null);
}
}
return true;
}
return false;
}
}
}
Then all you need to do is create an instance and give it a ViewModel:
var windowManager = new WindowManager();
windowManager.ShowPopup(new MyPopupThingViewModel());
Note: I've only used this in an 8.1 app so not 100% sure if it will completely work with 8.0
You might have some success with the Caliburn.Micro WindowManager. There isn't a great deal about it in the official documentation (you're best off searching Google and the CM discussions). I've used it in one of my applications where I needed to host a particular ViewModel in a new window, and wanted to utilise all of the Caliburn.Micro goodness (and my existing Views).
Have a look at the Caliburn.Micro.IWindowManager interface, you'll see some handy methods that you can call from a WindowManager instance (depending on the popup type you're after).
public interface IWindowManager
{
bool? ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null);
void ShowPopup(object rootModel, object context = null, IDictionary<string, object> settings = null);
void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null);
}
In my application, to pop up my a Window with my ViewModel of choice, I did something along these lines (your names inserted):
// Some basic Window settings.
dynamic settings = new ExpandoObject();
settings.Title = "Test Window";
settings.WindowStartupLocation = WindowStartupLocation.Manual;
settings.SizeToContent = SizeToContent.Manual;
settings.Width = 450;
settings.Height = 300;
var localAudioCaptureViewModel new AudioCaptureViewModel ();
WindowManagerFactory.WindowManager.ShowWindow(localAudioCaptureViewModel, null, settings); // I didn't require context (null)
Caliburn.Micro should resolve your Views to the correct ViewModels, and you're good to go.

C# Windows.Forms converting code to WPF

I am trying to convert my Windows Form program into a WPF program in order to get images in my NotifyIcon systemtray icon. I am having issues converting it though. My current program has a tabcontrol1 and it uses the function: TabPages and DrawItem. WPF does not have these functions, but instead of TabPages it has Items, but I do not know what to change "DrawItem" to for WPF.
The reason why I am using "DrawItem" in my Windows Form is to change the color of the Tab text.
Changing from:
if (!find<T>(p.Value))
{
T myPage = new T();
myPage.Text = "Ping";
this.FormstabControl.TabPages.Add(myPage);
this.FormstabControl.DrawItem += new DrawItemEventHandler(ListBox1_DrawItem);
myPage.DataGridView.DataSource = p.Result;
}
To:
if (!find<T>(p.Value))
{
T myPage = new T();
myPage.Text = "Ping";
this.WPFtabControl.Items.Add(myPage);
//this.WPFtabControl.DrawItem += new DrawItemEventHandler(ListBox1_DrawItem);
myPage.DataGridView.DataSource = p.Result;
}
I had to comment out DrawItem because I do not know what to use instead. Also, type "T" is of type "TabPages" not Item. Below is the Find function that checks to see if the Tab already exists in the tabcontrol. When I declare myPage of type T, it setsup the DataGridView from within a different class (same as where T is defined.) I tried to fix it by throwing the WPF TabControl1 into a Windows.Form TabControl2, then searching through that tabcontrol's tabpages instead of passing a WPF Tabcontrol Item.
private bool find<T>()
{
bool found = false;
System.Windows.Forms.TabControl FormstabControl= new System.Windows.Forms.TabControl();
FormstabControl.TabPages.Add(this.WPFtabControl.Items.ToString());
foreach (TabPage page in FormstabControl.TabPages)
{
if (page is T)
{
found = true;
break;
}
}
return found;
}
private bool find<T>(string text) where T : TabPage
{
bool found = false;
System.Windows.Forms.TabControl FormstabControl= new System.Windows.Forms.TabControl();
FormstabControl.TabPages.Add(this.WPFtabControl.Items.ToString());
foreach (TabPage page in FormstabControl.TabPages)
{
if (page is T && text.Equals(page.Text))
{
found = true;
break;
}
}
return found;
}
When I run this app and the adjacent function, it runs and completes and adds a BLANK text tab, but returns no data to the datagrid within the TabControl. I do not know what is wrong.
How can I incorporate images in my NotifyIcon? I incorporate it like this:
this.notifyIcon.ContextMenu = new ContextMenu();
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Hide", new EventHandler(hideApp)));
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Show", new EventHandler(showApp)));
this.notifyIcon.ContextMenu.MenuItems.Add(new MenuItem("Exit", new EventHandler(exitApp)));

WPF TreeView Scrolled to Bottom automatically?

WPF default TreeView is scrolled to bottom of the node automatically where as we need to show the top view of the tree view. How to do that?
Also I could not get the scroll viewer by walking down the Visual Tree.
Preselect top node and call TreeViewItem.BringIntoView method on selection changed event. Call TreeView.ItemContainerGenerator.ContainerFromItem(e.NewValue) to get hold of the TreeViewItem.
This code is very rough.
The key to getting the TreeViewItem.BringIntoView() to get an item to the top, is to first scroll the TreeView to the bottom rather than the top.
To do this, we need to access the ScrollViewer inside the TreeView's control template first. Lots of messing around IMO, that should have been provided in the framework from the outset.
Your item control in this case, should be your TreeViewItem that you are trying to get to the top.
The uxTree control is the TreeView.
item.IsSelected = true;
ScrollViewer scroller = (ScrollViewer)this.FindVisualChildElement(this.uxTree, typeof(ScrollViewer));
scroller.ScrollToBottom();
item.BringIntoView();
private FrameworkElement FindVisualChildElement(DependencyObject element, Type childType)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
var dependencyObject = VisualTreeHelper.GetChild(element, i);
var fe = (FrameworkElement)dependencyObject;
if (fe.GetType() == childType)
{
return fe;
}
FrameworkElement ret = null;
if (fe.GetType().Equals(typeof(ScrollViewer)))
{
ret = FindVisualChildElement((fe as ScrollViewer).Content as FrameworkElement, childType);
}
else
{
ret = FindVisualChildElement(fe, childType);
}
if (ret != null)
{
return ret;
}
}
return null;
}

Categories

Resources