WPF在VisualTree上增加Visual

2022-06-27 17:12:21

作为一个WPF控件开发者,我在工作中经常遇到如本文标题所示的问题。其实,这个问题并不是很难,只是在操作上有些繁琐。本文将尝试对这个问题进行解答,并且对相关的一些技术细节加以探讨。

先从我遇到的一个典型的问题开始吧:写一个MyElement类,要求如下:

    从FrameworkElement继承增加一个Button到它的VisualTree上

    在Visual上有一个AddVisualChild方法,相信很多刚接触这个方法的同学们(好吧,至少我是这样)都会“顾名思义”地认为这个方法就可以解决本文的问题。再加上MSDN上也给出了一个例子来“火上浇油”一把。于是,一阵窃喜之后,我兴奋地敲出了以下代码:

        class MyElement : FrameworkElement
        {
            private Button _button = new Button() { Content = "I'm a Button!"};        
    
            public MyElement()
            {
                this.AssembleVisualChildren();
            }
    
            private void AssembleVisualChildren()
            {
                this.AddVisualChild(this._button);
            }
            protected override int VisualChildrenCount
            {
                get
                {
                    return 1;
                }
            }
            protected override Visual GetVisualChild(int index)
            {            
                return this._button ;
            }
         }

    然后将这个MyElement加入测试窗口,代码如下:

    <Window 
        x:Class="AddVisualChildTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:loc="clr-namespace:AddVisualChildTest"
        WindowStartupLocation="CenterScreen"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <loc:MyElement Margin="10"/>
        </Grid>
    </Window>

    运行后的结果如下:

    空空如也!嗯,被忽悠了。一阵失落、打击之后,我的好奇心被激发了:这是为什么呢?于是我狂找资料,终于被我发现了:

    实际上,在上面这个例子中,AddVisualChild这个方法只是在MyElement和Button之间建立起了一种VisualTree上的父子关系,但是并没有将Button挂接到MyElement的VisualTree上,所以最终我们没有在屏幕上看到这个Button。

    为了将Button真正挂接到MyElement的VisualTree上,还需要额外做一件事情:在VisualTree上为这个Button分配空间并且指定位置,这个过程叫做Layout。此过程分两个部分:一个是Measure,另一个是Arrange。这两个过程在FrameworkElement上对应着两个方法:MeasureOverride和ArrangeOverride方法。具体做法如下:

            protected override Size MeasureOverride(Size availableSize)
            {
                if (this.VisualChildrenCount > 0)
                {
                    UIElement child = this.GetVisualChild(0) as UIElement;
                    Debug.Assert(child != null); // !Assert
                    child.Measure(availableSize);
                    return child.DesiredSize;
                }
    
                return availableSize;
            }
    
            protected override Size ArrangeOverride(Size finalSize)
            {
                Rect arrangeRect = new Rect()
                {
                    Width = finalSize.Width,
                    Height = finalSize.Height
                };
    
                if (this.VisualChildrenCount > 0)
                {
                    UIElement child = this.GetVisualChild(0) as UIElement;
                    Debug.Assert(child != null); // !Assert
                    child.Arrange(arrangeRect);
                }
    
                return finalSize;
            }

    再次运行程序:

    目标实现。

    由此,我们可以总结出这个问题的解决方案如下:

      在MyElement的构造器中调用AddVisualChild方法;

      重写VisualChildCount属性;

      重写GetVisualChild方法;

      重写MeasureOverride方法;

      重写ArrangeOverride方法; 

      另外,WPF在此问题的解决上也为开发者提供了一些必要的帮助。就我所知的,有如下几个内容:

      1、Panel

      还是本文开始提到的问题,只不过要将其中的FrameworkElement换为Panel。除了上面所提到的方法,Panel为我们提供了更加方便的实现方式。代码如下:

          class MyElement : Panel
          {
              private Button _button = new Button() { Content = "I'm a Button!" };
      
              public MyElement()
              {
                  this.Children.Add(_button);
              }
      
              protected override Size MeasureOverride(Size availableSize)
              {
                  if (this.VisualChildrenCount > 0)
                  {
                      UIElement child = this.GetVisualChild(0) as UIElement;
                      Debug.Assert(child != null); // !Assert
                      child.Measure(availableSize);
                      return child.DesiredSize;
                  }
      
                  return availableSize;
              }
              protected override Size ArrangeOverride(Size finalSize)
              {
                  Rect arrangeRect = new Rect()
                  {
                      Width = finalSize.Width,
                      Height = finalSize.Height
                  };
      
                  if (this.VisualChildrenCount > 0)
                  {
                      UIElement child = this.GetVisualChild(0) as UIElement;
                      Debug.Assert(child != null); // !Assert
                      child.Arrange(arrangeRect);
                  }
      
                  return finalSize;
              }
          }

      之所以能这样做的原因是Panel已经替我们将如下几个工作封装在了UIElementCollection(Panel的Children属性)中:

        AddVisualChild

        VisualChildCount

        GetVisualChild

        2、VisualCollection

        另外,在这个过程中,我们还可以使用一个叫做VisualCollection的类来作为所有>

        到此这篇关于WPF在VisualTree上增加Visual的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持易采站长站。