WPF用户控制父级


183

我有一个MainWindow在运行时加载到的用户控件。我无法从中获取包含窗口的句柄UserControl

我已经尝试过this.Parent,但是它始终为null。有谁知道如何从WPF中的用户控件获取包含窗口的句柄?

这是控件的加载方式:

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

Answers:


346

尝试使用以下内容:

Window parentWindow = Window.GetWindow(userControlReference);

GetWindow方法将为您遍历VisualTree并找到承载控件的窗口。

您应该在控件加载后(而不是在Window构造函数中)运行此代码,以防止GetWindow方法返回null。例如,组织一个事件:

this.Loaded += new RoutedEventHandler(UserControl_Loaded); 

6
仍返回null。就像控件没有父级一样。
donniefitz2

2
我使用了上面的代码,并且获取parentWindow也为我返回了null。
Peter Walke

106
我发现了它返回null的原因。我将这段代码放入用户控件的构造函数中。您应该在控件加载后运行此代码。EG连线事件:this.Loaded + = new RoutedEventHandler(UserControl_Loaded);
Peter Walke

2
在查看了Paul的答复之​​后,可能有意义的是使用OnInitialized方法而不是Loaded。
Peter Walke

@PeterWalke,您解决了我很长一段时间的问题...谢谢
Waqas Shabbir,

34

我将补充我的经验。尽管使用Loaded事件可以完成此工作,但我认为重写OnInitialized方法可能更合适。加载在第一次显示窗口后发生。OnInitialized使您有机会进行任何更改,例如,在呈现窗口之前将控件添加到窗口中。


8
+1表示正确。有时,了解使用哪种技术可能是微妙的,特别是当您将事件和替代项(例如Loaded事件,OnLoaded替代项,Initialized事件,OnInitialized替代项等)投入到混合中时。在这种情况下,OnInitialized很有意义,因为您想找到父级,并且必须初始化控件以使父级“存在”。加载意味着不同。
格雷格D

3
Window.GetWindow仍然返回nullOnInitialized。似乎Loaded仅在事件中起作用。
Physikbuddha 2015年

必须在InitializeComponent()之前定义Initialized事件。无论如何,我的绑定(XAML)元素无法解析源(窗口)。所以我结束了使用Loaded Event。
Lenor

15

尝试使用VisualTreeHelper.GetParent或使用下面的递归函数查找父窗口。

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

我尝试从我的用户控件中传递此代码。我将此方法传递给此方法,但它返回null,表明它是树的结尾(根据您的评论)。你知道为什么吗 用户控件有一个父级,它是包含窗体。我如何处理此表格?
Peter Walke

2
我发现了它返回null的原因。我将这段代码放入用户控件的构造函数中。您应该在控件加载后运行此代码。EG连线了一个事件:this.Loaded + = new RoutedEventHandler(UserControl_Loaded)
Peter Walke 2009年

另一个问题在调试器中。VS将执行Load事件的代码,但找不到Window父级。
bohdan_trotsenko

1
如果要实现自己的方法,则应结合使用VisualTreeHelper和LogicalTreeHelper。这是因为某些非窗口控件(如Popup)没有可视的父级,并且看来从数据模板生成的控件没有逻辑的父级。
布赖恩·赖希勒

14

我需要在Loaded事件处理程序中使用Window.GetWindow(this)方法。换句话说,我同时使用了Ian Oakes的答案和Alex的答案来获得用户控件的父级。

public MainView()
{
    InitializeComponent();

    this.Loaded += new RoutedEventHandler(MainView_Loaded);
}

void MainView_Loaded(object sender, RoutedEventArgs e)
{
    Window parentWindow = Window.GetWindow(this);

    ...
}


7

如果发现此问题,而VisualTreeHelper不适用于您或不时地工作,则可能需要在算法中包括LogicalTreeHelper。

这是我正在使用的:

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

您错过了LogicalTreeHelper.GetParent代码中的方法名称。
xmedeko

这对我来说是最好的解决方案。
Jack B Nimble

6

这个怎么样:

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

5

