一个自定义的自动调整大小的WPF面板类

时间:2022-11-13 08:51:02

I'm trying to write a custom Panel class for WPF, by overriding MeasureOverride and ArrangeOverride but, while it's mostly working I'm experiencing one strange problem I can't explain.

我正在尝试为WPF编写一个自定义面板类,通过重写度量重写和安排重写,但是,当它主要工作时,我遇到了一个我无法解释的奇怪问题。

In particular, after I call Arrange on my child items in ArrangeOverride after figuring out what their sizes should be, they aren't sizing to the size I give to them, and appear to be sizing to the size passed to their Measure method inside MeasureOverride.

特别是,当我在确定了它们的大小之后,调用arrangement on我的子条目之后,它们并没有按照我给它们的大小进行调整,并且看起来是按照在MeasureOverride中传递给它们的度量方法的大小进行调整。

Am I missing something in how this system is supposed to work? My understanding is that calling Measure simply causes the child to evaluate its DesiredSize based on the supplied availableSize, and shouldn't affect its actual final size.

我是否遗漏了这个系统应该如何工作?我的理解是,调用度量只会让孩子根据提供的可用性评估它的需求大小,不应该影响它的实际最终大小。

Here is my full code (the Panel, btw, is intended to arrange children in the most space-efficient manner, giving less space to rows that don't need it and splitting remaining space up evenly among the rest--it currently only supports vertical orientation but I plan on adding horizontal once I get it working properly):

这是我的完整代码(面板,顺便说一句,是为了安排孩子空间效率最高的方式,让更少的空间行不需要,将剩余空间平分给其余——它目前只支持垂直方向但我计划添加水平一旦我得到它正常工作):

Edit: Thanks for the responses. I will examine them more closely in a moment. However let me clarify how my intended algorithm works since I didn't explain that.

编辑:谢谢你的回复。我一会儿会更仔细地检查它们。但是,让我解释一下我的算法是如何工作的,因为我没有解释。

First of all, the best way to think of what I'm doing is to imagine a Grid with each row set to *. This divides the space up evenly. However, in some cases the element in a row may not need all that space; if this is the case, I want to take any leftover space and give it to those rows that could use the space. If no rows need any extra space, I just try to space things evenly (that's what extraSpace is doing, it's only for that case).

首先,考虑我所做的事情的最好方法是想象一个网格,每一行都被设置为*。这将空间平均分配。然而,在某些情况下,行中的元素可能不需要所有的空间;如果是这种情况,我想取任何剩余的空间给那些需要空间的行。如果没有行需要任何额外的空间,我只是尝试均匀地分配东西(这就是extraSpace正在做的,它只适用于这种情况)。

I do this in two passes. The ultimate point of the first pass is to determine the final "normal size" of a row--i.e. the size of the rows that will be shrunk (given a size smaller than its desired size). I do this by stepping through smallest item to biggest and adjusting the calculated normal size at each step, by adding the leftover space from each small item to each subsequent larger item until no more items "fit" and then break.

我做了两次。第一步的最终目的是确定一排的最终“正常尺寸”。将被收缩的行的大小(给定小于其期望的大小)。我这样做的方法是,将最小的项目逐步扩展到最大,并在每个步骤中调整计算出的正常大小,将每个小项目的剩余空间添加到每个后续的大项目中,直到没有更多的项目“适合”,然后断开。

In the next pass I use this normal value to determine if an item can fit or not, simply by taking the Min of the normal size with the item's desired size.

在下一遍中,我使用这个正常值来确定一个项目是否适合,只需将正常大小的最小值与该项目所需的大小进行比较。

(I also changed the anonymous method to a lambda function for simplicity.)

(为了简单起见,我还将匿名方法更改为lambda函数。)

Edit 2: My algorithm seems to work great at determining the proper size of the children. However, the children just aren't accepting their given size. I tried Goblin's suggested MeasureOverride by passing PositiveInfinity and returning Size(0,0), but this causes the children to draw themselves as though there are no space constraints at all. The part that's not obvious about this is that it's happening because of a call to Measure. Microsoft's documentation on this subject is not at all clear, as I've read over each class and property description several times. However, it's now clear that calling Measure does in fact affect the rendering of the child, so I will attempt to split the logic up among the two functions as BladeWise suggested.

编辑2:我的算法似乎能很好地决定孩子的大小。然而,孩子们就是不接受他们给定的尺寸。我试图通过传递PositiveInfinity和返回Size(0,0)来实现Goblin的建议方法,但这会让孩子们把自己画成完全没有空间限制的样子。不明显的是,它的发生是由于测量的需要。微软关于这个主题的文档一点也不清楚,因为我已经读过每一个类和属性描述好几次了。但是,现在很明显,调用度量实际上会影响子函数的呈现,因此我将尝试按照BladeWise的建议,将逻辑分割到两个函数中。

Solved!! I got it working. As I suspected, I needed to call Measure() twice on each child (once to evaluate DesiredSize and a second to give each child its proper height). It seems odd to me that layout in WPF would be designed in such an odd way, where it's split up into two passes, but the Measure pass actually does two things: measures and sizes children and the Arrange pass does next to nothing besides actually physically position the children. Very bizarre.

解决了! !我明白了工作。正如我所怀疑的,我需要对每个孩子调用Measure()两次(一次评估需求大小,另一次给每个孩子合适的身高)。在我看来,WPF的布局设计的方式很奇怪,它被分成两部分,但实际上通过了两件事:测量和尺寸的儿童和安排的通过几乎没有任何实际的物理位置的孩子。非常奇怪。

I'll post the working code at the bottom.

我将在底部张贴工作代码。

First, the original (broken) code:

首先,原始(破损)代码:

protected override Size MeasureOverride( Size availableSize ) {
    foreach ( UIElement child in Children )
        child.Measure( availableSize );

    return availableSize;
}

protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
    double extraSpace = 0.0;
    var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child=>child.DesiredSize.Height; );
    double remainingSpace = finalSize.Height;
    double normalSpace = 0.0;
    int remainingChildren = Children.Count;
    foreach ( UIElement child in sortedChildren ) {
        normalSpace = remainingSpace / remainingChildren;
        if ( child.DesiredSize.Height < normalSpace ) // if == there would be no point continuing as there would be no remaining space
            remainingSpace -= child.DesiredSize.Height;
        else {
            remainingSpace = 0;
            break;
        }
        remainingChildren--;
    }

    // this is only for cases where every child item fits (i.e. the above loop terminates normally):
    extraSpace = remainingSpace / Children.Count;
    double offset = 0.0;

    foreach ( UIElement child in Children ) {
        //child.Measure( new Size( finalSize.Width, normalSpace ) );
        double value = Math.Min( child.DesiredSize.Height, normalSpace ) + extraSpace;
            child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
        offset += value;
    }

    return finalSize;
}

