廊坊人才:【WPF学习】第六十七章 建立自定义面板

admin 6个月前 (04-22) 科技 50 0

|  前面两个章节划分先容了两个自界说控件:自界说的ColorPicker和FlipPanel控件。接下来先容派生自界说面板以及构建自界说绘图控件。

  建立自界说面板是一种特殊但较常见的自界说控件开发子集。前面以及先容过有关面板方面的知识,领会到面板驻留一个或多个子元素,而且实现了特定的结构逻辑以恰当地放置子元素。若是希望构建自己的可拖动的工具栏或可停靠的窗口系统,自界说面板是很主要的元素。当建立需要非标准特定结构的组合控件时,自界说面板通常很有用的,例如停靠工具栏。

  接下里先容一个基本的Canvas面板部门以及一个增强版本的WrapPanel面板两个简朴的示例。

一、两步结构历程

  每个面板都使用相同的装备:卖力改变子元素尺寸和放置子元素的两步结构历程。第一阶段是丈量阶段(measure pass),在这一阶段面板决议其子元素希望具有多大的尺寸。第二个阶段是排列阶段(layout pass),在这一阶段为每个控件指定界限。这两个步骤是必须的,由于在决议若何支解可用空间时,面板需要思量所有子元素的期望。

  可以通过重写名称为MeasureOverride()和ArrangeOverride()方式,为这两个步骤添加自己的逻辑,这两个方式是作为WPF结构系统的一部门在FrameworkElement类中界说的。奇异的名称使用标识MeasureOverride()和ArrangeOverride()方式取代在MeasureCore()和ArrangeCore()方式中界说的逻辑,后两个方式在UIElement类中界说的。这两个方式是不能被重写的。

  1、MeasureOverride()方式

  第一步是首先使用MeasureOverride()方式决议每个子元素希望多大的空间。然而,纵然是在MeasureOverride()方式中,也不能为子元素提供无限空间,至少,也应当将自元素限制在能够顺应面板可用空间的局限之内。此外,可能希望更严格地限制子元素。例如,具有按比例分配尺寸的两行的Grid面板,会为子元素提供可用高度的一样平常。StackPanel面板会为第一个元素提供所有可用空间,然后为第二个元素提供剩余的空间等等。

  每个MeasureOverride()方式的实现卖力遍历子元素聚集,并挪用每个子元素的Measure()方式。当挪用Measure()方式时,需要提供界限框——决议每个子空间最大可用空间的Size工具。在MeasureOverride()方式的最后,面板返回显示所有子元素所需的空间,并返回它们所期望的尺寸。

  下面是MeasureOverride()方式的基本结构,其中没有详细的尺寸细节:

Protected override Size MeasureOverride(Size constRaint)
        {
           //Examine all the children
            foreach (UIElement element in base.InternalChildren)
            {
               //Ask each child how much space it would like,given the
               //AVAIlableSize constraint
               Size availableSize=new Size{...};
                element.Measure(availableSize);
               //(you can now read element.DesiRedSize to get the requested size.)
            }
    
            //Indicate how mush space this panel requires.
            //This will be used to set the DesiredSize property of the panel.
            return new Size(...);
        }

  Measure()方式不返回数值。在为每个子元素挪用Measure()方式之后,子元素的DesiredSize属性提供了请求的尺寸。可以在为后续子元素执行盘算是(以及决议面板需要的总空间时)使用这一信息

  由于许多元素直接挪用了Measure()方式之后才会渲染它们自身,以是必须为每个子元素挪用Measure()方式,纵然不希望限制子元素的尺寸或使用DesiredSize属性也同样云云。若是希望让所有子元素能够自由获得它们所希望的所有空间,可以通报在两个方向上的值都是Double.PositiveInfinity的Size工具(ScrollViewer是使用这种计谋的一个元素,原因是它可以处置随便数目的内容)。然后子元素会返回其中所有内容所需要的空间。否则,子元素通常会返回其中内容需要的空间或可用空间——返回较小值。

  在丈量历程的末端,结构容器必须返回它所期望的尺寸。在简朴的面包中,可以通过组合每个子元素的期望尺寸盘算面板所期望的尺寸。

  Measure()方式触发MeasureOverride()方式。以是若是在一个结构容器中放置另一个结构容器,当挪用Measure()方式时,将会获得结构容器及其所有子元素所需要的总尺寸。

  2、ArrangeOverride()方式

  丈量完所有元素后,就可以在可用的空间中排列元素了。结构系统挪用面板的ArrangeOverride()方式,而面板为每个子元素挪用Arrange()方式,以高速子元素为它分配了多大的控件(Arrange()方式会触发ArrangeOverride()方式,这与Measure()方式会触发MeasureOverride()方式异常类似).

  当使用Measure()方式丈量条目时,通报能够界说可用空间界限的Size工具。当使用Arrange()方式放置条目时,通报能够界说条目尺寸和位置的System.Windows.Rect工具。这时,就像使用Canvas面板气概的X和Y坐标放置每个元素一样(坐标确定结构容器左上角与元素左上角之间的距离)。

  下面是ArrangeOverride()方式的基本结构。

 protected override Size ArrangeOverride(Size arrangeBounds)
{
    //Examine all the children.
    foreach(UIElement element in base.InternalChildren)
    {
        //AssIGn the child it's bounds.
        Rect bounds=new Rect(...);
        element.Arrange(bounds);
        //(You can now read element.ActualHeight and element.ActualWidth to find out the size it used ..)
    }
    //Indicate how much space this panel occupies.
    //This will be used to set the AcutalHeight and ActualWidth properties
    //of the panel.
    return arrangeBounds;
}

  当排列元素时,不能通报无限尺寸。然而,可以通过通报来自DesiredSize属性值,为元素提供它所期望的数值。也可以为元素提供比所需尺寸更大的空间。实际上,经常会泛起这种情形。例如,垂直的StackPanel面板为其子元素提供所请求的高度,然则为了子元素提供面板自己的整个宽度。同样,Grid面板使用具有牢固尺寸或按比例盘算尺寸的行,这些行的尺寸可能大于其内部元素所期望的尺寸。纵然已经在凭据内容改变尺寸的容器中放置了元素,若是使用Height和Width属性明确设置了元素的尺寸,那么仍可以扩展该元素。

  当使元素比所期望的尺寸更大时,就需要使用HorizontalAlignment和VerticalAlignment属性。元素内容被放置到指定界限内部的某个位置。

  由于ArrangeOverride()方式总是吸收界说的尺寸(而非无限的尺寸),以是为了设置面板的最终尺寸,可以返回通报的Size工具。实际上,许多结构容器就是接纳这一步骤来占有提供的所有空间。

二、Canvas面板的副本

  明白这两个方式的最快捷方式是研究Canvas类的内部事情原理,Canvas是最简朴的结构容器。为了建立自己的Canvas气概的面板,只需要简朴地继续Panel类,而且添加MeasureOverride()和ArrangeOverride()方式,如下所示:

public class CanvasClone:System.Windows.Controls.Panel
    {
        ...
    }

  Canvas面板在他们希望的位置放置子元素,而且为子元素设置它们希望的尺寸。以是,Canvas面板不需要盘算若何支解可用空间。这使得MeasureOverride()方式异常简朴。为每个子元素提供无限的空间:

protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);
            foreach (UIElement element in base.InternalChildren)
            {
                element.Measure(size);
            }
            return new Size();
        }

  注重,MeasureOverride()方式返回空的Size工具。这意味着Canvas 面板基本不请求人和空间,而是由用户明确地为Canvas面板指定尺寸,或者将其放置到结构容器中举行拉伸以填充整个容器的可用空间。

  ArrangeOverride()方式包罗的内容稍微多一些。为了确定每个元素的准确位置,Canvas面板使用附加属性(Left、Right、Top以及Bottom)。附加属性使用界说类中的两个辅助方式实现:GetProperty()和SetProperty()方式。

  下面是用于排列元素的代码:

protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            foreach (UIElement element in base.InternalChildren)
            {
                double x = 0;
                double y = 0;
                double left = Canvas.GetLeft(element);
                if (!DoubleUtil.IsNaN(left))
                {
                    x = left;
                }
                double top = Canvas.GetTop(element);
                if (!DoubleUtil.IsNaN(top))
                {
                    y = top;
                }
                element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
            }
            return finalSize;
        }

三、更好的WrapPanel面板

  WrapPanel面板执行一个简朴的功效,该功效有有时十分有用。该面板逐个地部署其子元素,一旦当前行的宽度用完,就会切换到下一行。但有时刻需要接纳一种方式来强制立刻换行,以便在新行中启动某个特定控件。只管WrapPanel面板原本没有提供这一功效,但通过建立自界说控件可以方便地添加该功效。只需要添加一个请求换行的附加属性即可。今后,面板中的子元素可使用该属性在适当位置换行。

  下面的代码清单显示了WrapBreakPanel类,该类添加了LINEBreakBeforeProperty附加属性。当将该属性设置为true时,这个属性会导致在元素之前立刻换行。

public class WrapBreakPanel : Panel
    {
        public static DependencyProperty LineBreakBeforeProperty;

        static WrapBreakPanel()
        {
            FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
            metadata.AffectsArrange = true;
            metadata.AffectsMeasure = true;
            LineBreakBeforeProperty = DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), metadata);

        }
        ...
    }

  与所有依赖项属性一样,LineBreakBefore属性被界说静态字段,然后在自界说类的静态组织函数中注册该属性。唯一的区别在于举行注册时使用的是RegisterAttached()方式而非Register()方式。

  用于LineBreakBefore属性的FrameworkPropertyMetadata工具明确指定该属性影响结构历程。以是,无论何时设置该属性,都市触发新的排列阶段。

  这里没有使用通例属性封装器封装这些附加属性,由于不在界说它们的同一个类中设置它们。相反,需要提供两个静态方式,这来改谁人方式能够使用DependencyObject.SetValue()方式在随便元素上设置这个属性。下面是LineBreakBefore属性需要的代码:

/// <summary>
        /// 设置附加属性值
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetLineBreakBefore(UIElement element, Boolean value)
        {
            element.SetValue(LineBreakBeforeProperty, value);
        }

        /// <summary>
        /// 获取附加属性值
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static Boolean GetLineBreakBefore(UIElement element)
        {
            return (bool)element.GetValue(LineBreakBeforeProperty);
        }

  唯一保留的细节是当执行结构逻辑时需要思量该属性。WrapBreakPanel面板的结构逻辑以WrapPanel面板的结构逻辑为基础。在丈量阶段,元素按行排列,从而使面板能够盘算需要的总空间。除非太大或LineBreakBefore属性被设置为true。否则每个元素都呗添加到当前行中。下面是完整的代码:

protected override Size MeasureOverride(Size constraint)
        {
            Size currentLineSize = new Size();
            Size panelSize = new Size();

            foreach (UIElement element in base.InternalChildren)
            {
                element.Measure(constraint);
                Size desiredSize = element.DesiredSize;

                if (GetLineBreakBefore(element) ||
                    currentLineSize.Width + desiredSize.Width > constraint.Width)
                {
                    // Switch to a new line (either because the element has requested it
                    // or space has run out).
                    panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
                    panelSize.Height += currentLineSize.Height;
                    currentLineSize = desiredSize;

                    // If the element is too wide to fit using the maximum width of the line,
                    // just give it a separate line.
                    if (desiredSize.Width > constraint.Width)
                    {
                        panelSize.Width = Math.Max(desiredSize.Width, panelSize.Width);
                        panelSize.Height += desiredSize.Height;
                        currentLineSize = new Size();
                    }
                }
                else
                {
                    // Keep adding to the current line.
                    currentLineSize.Width += desiredSize.Width;

                    // Make sure the line is as tall as its tallest element.
                    currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
                }
            }

            // Return the size required to fit all elements.
            // Ordinarily, this is the width of the constraint, and the height
            // is based on the size of the elements.
            // However, if an element is wider than the width given to the panel,
            // the desired width will be the width of that line.
            panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
            panelSize.Height += currentLineSize.Height;
            return panelSize;
        }