我发现UserControl的父级在构造函数中始终为null,但在任何事件处理程序中,父级均已正确设置。我猜想它一定与控制树的加载方式有关。因此,要解决此问题,您只需在控件的Loaded事件中获取父项即可。

例如,该问题的示例签出WPF用户控件的DataContext为Null


1
您有点必须等待它首先出现在“树”中。有时很讨厌。
user7116

3

其他方式:

var main = App.Current.MainWindow as MainWindow;

为我工作,必须将其放在“ Loaded”事件中,而不是构造函数中(调出属性窗口,双击,它将为您添加处理程序)。
Contango 2015年

(我的投票是Ian接受的答案,这只是记录。)当用户控件位于带有ShowDialog的另一个窗口中并将内容设置为用户控件时,此操作不起作用。一种类似的方法是遍历App.Current.Windows并使用以下条件,其中idx从(Current.Windows.Count-1)到0(App.Current.Windows [idx] == userControlRef)为true。如果我们以相反的顺序执行此操作,则它很可能是最后一个窗口,并且只需一次迭代即可获得正确的窗口。userControlRef通常这个用户控件类中。
msanjay

3

它为我工作:

DependencyObject GetTopLevelControl(DependencyObject control)
{
    DependencyObject tmp = control;
    DependencyObject parent = null;
    while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
    {
        parent = tmp;
    }
    return parent;
}

3

这对我不起作用,因为它离树太远了,并且获得了整个应用程序的绝对根窗口:

Window parentWindow = Window.GetWindow(userControlReference);

但是,这样做可以立即获得窗口:

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

您应该使用空检查,而不是任意的'avoidInfiniteLoop'变量。更改“ while”以先检查null,如果不为null,则检查它是否不是窗口。否则,只需休息/退出。
Mark A. Donohoe

@MarquelV我听到了你的声音。通常,我对每个循环都添加“ avoidInfiniteLoop”检查,如果出现问题,理论上该循环可能会卡住。它是防御性编程的一部分。由于该程序避免了挂起,因此每隔一段时间,它就会获得丰厚的回报。在调试期间非常有用,并且在记录超限时在生产中非常有用。我使用了这项技术(还有许多其他技术)来编写健壮的代码,使之正常工作。
Contango

我得到了防御性编程,并且在原则上对此表示同意,但是作为代码审阅者,我认为这将因引入不属于实际逻辑流的任意数据而被标记。您已经具有通过检查null来停止无限递归所需的所有信息,因为不可能无限递归上一棵树。当然,您可能会忘记更新父级并有一个无限循环,但是您也很容易忘记更新该任意变量。换句话说,在不引入新的不相关数据的情况下检查空值已经是防御性编程。
Mark A. Donohoe

1
@MarquelIV我必须同意。添加额外的空检查是更好的防御性编程。
Contango


1
DependencyObject GetTopParent(DependencyObject current)
{
    while (VisualTreeHelper.GetParent(current) != null)
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current;
}

DependencyObject parent = GetTopParent(thisUserControl);

0

上面的镀金版本(我需要一个通用函数,可以Window在a 的上下文中推断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() 将在以下基础上正确推断Window:

  • Window通过遍历可视化树的根(如果在的上下文中使用UserControl
  • 使用它的窗口(如果在Window标记的上下文中使用)

0

不同的方法和不同的策略。就我而言,我无法通过使用VisualTreeHelper或Telerik的扩展方法来找到对话框的窗口来查找给定类型的父级。相反,我找到了我的对话框视图,该视图接受使用Application.Current.Windows的自定义内容注入。

public Window GetCurrentWindowOfType<TWindowType>(){
 return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}

0

Window.GetWindow(userControl)将返回窗口被初始化后,才实际窗口(InitializeComponent()方法结束)。

这意味着,如果将用户控件及其窗口一起初始化(例如,将用户控件放入窗口的xaml文件中),则在用户控件OnInitialized发生事件时,您将不会获得该窗口(它将为null),导致这种情况下,OnInitialized在初始化窗口之前会触发用户控件的事件。

这也意味着,如果您的用户控件在其窗口之后初始化,那么您可以在用户控件的构造函数中获取该窗口。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.