And here's the working code:

这里是工作代码:

double _normalSpace = 0.0;
double _extraSpace = 0.0;

protected override Size MeasureOverride( Size availableSize ) {
    // first pass to evaluate DesiredSize given available size:
    foreach ( UIElement child in Children )
        child.Measure( availableSize );

    // now determine the "normal" size:
    var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>( child => child.DesiredSize.Height );
    double remainingSpace = availableSize.Height;
    int remainingChildren = Children.Count;
    foreach ( UIElement child in sortedChildren ) {
        _normalSpace = remainingSpace / remainingChildren;
        if ( child.DesiredSize.Height < _normalSpace ) // if == there would be no point continuing as there would be no remaining space
            remainingSpace -= child.DesiredSize.Height;
        else {
            remainingSpace = 0;
            break;
        }
        remainingChildren--;
    }
    // there will be extra space if every child fits and the above loop terminates normally:
    _extraSpace = remainingSpace / Children.Count; // divide the remaining space up evenly among all children

    // second pass to give each child its proper available size:
    foreach ( UIElement child in Children )
        child.Measure( new Size( availableSize.Width, _normalSpace ) );

    return availableSize;
}

protected override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
    double offset = 0.0;

    foreach ( UIElement child in Children ) {
        double value = Math.Min( child.DesiredSize.Height, _normalSpace ) + _extraSpace;
        child.Arrange( new Rect( 0, offset, finalSize.Width, value ) );
        offset += value;
    }

    return finalSize;
}

It may not be super-efficient what with having to call Measure twice (and iterating Children three times), but it works. Any optimizations to the algorithm would be appreciated.