MeasureOverride

  上面代码中的主要细节是检查LineBreakBefore属性。这实现了普遍WrapPanel面板没有提供的分外逻辑。

  ArrangeOverride()方式的代码险些相同。区别在于:面板在最先结构一行之前需要决议该行的最大高度(凭据最高的元素确定)。这样,每个元素可以获得完整数目的可用空间,可用控件占用行的整个高度。与使用通俗的WrapPanel面板举行结构时的历程相同。下面是完整的代码:

protected override Size ArrangeOverride(Size arrangeBounds)
        {
            int firstInLine = 0;

            Size currentLineSize = new Size();

            double accumulatedHeight = 0;

            UIElementCollection elements = base.InternalChildren;
            for (int i = 0; i < elements.Count; i++)
            {

                Size desiredSize = elements[i].DesiredSize;

                if (GetLineBreakBefore(elements[i]) || currentLineSize.Width + desiredSize.Width > arrangeBounds.Width) //need to switch to another line
                {
                    arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i);

                    accumulatedHeight += currentLineSize.Height;
                    currentLineSize = desiredSize;

                    if (desiredSize.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line                    
                    {
                        arrangeLine(accumulatedHeight, desiredSize.Height, i, ++i);
                        accumulatedHeight += desiredSize.Height;
                        currentLineSize = new Size();
                    }
                    firstInLine = i;
                }
                else //continue to accumulate a line
                {
                    currentLineSize.Width += desiredSize.Width;
                    currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
                }
            }

            if (firstInLine < elements.Count)
                arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, elements.Count);

            return arrangeBounds;
        }

        private void arrangeLine(double y, double lineHeight, int start, int end)
        {
            double x = 0;
            UIElementCollection children = InternalChildren;
            for (int i = start; i < end; i++)
            {
                UIElement child = children[i];
                child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineHeight));
                x += child.DesiredSize.Width;
            }
        }
ArrangeOverride

  WrapBreakPanel面板使用起来十分简捷。下面的一些符号演示了使用WrapBreakPanel面板的一个示例。在该例中,WrapBreakPanel面板准确地支解行,而且凭据其子元素的尺寸盘算所需的尺寸:

<Window x:Class="CustomControlsClient.WrapBreakPanelTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lib="clr-namespace:CustomControls;assembly=CustomControls"
        Title="WrapBreakPanelTest" Height="300" Width="300">
       
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Margin" Value="3"></Setter>
                <Setter Property="Padding" Value="5"/>
            </Style>
        </StackPanel.Resources>
        <TextBlock Padding="5" Background="LightGray">Content above the WrapBreakPanel.</TextBlock>
        <lib:WrapBreakPanel>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
            <Button lib:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold">Button with Break</Button>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
            <Button>No Break Here</Button>
        </lib:WrapBreakPanel>
        <TextBlock Padding="5" Background="LightGray">Content below the WrapBreakPanel.</TextBlock>
    </StackPanel>
</Window>

  下图显示了若何注释上面的符号:

&NBsp;

,

Sunbet

Sunbet www.hzjdjfls.com Sunbet是Sunbet的官方网站。Sunbt官网有你喜欢的Sunbet、申博APP下载、Sunbet最新网址、Sunbet管理网最新网址等。

皇冠APP下载声明:该文看法仅代表作者自己,与本平台无关。转载请注明:廊坊人才:【WPF学习】第六十七章 建立自定义面板

网友评论

  • (*)

最新评论

文章归档