【问题标题】:WPF User Control ParentWPF 用户控件父级
【发布时间】:2010-09-23 02:28:07
【问题描述】:

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

我试过this.Parent,但它总是为空。有谁知道如何从 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);
}

【问题讨论】:

    标签: c# .net wpf


    【解决方案1】:

    尝试使用以下方法:

    Window parentWindow = Window.GetWindow(userControlReference);
    

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

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

    this.Loaded += new RoutedEventHandler(UserControl_Loaded); 
    

    【讨论】:

    • 仍然返回 null。就好像控件没有父控件一样。
    • 我使用了上面的代码,得到的 parentWindow 也为我返回 null。
    • 我发现了它返回 null 的原因。我将此代码放入我的用户控件的构造函数中。您应该在控件加载后运行此代码。例如。连接一个事件:this.Loaded += new RoutedEventHandler(UserControl_Loaded);
    • 查看 Paul 的回复后,使用 OnInitialized 方法代替 Loaded 可能更有意义。
    • 在我得到这样的父窗口后,它会在资源文件夹中引发异常。业主财产.....
    【解决方案2】:

    我会补充我的经验。虽然使用 Loaded 事件可以完成这项工作,但我认为重写 OnInitialized 方法可能更合适。首次显示窗口后发生加载。 OnInitialized 让您有机会进行任何更改,例如,在呈现之前向窗口添加控件。

    【讨论】:

    • +1 表示正确。了解使用哪种技术有时可能很微妙,尤其是当您将事件和覆盖放入混合中时(Loaded 事件、OnLoaded 覆盖、Initialized 事件、OnInitialized 覆盖等)。在这种情况下, OnInitialized 是有意义的,因为您想找到父级,并且必须为父级“存在”初始化控件。加载意味着不同的东西。
    • Window.GetWindow 仍然在 OnInitialized 中返回 null。似乎仅适用于 Loaded 事件。
    • 必须在InitializeComponent()之前定义初始化事件;无论如何,我的绑定(XAML)元素无法解析源(窗口)。所以我结束了使用 Loaded Event。
    【解决方案3】:

    使用 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,表明它是树的结尾(根据您的评论)。你知道这是为什么吗?用户控件有一个父级,它是包含表单。我如何处理这个表格?
    • 我发现了它返回 null 的原因。我将此代码放入我的用户控件的构造函数中。您应该在控件加载后运行此代码。例如。连接一个事件:this.Loaded += new RoutedEventHandler(UserControl_Loaded)
    • 调试器中的另一个问题。 VS会执行Load事件的代码,但是找不到Window的父级。
    • 如果你要实现自己的方法,你应该使用 VisualTreeHelper 和 LogicalTreeHelper 的组合。这是因为一些非窗口控件(如 Popup)没有可视父项,而且从数据模板生成的控件似乎没有逻辑父项。
    【解决方案4】:

    我需要在 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);
    
        ...
    }
    

    【讨论】:

      【解决方案5】:

      如果您发现这个问题并且 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
      • 这对我来说是最好的解决方案。
      【解决方案6】:

      这种方法对我有用,但它不像你的问题那么具体:

      App.Current.MainWindow
      

      【讨论】:

        【解决方案7】:

        这个怎么样:

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

        【讨论】:

          【解决方案8】:

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

          例如查看这个问题WPF User Control's DataContext is Null

          【讨论】:

          • 您必须先等待它位于“树”中。有时很讨厌。
          【解决方案9】:

          另一种方式:

          var main = App.Current.MainWindow as MainWindow;
          

          【讨论】:

          • 为我工作,必须把它放在“Loaded”事件而不是构造函数中(打开属性窗口,双击它会为你添加处理程序)。
          • (我投票赞成 Ian 接受的答案,这只是为了记录)当用户控件在另一个带有 ShowDialog 的窗口中时,这不起作用,将内容设置为用户控件。类似的方法是遍历 App.Current.Windows 并使用以下条件的窗口,对于从 (Current.Windows.Count - 1) 到 0 (App.Current.Windows[idx] == userControlRef) 的 idx 为 真的。如果我们以相反的顺序执行此操作,它可能是最后一个窗口,我们只需一次迭代即可获得正确的窗口。 userControlRef 通常是 UserControl 类中的 this
          【解决方案10】:

          它对我有用:

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

          【讨论】:

            【解决方案11】:

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

            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,则检查它是否不是窗口。否则,只需中断/退出。
            • @MarquelV 我听到了。一般来说,我会为每个循环添加一个“avoidInfiniteLoop”检查,如果出现问题,理论上可能会卡住。它是防御性编程的一部分。每隔一段时间,它就会带来丰厚的回报,因为该程序避免了挂起。在调试期间非常有用,如果记录了溢出,在生产中非常有用。我使用这种技术(以及许多其他技术)来编写能够正常工作的健壮代码。
            • 我从事防御性编程,我原则上同意这一点,但作为一名代码审查员,我认为这会因为引入不属于实际逻辑流的任意数据而被标记。您已经拥有通过检查 null 来停止无限递归所需的所有信息,因为不可能无限递归一棵树。当然,您可能会忘记更新父级并有一个无限循环,但您也可能很容易忘记更新该任意变量。换句话说,在不引入新的、不相关的数据的情况下检查 null 是已经防御性编程。
            • @MarquelIV 我必须同意。添加额外的空检查是更好的防御性编程。
            【解决方案12】:

            如果您只想获取特定的父级,不仅是窗口,树结构中的特定父级,并且不使用递归或硬中断循环计数器,您可以使用以下内容:

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

            只是不要将此调用放在构造函数中(因为Parent 属性尚未初始化)。将其添加到加载事件处理程序或应用程序的其他部分。

            【讨论】:

              【解决方案13】:
              DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
              

              【讨论】:

              • 请删除此内容并将其整合到your other answer 中尚未涵盖的任何观点(我认为这是一个很好的答案)
              【解决方案14】:
              DependencyObject GetTopParent(DependencyObject current)
              {
                  while (VisualTreeHelper.GetParent(current) != null)
                  {
                      current = VisualTreeHelper.GetParent(current);
                  }
                  return current;
              }
              
              DependencyObject parent = GetTopParent(thisUserControl);
              

              【讨论】:

                【解决方案15】:

                Window.GetWindow(userControl) 只会在窗口初始化后返回实际窗口(@98​​7654322@ 方法完成)。

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

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

                【讨论】:

                  【解决方案16】:

                  上面的镀金版本(我需要一个通用函数,它可以在MarkupExtension 的上下文中推断Window:-

                  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 通过遍历可视化树(如果在 UserControl 的上下文中使用)
                  • 使用它的窗口(如果它用于Window 标记的上下文中)

                  【讨论】:

                    【解决方案17】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-01-17
                      • 1970-01-01
                      • 2013-07-11
                      • 1970-01-01
                      • 2021-06-09
                      相关资源
                      最近更新 更多