调用两次度量(并迭代三次子度量)可能不是非常有效,但它确实有效。对算法的任何优化都是值得赞赏的。

2 个解决方案

#1


8  

Let's see if I got right how the Panel should work:

让我们看看我是否理解了小组应该如何工作:

  • It should determine the desired size of every UIElement child
  • 它应该确定每个UIElement子元素的期望大小
  • Depending on such sizes, it should determine if there is some available space
  • 根据这些大小,它应该确定是否有一些可用的空间
  • In case such space exists, every UIElement size should be adjusted so that the entire space is filled (i.e. every element size will be incremented by a portion of the remaining space)
  • 如果存在这样的空间,则应该调整每个UIElement大小,以便填充整个空间(也就是说,每个元素大小将增加剩余空间的一部分)

If I get it right, your current implementation cannot accomplish this task, since you need to change the desired size of the children themselves, not only their the render size (which is done by the Measure and Arrange passes).

如果我做对了,您当前的实现不能完成这个任务,因为您需要更改孩子本身的期望大小,而不仅仅是他们的呈现大小(由度量和排列通过)。

Keep in mind that the Measure pass is used to determine how much space an UIElement would require, given a size constraint (the availableSize passed to the method). In case of a Panel, it invokes a Measure pass on its children too, but does not set the desired size of its children (in other words, the size of the children is an input for the measure pass of the panel). As for the Arrange pass, it is used to determine the rectangle where the UI element will be finally rendered, whatever the measured size. In case of a Panel, it invokes an Arrange pass on its children too, but just like the measure pass it will not change the desired size of the children (it will just define their render space).

请记住,在给定大小约束(传递给方法的availableSize)的情况下,度量pass用于确定UIElement需要多少空间。对于面板,它也调用一个传递给它的子组件的度量,但是不设置它的子组件的期望大小(换句话说,子组件的大小是面板的度量传递的输入)。对于安排传递,它用于确定最终呈现UI元素的矩形,无论大小。在面板的情况下,它也会调用它的子元素的一个安排传递,但是就像度量传递一样,它不会改变子元素的大小(它只会定义它们的呈现空间)。

To achieve the required behaviour:

达到所需的行为:

  • Split properly the logic between the Measure and Arrange pass (in your code, all the logic is in the Arrange pass, while the code used to determine how much space is required for each child should be placed in the measure pass)
  • 适当地在度量之间划分逻辑并安排pass(在您的代码中,所有逻辑都在安排pass中,而用于确定每个子项需要多少空间的代码应该放在度量pass中)
  • Use a proper AttachedProperty (i.e. RequiredHeight) in place of the desired size of the children (you have no control on the child size unless it is set to Auto, so there is no need to take DesiredSize)
  • 使用适当的AttachedProperty(例如required redheight)来替换所需的子节点的大小(除非将子节点的大小设置为Auto,否则无法控制子节点的大小)

Since I'm not sure I have understood the purpose of the panel, I wrote an example:

由于我不确定我是否理解这个小组的目的,我写了一个例子:

a. Create a new Wpf solution (WpfApplication1) and add a new class file (CustomPanel.cs*)

a.创建一个新的Wpf解决方案(WpfApplication1)并添加一个新的类文件(CustomPanel.cs*)

b. Open the CustomPanel.cs file and paste this code

