When you create an AppBar or a CommandBar in a UWP app, there's always an ellipsis hiding near the side of the control, like so:
I don't want it in my app but I haven't found any methods/properties within AppBarthat would help me get rid of it. It should be possible, because many of the default Windows 10 apps don't have it. For example, there's no ellipsis on the main menu bar below:
Is it possible to hide the ellipsis using AppBar, or do I have to use a SplitView or some other control to implement this?
First, try not to use AppBar in your new UWP apps.
The CommandBar control for universal Windows apps has been improved to
provide a superset of AppBar functionality and greater flexibility in
how you can use it in your app. You should use CommandBar for all new
universal Windows apps on Windows 10.
You can read more about it here.
Both CommandBar and AppBar can be full styled and templated. This gives you the ability to remove whatever UI elements you don't want to display.
This is how you do it -
Open your page in Blend, right click on CommandBar > Edit Template > Edit a Copy. Then make sure you select Define in Application as currently there's a bug in Blend which will fail to generate the styles if you choose This document.
Once you have all the styles, find the MoreButton control and set its Visibility to Collapsed (or you can remove it but what if you realise you need it later?).
Then you should have a CommandBar without the ellipsis.
Update for 2017
The visibility of the Ellipsis button can now be found in the OverflowButtonVisibility Property of a CommandBar. As above set it to Collapsed to hide it.
If you want to hide this button globally it enough to add
<Style x:Key="EllipsisButton" TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
to global resource file
I know this question is is not active any more, but for sake of completion I am proposing my answer.
Instead of changing the visibility by using Styles, I have written an AttachedProperty extension that is able to hide/show the MoreButton via data binding. This way you can show/hide it conditionally as you please.
Usage is as simple as binding your property to the extension:
<CommandBar extensions:CommandBarExtensions.HideMoreButton="{Binding MyBoolean}">
...
</CommandBar>
The extension code is as follows:
public static class CommandBarExtensions
{
public static readonly DependencyProperty HideMoreButtonProperty =
DependencyProperty.RegisterAttached("HideMoreButton", typeof(bool), typeof(CommandBarExtensions),
new PropertyMetadata(false, OnHideMoreButtonChanged));
public static bool GetHideMoreButton(UIElement element)
{
if (element == null) throw new ArgumentNullException(nameof(element));
return (bool)element.GetValue(HideMoreButtonProperty);
}
public static void SetHideMoreButton(UIElement element, bool value)
{
if (element == null) throw new ArgumentNullException(nameof(element));
element.SetValue(HideMoreButtonProperty, value);
}
private static void OnHideMoreButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var commandBar = d as CommandBar;
if (e == null || commandBar == null || e.NewValue == null) return;
var morebutton = commandBar.FindDescendantByName("MoreButton");
if (morebutton != null)
{
var value = GetHideMoreButton(commandBar);
morebutton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
}
else
{
commandBar.Loaded += CommandBarLoaded;
}
}
private static void CommandBarLoaded(object o, object args)
{
var commandBar = o as CommandBar;
var morebutton = commandBar?.FindDescendantByName("MoreButton");
if (morebutton == null) return;
var value = GetHideMoreButton(commandBar);
morebutton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
commandBar.Loaded -= CommandBarLoaded;
}
}
On initial binding it uses the Loaded event to apply the hiding once it has been loaded. The FindDescendantByName is another extension method that iterates the visual tree. You might want to create or grab one if your solution does not yet contain it.
Since I cannot add a comment to the particular answer I'll post it here.
The following page gives many examples that will find the child object to compliment #RadiusK's answer.
How can I find WPF controls by name or type?
The one that worked for me specifically in UWP was:
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null)
return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
Calling the code like this:
var morebutton = FindChild<Button>(commandBar, "MoreButton");
Building upon #RadiusK's answer (which has some issues), I came up with a conciser alternative that's tested and works:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace Linq
{
public static class CommandBarExtensions
{
public static readonly DependencyProperty HideMoreButtonProperty = DependencyProperty.RegisterAttached("HideMoreButton", typeof(bool), typeof(CommandBarExtensions), new PropertyMetadata(false, OnHideMoreButtonChanged));
public static bool GetHideMoreButton(CommandBar d)
{
return (bool)d.GetValue(HideMoreButtonProperty);
}
public static void SetHideMoreButton(CommandBar d, bool value)
{
d.SetValue(HideMoreButtonProperty, value);
}
static void OnHideMoreButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var CommandBar = d as CommandBar;
if (CommandBar != null)
{
var MoreButton = CommandBar.GetChild<Button>("MoreButton") as UIElement;
if (MoreButton != null)
{
MoreButton.Visibility = !(e.NewValue as bool) ? Visibility.Visible : Visibility.Collapsed;
}
else CommandBar.Loaded += OnCommandBarLoaded;
}
}
static void OnCommandBarLoaded(object sender, RoutedEventArgs e)
{
var CommandBar = sender as CommandBar;
var MoreButton = CommandBar?.GetChild<Button>("MoreButton") as UIElement;
if (MoreButton != null)
{
MoreButton.Visibility = !(GetHideMoreButton(CommandBar) as bool) ? Visibility.Visible : Visibility.Collapsed;
CommandBar.Loaded -= OnCommandBarLoaded;
}
}
public static T GetChild<T>(this DependencyObject Parent, string Name) where T : DependencyObject
{
if (Parent != null)
{
for (int i = 0, Count = VisualTreeHelper.GetChildrenCount(Parent); i < Count; i++)
{
var Child = VisualTreeHelper.GetChild(Parent, i);
var Result = Child is T && !string.IsNullOrEmpty(Name) && (Child as FrameworkElement)?.Name == Name ? Child as T : Child.GetChild<T>(Name);
if (Result != null)
return Result;
}
}
return null;
}
}
}
Related
I have a UserControl 'child' within another UserControl (that's acting as a TabItem in a TabControl). Between the child UserControl and the TabItem ancestor are a number of other controls (eg: Grids, a StackPanel, possibly a ScrollViewer, etc).
I want to access a property of the TabItem UserControl in my child UserControl and customised a commonly suggested recursive function that walks up the Visual tree. However, this always returned true at the first null check until I added a query on the Logical tree.
Code:
public MyTabItem FindParentTabItem(DependencyObject child)
{
DependencyObject parent = VisualTreeHelper.GetParent(child) ?? LogicalTreeHelper.GetParent(child);
// are we at the top of the tree
if (parent == null)
{
return null;
}
MyTabItem parentTabItem = parent as MyTabItem;
if (parentTabItem != null)
{
return parentTabItem;
}
else
{
//use recursion until it reaches the control
return FindParentTabItem(parent);
}
}
Unfortunately, this too returns null. When stepping through the method, I see it does find the correct UserControl TabItem, but then as it recurses(?) back through the returns, it reverts this back to null which is then returned to the calling method (in the child UserControl's Loaded event):
MyTabItem tab = FindParentTabItem(this);
How do I fix this so my method correctly returns the found MyTabItem?
Here's a working Unit-Tested solution.
public static T FindAncestor<T>(DependencyObject obj)
where T : DependencyObject
{
if (obj != null)
{
var dependObj = obj;
do
{
dependObj = GetParent(dependObj);
if (dependObj is T)
return dependObj as T;
}
while (dependObj != null);
}
return null;
}
public static DependencyObject GetParent(DependencyObject obj)
{
if (obj == null)
return null;
if (obj is ContentElement)
{
var parent = ContentOperations.GetParent(obj as ContentElement);
if (parent != null)
return parent;
if (obj is FrameworkContentElement)
return (obj as FrameworkContentElement).Parent;
return null;
}
return VisualTreeHelper.GetParent(obj);
}
Usage would be
FindAncestor<MyTabItemType>(someChild);
Edit:
Let's assume your xaml looks like what you describe it as:
<UserControl>
<Grid></Grid>
<StackPanel></StackPanel>
<!-- Probably also something around your child -->
<Grid>
<UserControl x:Name="child"/>
</Grid>
</UserControl>
You're currently in your child-xaml.cs
void OnChildUserControlLoaded(object sender, RoutedEventArgs e)
{
var parent = FindAncestor<ParentUserControlType>(this);
DoSomething(parent.SomeProperty);
}
Unless you do something you did not describe the code will work as is.
I suggest you provide a MCVE with all necessary information.
I'm wondering how I would go about this. I can't very well check that mouse is not over item1, item2, ...., can I? there should be some better way of doing this. I just want to deselect all the items if the user clicks on non-item space.
You can do what you want... in your Click handler, add this code:
HitTestResult hitTestResult = VisualTreeHelper.HitTest(uiElement, DragStartPosition);
TreeViewItem listBoxItem = hitTestResult.VisualHit.GetParentOfType<TreeViewItem>();
if (listBoxItem == null)
{
// user has clicked, but not on a TreeViewItem
}
The GetParentOfType method is an extension method that I created and is as follows:
public static T GetParentOfType<T>(this DependencyObject element) where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject)
parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type))
return parent as T;
return GetParentOfType<T>(parent);
}
Please note that extension methods need to be placed into a static class... you could always refactor it into a normal method if you prefer.
I have a form and I want to set the focus to a text box when certain user actions happen. I know the MVVM way of doing things is to bind to VM properties, however the TextBox does not have a property that will allow this to happen. What's the best way to set the focus from the VM?
I have created an IResult implementation that works quite well for achieving this. You can get the view from the ActionExecutionContext of the IResult, which you can then search (I search by name) for the control you want to focus.
public class GiveFocusByName : ResultBase
{
public GiveFocusByName(string controlToFocus)
{
_controlToFocus = controlToFocus;
}
private string _controlToFocus;
public override void Execute(ActionExecutionContext context)
{
var view = context.View as UserControl;
// add support for further controls here
List<Control> editableControls =
view.GetChildrenByType<Control>(c => c is CheckBox ||
c is TextBox ||
c is Button);
var control = editableControls.SingleOrDefault(c =>
c.Name == _controlToFocus);
if (control != null)
control.Dispatcher.BeginInvoke(() =>
{
control.Focus();
var textBox = control as TextBox;
if (textBox != null)
textBox.Select(textBox.Text.Length, 0);
});
RaiseCompletedEvent();
}
}
I have ommitted some extra code to get the view from the context when the view is a ChildWindow I can provide if you require.
Also GetChildrenByType is an extension method, here is one of many implementations available in the wild:
public static List<T> GetChildrenByType<T>(this UIElement element,
Func<T, bool> condition) where T : UIElement
{
List<T> results = new List<T>();
GetChildrenByType<T>(element, condition, results);
return results;
}
private static void GetChildrenByType<T>(UIElement element,
Func<T, bool> condition, List<T> results) where T : UIElement
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
UIElement child = VisualTreeHelper.GetChild(element, i) as UIElement;
if (child != null)
{
T t = child as T;
if (t != null)
{
if (condition == null)
results.Add(t);
else if (condition(t))
results.Add(t);
}
GetChildrenByType<T>(child, condition, results);
}
}
}
Your action would then be something like the following (invoked in Caliburn.Micro ActionMessage style).
public IEnumerable<IResult> MyAction()
{
// do whatever
yield return new GiveFocusByName("NameOfControlToFocus");
}
There is an easier way.
1º In the ViewModel add property _view as your UserControl
2º You must override OnViewLoaded of your ViewModel and
set _view to View object.
3º Set focus from any method.
public UserControlView _view { get; set; }
protected override void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
_view = (UserControlView)view;
}
public void SetFocus()
{
_view.TextBox1.Focus();
}
I hope help you.
I might be suffering of Monday's dumbness, but I can't find a nice way of expanding all treeview nodes after I've added them in code behind (something like treeView.ExpandAll()).
Any quick help?
In xaml you could do it as follows :
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
After playing around with all of the various methods for fully expanding and collapsing a tree view, by far the fastest method is the following. This method seems to work on very large trees.
Ensure your tree is virtualized, if it isn't virtualized then as soon as the tree gets to any kind of size it is going to become painfully slow whatever you do.
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
Assume that you have a view model backing your tree, each node on that view model that corresponds to a HierarchicalDataTemplate needs an IsExpanded property (it doesn't need to implement property changed). Assume these view models implement an interface like this:
interface IExpandableItem : IEnumerable
{
bool IsExpanded { get; set; }
}
The TreeViewItem style needs to be set as follows to bind the IsExpanded property in the view model to the view:
<Style
TargetType="{x:Type TreeViewItem}">
<Setter
Property="IsExpanded"
Value="{Binding
IsExpanded,
Mode=TwoWay}" />
</Style>
We are going to use this property to set the expansion state, but also, because the tree is virtualized this property is necessary to maintain the correct view state as the individual TreeViewItems get recycled. Without this binding nodes will get collapsed as they go out of view as the user browses the tree.
The only way to get acceptable speed on large trees is to work in code behind in the view layer. The plan is basically as follows:
Get hold of the current binding to the TreeView.ItemsSource.
Clear that binding.
Wait for the binding to actually clear.
Set the expansion state in the (now unbound) view model.
Rebind the TreeView.ItemsSource using the binding we cached in step 1.
Because we have virtualization enabled, performing a bind on TreeView.ItemsSource turns out to be very fast, even with a large view model. Likewise, when unbound updating the expansion state of the nodes should be very fast. This results in surprisingly fast updates.
Here is some code:
void SetExpandedStateInView(bool isExpanded)
{
var model = this.DataContext as TreeViewModel;
if (model == null)
{
// View model is not bound so do nothing.
return;
}
// Grab hold of the current ItemsSource binding.
var bindingExpression = this.TreeView.GetBindingExpression(
ItemsControl.ItemsSourceProperty);
if (bindingExpression == null)
{
return;
}
// Clear that binding.
var itemsSourceBinding = bindingExpression.ParentBinding;
BindingOperations.ClearBinding(
this.TreeView, ItemsControl.ItemsSourceProperty);
// Wait for the binding to clear and then set the expanded state of the view model.
this.Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new Action(() => SetExpandedStateInModel(model.Items, isExpanded)));
// Now rebind the ItemsSource.
this.Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new Action(
() => this.TreeView.SetBinding(
ItemsControl.ItemsSourceProperty, itemsSourceBinding)));
}
void SetExpandedStateInModel(IEnumerable modelItems, bool isExpanded)
{
if (modelItems == null)
{
return;
}
foreach (var modelItem in modelItems)
{
var expandable = modelItem as IExpandableItem;
if (expandable == null)
{
continue;
}
expandable.IsExpanded = isExpanded;
SetExpandedStateInModel(expandable, isExpanded);
}
}
WPF doesn't have an ExpandAll method. You'll need to loop through and set the property on each node.
See this question or this blog post.
I have done an ExpandAll that works also if your tree is set for virtualization (recycling items).
This is my code. Perhaps you should consider wrapping your hierarchy into a hierarchical model model view ?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using HQ.Util.General;
namespace HQ.Util.Wpf.WpfUtil
{
public static class TreeViewExtensions
{
// ******************************************************************
public delegate void OnTreeViewVisible(TreeViewItem tvi);
public delegate void OnItemExpanded(TreeViewItem tvi, object item);
public delegate void OnAllItemExpanded();
// ******************************************************************
private static void SetItemHierarchyVisible(ItemContainerGenerator icg, IList listOfRootToNodeItemPath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodeItemPath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodeItemPath.RemoveAt(0);
if (listOfRootToNodeItemPath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodeItemPath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodeItemPath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with ExpandItem is that this one open up the tree up to the target but will not expand the target itself,
/// while ExpandItem expand the target itself.
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">Any collectionic List. The collection should have every objet of the path to the targeted item from the root
/// to the target. For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2 (index 3)</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void SetItemHierarchyVisible(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
SetItemHierarchyVisible(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandItem(ItemContainerGenerator icg, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodePath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodePath.RemoveAt(0);
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
if (listOfRootToNodePath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodePath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with SetItemHierarchyVisible is that this one open the target while SetItemHierarchyVisible does not try to expand the target.
/// (SetItemHierarchyVisible just ensure the target will be visible)
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">The collection should have every objet of the path, from the root to the targeted item.
/// For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void ExpandItem(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
ExpandItem(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandSubWithContainersGenerated(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
foreach (object item in ic.Items)
{
var tvi = icg.ContainerFromItem(item) as TreeViewItem;
actionItemExpanded(tvi, item);
tvi.IsExpanded = true;
ExpandSubContainers(tvi, actionItemExpanded, referenceCounterTracker);
}
}
// ******************************************************************
/// <summary>
/// Expand any ItemsControl (TreeView, TreeViewItem, ListBox, ComboBox, ...) and their childs if any (TreeView)
/// </summary>
/// <param name="ic"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="referenceCounterTracker"></param>
public static void ExpandSubContainers(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
{
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
else if (icg.Status == GeneratorStatus.NotStarted)
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
if (icgSender.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
// Never use the following method in BeginInvoke due to ICG recycling. The same icg could be
// used and will keep more than one subscribers which is far from being intended
// ic.Dispatcher.BeginInvoke(actionHolder.Action, DispatcherPriority.Background);
// Very important to unsubscribe as soon we've done due to ICG recycling.
actionHolder.Execute();
referenceCounterTracker.ReleaseRef();
}
};
referenceCounterTracker.AddRef();
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
// Next block is only intended to protect against any race condition (I don't know if it is possible ? How Microsoft implemented it)
// I mean the status changed before I subscribe to StatusChanged but after I made the check about its state.
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
}
}
}
// ******************************************************************
/// <summary>
/// This method is asynchronous.
/// Expand all items and subs recursively if any. Does support virtualization (item recycling).
/// But honestly, make you a favor, make your life easier en create a model view around your hierarchy with
/// a IsExpanded property for each node level and bind it to each TreeView node level.
/// </summary>
/// <param name="treeView"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="actionAllItemExpanded"></param>
public static void ExpandAll(this TreeView treeView, Action<TreeViewItem, object> actionItemExpanded = null, Action actionAllItemExpanded = null)
{
var referenceCounterTracker = new ReferenceCounterTracker(actionAllItemExpanded);
referenceCounterTracker.AddRef();
treeView.Dispatcher.BeginInvoke(new Action(() => ExpandSubContainers(treeView, actionItemExpanded, referenceCounterTracker)), DispatcherPriority.Background);
referenceCounterTracker.ReleaseRef();
}
// ******************************************************************
}
}
And
using System;
using System.Threading;
namespace HQ.Util.General
{
public class ReferenceCounterTracker
{
private Action _actionOnCountReachZero = null;
private int _count = 0;
public ReferenceCounterTracker(Action actionOnCountReachZero)
{
_actionOnCountReachZero = actionOnCountReachZero;
}
public void AddRef()
{
Interlocked.Increment(ref _count);
}
public void ReleaseRef()
{
int count = Interlocked.Decrement(ref _count);
if (count == 0)
{
if (_actionOnCountReachZero != null)
{
_actionOnCountReachZero();
}
}
}
}
}
You have to include the following method in your project:
private void ExpandAllNodes(TreeViewItem treeItem)
{
treeItem.IsExpanded = true;
foreach (var childItem in treeItem.Items.OfType<TreeViewItem>())
{
ExpandAllNodes(childItem);
}
}
then, you only need to call it like this:
treeView.Items.OfType<TreeViewItem>().ToList().ForEach(ExpandAllNodes);
I have a user control that I load into a MainWindow at runtime. I cannot get a handle on the containing window from the UserControl.
I have tried this.Parent, but it's always null. Does anyone know how to get a handle to the containing window from a user control in WPF?
Here is how the control is loaded:
private void XMLLogViewer_MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem application = sender as MenuItem;
string parameter = application.CommandParameter as string;
string controlName = parameter;
if (uxPanel.Children.Count == 0)
{
System.Runtime.Remoting.ObjectHandle instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, controlName);
UserControl control = instance.Unwrap() as UserControl;
this.LoadControl(control);
}
}
private void LoadControl(UserControl control)
{
if (uxPanel.Children.Count > 0)
{
foreach (UIElement ctrl in uxPanel.Children)
{
if (ctrl.GetType() != control.GetType())
{
this.SetControl(control);
}
}
}
else
{
this.SetControl(control);
}
}
private void SetControl(UserControl control)
{
control.Width = uxPanel.Width;
control.Height = uxPanel.Height;
uxPanel.Children.Add(control);
}
Try using the following:
Window parentWindow = Window.GetWindow(userControlReference);
The GetWindow method will walk the VisualTree for you and locate the window that is hosting your control.
You should run this code after the control has loaded (and not in the Window constructor) to prevent the GetWindow method from returning null. E.g. wire up an event:
this.Loaded += new RoutedEventHandler(UserControl_Loaded);
I'll add my experience. Although using the Loaded event can do the job, I think it may be more suitable to override the OnInitialized method. Loaded occurs after the window is first displayed. OnInitialized gives you chance to make any changes, for example, add controls to the window before it is rendered.
Use VisualTreeHelper.GetParent or the recursive function below to find the parent window.
public static Window FindParentWindow(DependencyObject child)
{
DependencyObject parent= VisualTreeHelper.GetParent(child);
//CHeck if this is the end of the tree
if (parent == null) return null;
Window parentWindow = parent as Window;
if (parentWindow != null)
{
return parentWindow;
}
else
{
//use recursion until it reaches a Window
return FindParentWindow(parent);
}
}
I needed to use the Window.GetWindow(this) method within Loaded event handler. In other words, I used both Ian Oakes' answer in combination with Alex's answer to get a user control's parent.
public MainView()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainView_Loaded);
}
void MainView_Loaded(object sender, RoutedEventArgs e)
{
Window parentWindow = Window.GetWindow(this);
...
}
If you are finding this question and the VisualTreeHelper isn't working for you or working sporadically, you may need to include LogicalTreeHelper in your algorithm.
Here is what I am using:
public static T TryFindParent<T>(DependencyObject current) where T : class
{
DependencyObject parent = VisualTreeHelper.GetParent(current);
if( parent == null )
parent = LogicalTreeHelper.GetParent(current);
if( parent == null )
return null;
if( parent is T )
return parent as T;
else
return TryFindParent<T>(parent);
}
This approach worked for me but it is not as specific as your question:
App.Current.MainWindow
How about this:
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
public static class ExVisualTreeHelper
{
/// <summary>
/// Finds the visual parent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sender">The sender.</param>
/// <returns></returns>
public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
{
if (sender == null)
{
return (null);
}
else if (VisualTreeHelper.GetParent(sender) is T)
{
return (VisualTreeHelper.GetParent(sender) as T);
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(sender);
return (FindVisualParent<T>(parent));
}
}
}
I've found that the parent of a UserControl is always null in the constructor, but in any event handlers the parent is set correctly. I guess it must have something to do with the way the control tree is loaded. So to get around this you can just get the parent in the controls Loaded event.
For an example checkout this question WPF User Control's DataContext is Null
Another way:
var main = App.Current.MainWindow as MainWindow;
It's working for me:
DependencyObject GetTopLevelControl(DependencyObject control)
{
DependencyObject tmp = control;
DependencyObject parent = null;
while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
{
parent = tmp;
}
return parent;
}
This didn't work for me, as it went too far up the tree, and got the absolute root window for the entire application:
Window parentWindow = Window.GetWindow(userControlReference);
However, this worked to get the immediate window:
DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
while ((parent is Window)==false)
{
parent = VisualTreeHelper.GetParent(parent);
avoidInfiniteLoop++;
if (avoidInfiniteLoop == 1000)
{
// Something is wrong - we could not find the parent window.
break;
}
}
Window window = parent as Window;
window.DragMove();
If you just want to get a specific parent, not only the window, a specific parent in the tree structure, and also not using recursion, or hard break loop counters, you can use the following:
public static T FindParent<T>(DependencyObject current)
where T : class
{
var dependency = current;
while((dependency = VisualTreeHelper.GetParent(dependency) ?? LogicalTreeHelper.GetParent(dependency)) != null
&& !(dependency is T)) { }
return dependency as T;
}
Just don't put this call in a constructor (since the Parent property is not yet initialized). Add it in the loading event handler, or in other parts of your application.
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
DependencyObject GetTopParent(DependencyObject current)
{
while (VisualTreeHelper.GetParent(current) != null)
{
current = VisualTreeHelper.GetParent(current);
}
return current;
}
DependencyObject parent = GetTopParent(thisUserControl);
The Window.GetWindow(userControl) will return the actual window only after the window was initialized (InitializeComponent() method finished).
This means, that if your user control is initialized together with its window (for instance you put your user control into the window's xaml file), then on the user control's OnInitialized event you will not get the window (it will be null), cause in that case the user control's OnInitialized event fires before the window is initialized.
This also means that if your user control is initialized after its window, then you can get the window already in the user control's constructor.
Gold plated edition of the above (I need a generic function which can infer a Window within the context of a MarkupExtension:-
public sealed class MyExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider) =>
new MyWrapper(ResolveRootObject(serviceProvider));
object ResolveRootObject(IServiceProvider serviceProvider) =>
GetService<IRootObjectProvider>(serviceProvider).RootObject;
}
class MyWrapper
{
object _rootObject;
Window OwnerWindow() => WindowFromRootObject(_rootObject);
static Window WindowFromRootObject(object root) =>
(root as Window) ?? VisualParent<Window>((DependencyObject)root);
static T VisualParent<T>(DependencyObject node) where T : class
{
if (node == null)
throw new InvalidOperationException("Could not locate a parent " + typeof(T).Name);
var target = node as T;
if (target != null)
return target;
return VisualParent<T>(VisualTreeHelper.GetParent(node));
}
}
MyWrapper.Owner() will correctly infer a Window on the following basis:
the root Window by walking the visual tree (if used in the context of a UserControl)
the window within which it is used (if it is used in the context of a Window's markup)
Different approaches and different strategies. In my case I could not find the window of my dialog either through using VisualTreeHelper or extension methods from Telerik to find parent of given type. Instead, I found my my dialog view which accepts custom injection of contents using Application.Current.Windows.
public Window GetCurrentWindowOfType<TWindowType>(){
return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}