WPF User Control Parent
Asked Answered
D

17

201

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);
}
Darnell answered 19/11, 2008 at 18:13 Comment(0)
S
372

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); 
Sternberg answered 20/11, 2008 at 8:3 Comment(5)
Still returns null. It's as if the control just has no parent.Darnell
I used the code above and get the parentWindow also returns null for me.Charleycharlie
I found out the reason it's returning null. I was putting this code into the constructor of my user control. You should run this code after the control has loaded. E.G. wire up an event: this.Loaded += new RoutedEventHandler(UserControl_Loaded);Charleycharlie
After reviewing the response from Paul, it might make sense to use the OnInitialized method instead of Loaded.Charleycharlie
after I get my parent window like this it throws exception at resource folders. Owner property.....Firebird
M
35

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.

Mourn answered 12/2, 2010 at 14:49 Comment(3)
+1 for correct. Understanding which technique to use can be subtle at times, especially when you've got events and overrides thrown into the mix (Loaded event, OnLoaded override, Initialized event, OnInitialized override, etcetcetc). In this case, OnInitialized makes sense because you want to find the parent, and the control must be initialized for the parent to "exist". Loaded means something different.Claw
Window.GetWindow still returns null in OnInitialized. Seems to work in the Loaded event only.Threegaited
The Initialized Event must be defined before InitializeComponent(); Anyway, my Binded (XAML) Elements couldn't resolve the source (Window). So i ended to use the Loaded Event.Shewchuk
K
17

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);
    }
}
Kuwait answered 19/11, 2008 at 18:56 Comment(4)
I tried passing using this code from within my user control. I passed this into this method but it returned null, indicating that it is the end of the tree (according to your comment). Do you know why this is? The user control has a parent which is the containing form. How do I get a handle to this form?Charleycharlie
I found out the reason it's returning null. I was putting this code into the constructor of my user control. You should run this code after the control has loaded. E.G. wire up an event: this.Loaded += new RoutedEventHandler(UserControl_Loaded)Charleycharlie
Another issue is in the debugger. VS will execute the code of Load event, but it won't find the Window parent.Jahvist
If you are going to implement your own method, you should use a combination of VisualTreeHelper and LogicalTreeHelper. This is because some non-window controls (like Popup) do not have visual parents and it appears that controls generated from a data template do not have logical parents.Damask
C
15

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);

    ...
}
Chlorite answered 9/2, 2009 at 5:17 Comment(0)
P
10

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);
}
Patmore answered 6/8, 2014 at 18:32 Comment(2)
You miss a method name LogicalTreeHelper.GetParent in the code.Iced
This was the best solution for me.As
B
8

This approach worked for me but it is not as specific as your question:

App.Current.MainWindow
Benign answered 5/3, 2012 at 11:38 Comment(0)
R
7

Another way:

var main = App.Current.MainWindow as MainWindow;
Radiobroadcast answered 6/6, 2013 at 14:17 Comment(2)
Worked for me, have to put it in the "Loaded" event rather than the constructor (bring up the properties window, double click and it will add the handler for you).Fillander
(My vote is for the accepted answer by Ian, this is just for the record) This did not work when the user control is in another window with ShowDialog, setting content to the user control. A similar approach is to walk through App.Current.Windows and use the window where the following condition, for idx from (Current.Windows.Count - 1) to 0 (App.Current.Windows[idx] == userControlRef) is true. If we do this in reverse order, its likely to be the last window and we get the correct window with just one iteration. userControlRef is typically this within the UserControl class.Isoniazid
W
6

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));
        }
    } 
}
Wooldridge answered 18/5, 2011 at 17:10 Comment(0)
D
5

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

Denny answered 24/1, 2009 at 15:14 Comment(1)
You kind of have to wait for it to be in the "tree" first. Pretty obnoxious at times.Shaniceshanie
F
4

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;
}
Fleming answered 18/6, 2013 at 13:39 Comment(0)
U
3

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.

Underprivileged answered 8/7, 2020 at 11:42 Comment(0)
F
2

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();
Fillander answered 11/3, 2016 at 16:48 Comment(4)
You should use a null check instead of an arbitrary 'avoidInfiniteLoop' variable. Change your 'while' to check for null first, and if not null, then check if it's not a window. Otherwise, just break/exit.Supercool
@MarquelV I hear you. Generally, I add an "avoidInfiniteLoop" check to every loop that could in theory get stuck if something goes wrong. Its part of defensive programming. Every so often, it pays good dividends as the program avoids a hang. Very useful during debugging, and very useful in production if the overrun is logged. I use this technique (among many others) to enable writing robust code that just works.Fillander
I get defensive programming, and I agree in principle about it, but as a code reviewer, I think this would get flagged for introducing arbitrary data that isn't part of the actual logic flow. You already have all the information needed to stop infinite recursion by checking for null as it is impossible to recurse up a tree infinitely. Sure you could forget to update the parent and have an infinite loop, but you could just as easily forget to update that arbitrary variable. In other words, it is already defensive programming to check for null without the introduction of new, unrelated data.Supercool
@MarquelIV I have to agree. Adding an additional null check is better defensive programming.Fillander
W
1
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
Wooldridge answered 18/5, 2011 at 17:18 Comment(1)
Please delete this and integrate any point this makes that is not already covered into your other answer (which I've upvoted as its a good answer)Ciliata
G
1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);
Galarza answered 23/8, 2012 at 14:17 Comment(0)
H
1

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.

Hairstyle answered 5/6, 2020 at 0:34 Comment(0)
C
0

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)
Ciliata answered 2/6, 2016 at 16:26 Comment(0)
B
0

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;
}
Browbeat answered 28/6, 2019 at 10:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.