b。打开CustomPanel。cs文件并粘贴此代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace WpfApplication1
{
 public class CustomPanel : Panel
 {

  /// <summary>
  /// RequiredHeight Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty RequiredHeightProperty = DependencyProperty.RegisterAttached("RequiredHeight", typeof(double), typeof(CustomPanel), new FrameworkPropertyMetadata((double)double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnRequiredHeightPropertyChanged)));

  private static void OnRequiredHeightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
  { 

  }

  public static double GetRequiredHeight(DependencyObject d)
  {
   return (double)d.GetValue(RequiredHeightProperty);
  }

  public static void SetRequiredHeight(DependencyObject d, double value)
  {
   d.SetValue(RequiredHeightProperty, value);
  }

  private double m_ExtraSpace = 0;

  private double m_NormalSpace = 0;

  protected override Size MeasureOverride(Size availableSize)
  {
   //Measure the children...
   foreach (UIElement child in Children)
    child.Measure(availableSize);

   //Sort them depending on their desired size...
   var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>(new Func<UIElement, double>(delegate(UIElement child)
   {
    return GetRequiredHeight(child);
   }));

   //Compute remaining space...
   double remainingSpace = availableSize.Height;
   m_NormalSpace = 0.0;
   int remainingChildren = Children.Count;
   foreach (UIElement child in sortedChildren)
   {
    m_NormalSpace = remainingSpace / remainingChildren;
    double height = GetRequiredHeight(child);
    if (height < m_NormalSpace) // if == there would be no point continuing as there would be no remaining space
     remainingSpace -= height;
    else
    {
     remainingSpace = 0;
     break;
    }
    remainingChildren--;
   }

   //Dtermine the extra space to add to every child...
   m_ExtraSpace = remainingSpace / Children.Count;
   return Size.Empty;  //The panel should take all the available space...
  }

  protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
  {
   double offset = 0.0;

   foreach (UIElement child in Children)
   {
    double height = GetRequiredHeight(child);
    double value = (double.IsNaN(height) ? m_NormalSpace : Math.Min(height, m_NormalSpace)) + m_ExtraSpace;
    child.Arrange(new Rect(0, offset, finalSize.Width, value));
    offset += value;
   }

   return finalSize;   //The final size is the available size...
  }
 }
}

c. Open the project MainWindow.xaml file and paste this code

c.打开项目主窗口。xaml文件并粘贴此代码

<Window x:Class="WpfApplication1.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="clr-namespace:WpfApplication1"
 Title="MainWindow" Height="350" Width="525">
 <Grid>
        <local:CustomPanel>
            <Rectangle Fill="Blue" local:CustomPanel.RequiredHeight="22"/>
            <Rectangle Fill="Red" local:CustomPanel.RequiredHeight="70"/>
            <Rectangle Fill="Green" local:CustomPanel.RequiredHeight="10"/>
            <Rectangle Fill="Purple" local:CustomPanel.RequiredHeight="5"/>
            <Rectangle Fill="Yellow" local:CustomPanel.RequiredHeight="42"/>
            <Rectangle Fill="Orange" local:CustomPanel.RequiredHeight="41"/>
        </local:CustomPanel>
    </Grid>
</Window>

#2


2  

I tried to simplify your code a bit:

我试图简化你的代码:

public class CustomPanel:Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
            child.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity));

        return new Size(0,0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double remainingSpace = Math.Max(0.0,finalSize.Height - Children.Cast<UIElement>().Sum(c => c.DesiredSize.Height));
        var extraSpace = remainingSpace / Children.Count;
        double offset = 0.0;

        foreach (UIElement child in Children)
        {
            double height = child.DesiredSize.Height + extraSpace;
            child.Arrange(new Rect(0, offset, finalSize.Width, height));
            offset += height;
        }

        return finalSize;
    }

}

A few notes:

一些笔记:

  • You shouldn't return available size in MeasureOverride - it could be positive infinity which will cause an exception. And since you basically don't care what size it is, just return new Size(0,0).
  • 你不应该在“测量覆盖”中返回可用的大小——它可能是正无穷大,这会导致异常。因为你根本不关心它的大小,只返回新的大小(0,0)
  • As for your problem with the height of the children - I'm thinking it has to do with the actual children - are they limited in size somehow via Style or properties in regards to HorizontalAlignment?
  • 至于你对孩子们身高的问题——我想这和孩子们的实际身高有关——他们的身高是否受到了限制?

EDIT: version 2.0:

编辑:2.0版本:

    public class CustomPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
            child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

        return new Size(0, 0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double optimumHeight = finalSize.Height / Children.Count;
        var smallElements = Children.Cast<UIElement>().Where(c => c.DesiredSize.Height < optimumHeight);
        double leftOverHeight = smallElements.Sum(c => optimumHeight - c.DesiredSize.Height);
        var extraSpaceForBigElements = leftOverHeight / (Children.Count - smallElements.Count());
        double offset = 0.0;

        foreach (UIElement child in Children)
        {
            double height = child.DesiredSize.Height < optimumHeight ? child.DesiredSize.Height : optimumHeight + extraSpaceForBigElements;
            child.Arrange(new Rect(0, offset, finalSize.Width, height));
            offset += height;
        }

        return finalSize;
    }

}

#1


8  

Let's see if I got right how the Panel should work:

让我们看看我是否理解了小组应该如何工作:

  • It should determine the desired size of every UIElement child
  • 它应该确定每个UIElement子元素的期望大小
  • Depending on such sizes, it should determine if there is some available space
  • 根据这些大小,它应该确定是否有一些可用的空间
  • In case such space exists, every UIElement size should be adjusted so that the entire space is filled (i.e. every element size will be incremented by a portion of the remaining space)
  • 如果存在这样的空间,则应该调整每个UIElement大小,以便填充整个空间(也就是说,每个元素大小将增加剩余空间的一部分)

If I get it right, your current implementation cannot accomplish this task, since you need to change the desired size of the children themselves, not only their the render size (which is done by the Measure and Arrange passes).

如果我做对了,您当前的实现不能完成这个任务,因为您需要更改孩子本身的期望大小,而不仅仅是他们的呈现大小(由度量和排列通过)。

Keep in mind that the Measure pass is used to determine how much space an UIElement would require, given a size constraint (the availableSize passed to the method). In case of a Panel, it invokes a Measure pass on its children too, but does not set the desired size of its children (in other words, the size of the children is an input for the measure pass of the panel). As for the Arrange pass, it is used to determine the rectangle where the UI element will be finally rendered, whatever the measured size. In case of a Panel, it invokes an Arrange pass on its children too, but just like the measure pass it will not change the desired size of the children (it will just define their render space).

请记住,在给定大小约束(传递给方法的availableSize)的情况下,度量pass用于确定UIElement需要多少空间。对于面板,它也调用一个传递给它的子组件的度量,但是不设置它的子组件的期望大小(换句话说,子组件的大小是面板的度量传递的输入)。对于安排传递,它用于确定最终呈现UI元素的矩形,无论大小。在面板的情况下,它也会调用它的子元素的一个安排传递,但是就像度量传递一样,它不会改变子元素的大小(它只会定义它们的呈现空间)。

To achieve the required behaviour:

达到所需的行为:

  • Split properly the logic between the Measure and Arrange pass (in your code, all the logic is in the Arrange pass, while the code used to determine how much space is required for each child should be placed in the measure pass)
  • 适当地在度量之间划分逻辑并安排pass(在您的代码中,所有逻辑都在安排pass中,而用于确定每个子项需要多少空间的代码应该放在度量pass中)
  • Use a proper AttachedProperty (i.e. RequiredHeight) in place of the desired size of the children (you have no control on the child size unless it is set to Auto, so there is no need to take DesiredSize)
  • 使用适当的AttachedProperty(例如required redheight)来替换所需的子节点的大小(除非将子节点的大小设置为Auto,否则无法控制子节点的大小)

Since I'm not sure I have understood the purpose of the panel, I wrote an example:

由于我不确定我是否理解这个小组的目的,我写了一个例子:

a. Create a new Wpf solution (WpfApplication1) and add a new class file (CustomPanel.cs*)

a.创建一个新的Wpf解决方案(WpfApplication1)并添加一个新的类文件(CustomPanel.cs*)

b. Open the CustomPanel.cs file and paste this code

b。打开CustomPanel。cs文件并粘贴此代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace WpfApplication1
{
 public class CustomPanel : Panel
 {

  /// <summary>
  /// RequiredHeight Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty RequiredHeightProperty = DependencyProperty.RegisterAttached("RequiredHeight", typeof(double), typeof(CustomPanel), new FrameworkPropertyMetadata((double)double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnRequiredHeightPropertyChanged)));

  private static void OnRequiredHeightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
  { 

  }

  public static double GetRequiredHeight(DependencyObject d)
  {
   return (double)d.GetValue(RequiredHeightProperty);
  }

  public static void SetRequiredHeight(DependencyObject d, double value)
  {
   d.SetValue(RequiredHeightProperty, value);
  }

  private double m_ExtraSpace = 0;

  private double m_NormalSpace = 0;

  protected override Size MeasureOverride(Size availableSize)
  {
   //Measure the children...
   foreach (UIElement child in Children)
    child.Measure(availableSize);

   //Sort them depending on their desired size...
   var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>(new Func<UIElement, double>(delegate(UIElement child)
   {
    return GetRequiredHeight(child);
   }));

   //Compute remaining space...
   double remainingSpace = availableSize.Height;
   m_NormalSpace = 0.0;
   int remainingChildren = Children.Count;
   foreach (UIElement child in sortedChildren)
   {
    m_NormalSpace = remainingSpace / remainingChildren;
    double height = GetRequiredHeight(child);
    if (height < m_NormalSpace) // if == there would be no point continuing as there would be no remaining space
     remainingSpace -= height;
    else
    {
     remainingSpace = 0;
     break;
    }
    remainingChildren--;
   }

   //Dtermine the extra space to add to every child...
   m_ExtraSpace = remainingSpace / Children.Count;
   return Size.Empty;  //The panel should take all the available space...
  }

  protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
  {
   double offset = 0.0;

   foreach (UIElement child in Children)
   {
    double height = GetRequiredHeight(child);
    double value = (double.IsNaN(height) ? m_NormalSpace : Math.Min(height, m_NormalSpace)) + m_ExtraSpace;
    child.Arrange(new Rect(0, offset, finalSize.Width, value));
    offset += value;
   }

   return finalSize;   //The final size is the available size...
  }
 }
}

c. Open the project MainWindow.xaml file and paste this code

c.打开项目主窗口。xaml文件并粘贴此代码

<Window x:Class="WpfApplication1.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="clr-namespace:WpfApplication1"
 Title="MainWindow" Height="350" Width="525">
 <Grid>
        <local:CustomPanel>
            <Rectangle Fill="Blue" local:CustomPanel.RequiredHeight="22"/>
            <Rectangle Fill="Red" local:CustomPanel.RequiredHeight="70"/>
            <Rectangle Fill="Green" local:CustomPanel.RequiredHeight="10"/>
            <Rectangle Fill="Purple" local:CustomPanel.RequiredHeight="5"/>
            <Rectangle Fill="Yellow" local:CustomPanel.RequiredHeight="42"/>
            <Rectangle Fill="Orange" local:CustomPanel.RequiredHeight="41"/>
        </local:CustomPanel>
    </Grid>
</Window>

#2


2  

I tried to simplify your code a bit:

我试图简化你的代码:

public class CustomPanel:Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
            child.Measure(new Size(double.PositiveInfinity,double.PositiveInfinity));

        return new Size(0,0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double remainingSpace = Math.Max(0.0,finalSize.Height - Children.Cast<UIElement>().Sum(c => c.DesiredSize.Height));
        var extraSpace = remainingSpace / Children.Count;
        double offset = 0.0;

        foreach (UIElement child in Children)
        {
            double height = child.DesiredSize.Height + extraSpace;
            child.Arrange(new Rect(0, offset, finalSize.Width, height));
            offset += height;
        }

        return finalSize;
    }

}

A few notes:

一些笔记:

  • You shouldn't return available size in MeasureOverride - it could be positive infinity which will cause an exception. And since you basically don't care what size it is, just return new Size(0,0).
  • 你不应该在“测量覆盖”中返回可用的大小——它可能是正无穷大,这会导致异常。因为你根本不关心它的大小,只返回新的大小(0,0)
  • As for your problem with the height of the children - I'm thinking it has to do with the actual children - are they limited in size somehow via Style or properties in regards to HorizontalAlignment?
  • 至于你对孩子们身高的问题——我想这和孩子们的实际身高有关——他们的身高是否受到了限制?

EDIT: version 2.0:

编辑:2.0版本:

    public class CustomPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
            child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

        return new Size(0, 0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double optimumHeight = finalSize.Height / Children.Count;
        var smallElements = Children.Cast<UIElement>().Where(c => c.DesiredSize.Height < optimumHeight);
        double leftOverHeight = smallElements.Sum(c => optimumHeight - c.DesiredSize.Height);
        var extraSpaceForBigElements = leftOverHeight / (Children.Count - smallElements.Count());
        double offset = 0.0;

        foreach (UIElement child in Children)
        {
            double height = child.DesiredSize.Height < optimumHeight ? child.DesiredSize.Height : optimumHeight + extraSpaceForBigElements;
            child.Arrange(new Rect(0, offset, finalSize.Width, height));
            offset += height;
        }

        return finalSize;
    }

}