簡體   English   中英

自定義自動調整大小的WPF Panel類

[英]A custom auto-sizing WPF Panel class

我正在嘗試通過覆蓋MeasureOverrideArrangeOverride為WPF編寫自定義Panel類,但是,雖然它主要工作,但我遇到了一個我無法解釋的奇怪問題。

特別是經過我打電話Arrange在我的子項ArrangeOverride搞清楚它們的大小應該是什么,他們沒有大小的尺寸,我給他們后,和似乎上漿傳遞給它們的尺寸Measure內方法MeasureOverride

我錯過了這個系統應該如何工作的東西嗎? 我的理解是調用Measure只會讓孩子根據提供的availableSize評估其DesiredSize ,並且不應該影響它的實際最終大小。

這是我的完整代碼(面板,順便說一句,旨在以最節省空間的方式安排孩子,為不需要它的行提供更少的空間,並在其余部分之間平均分配剩余空間 - 它目前僅支持垂直方向,但我打算一旦我正常工作就加水平):

編輯:感謝您的回復。 我馬上就會仔細研究它們。 但是,讓我澄清一下我的預期算法是如何工作的,因為我沒有解釋。

首先,想到我正在做的最好的方法是想象一個Grid,每行設置為*。 這樣可以均勻地划分空間。 但是,在某些情況下,行中的元素可能不需要所有空間; 如果是這種情況,我想占用任何剩余空間並將其分配給可以使用該空間的那些行。 如果沒有行需要任何額外的空間,我只是嘗試均勻地extraSpace事物(這就是extraSpace正在做的事情,它只適用於那種情況)。

我這兩次通過。 第一遍的最終點是確定一行的最終“正常大小” - 即將縮小的行的大小(給定小於其所需大小的大小)。 我這樣做是通過將最小項目逐步調整到最大值並在每一步調整計算出的正常大小,方法是將每個小項目的剩余空間添加到每個后續較大項目,直到沒有更多項目“適合”然后中斷。

在下一個過程中,我使用此正常值來確定項目是否適合,只需將正常大小的Min與項目所需的大小相對應即可。

(為簡單起見,我還將匿名方法更改為lambda函數。)

編輯2:我的算法似乎在確定孩子的正確大小方面非常有用。 然而,孩子們只是不接受他們給定的尺寸。 我通過傳遞PositiveInfinity並返回Size(0,0)來嘗試Goblin建議的MeasureOverride ,但是這會讓孩子們自己繪制,好像根本沒有空間限制。 對此不明顯的部分是由於調用Measure發生的事情。 微軟關於這個主題的文檔並不清楚,因為我已經多次閱讀過每個類和屬性描述。 但是,現在很清楚,調用Measure確實會影響孩子的渲染,因此我將嘗試將這些邏輯分解為BladeWise建議的兩個函數。

解決了!! 我搞定了。 我懷疑,我需要對每個孩子調用兩次Measure()(一次評估DesiredSize,一次給每個孩子一個適當的高度)。 對我來說,WPF中的布局會以如此奇怪的方式進行設計似乎很奇怪,它會分成兩個通道,但是測量通道實際上做了兩件事:測量尺寸的孩子和編程通道幾乎沒有任何實際的物理定位孩子們。 非常離奇。

我將在底部發布工作代碼。

首先,原始(破碎)代碼:

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

這是工作代碼:

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

不得不稱之為Measure兩次(並且迭代Children三次)可能不是非常有效,但是它有效。 可以理解對算法的任何優化。

讓我們看看Panel應該如何運作:

  • 它應該確定每個UIElement孩子的期望大小
  • 根據這些尺寸,它應確定是否有可用空間
  • 如果存在這樣的空間,則應調整每個UIElement大小以便填充整個空間(即每個元素大小將增加剩余空間的一部分)

如果我做對了,你當前的實現就無法完成這個任務,因為你需要改變孩子們自己想要的大小,而不僅僅是他們的渲染大小(由Measure和Arrange傳遞完成)。

請記住,在給定大小約束(傳遞給方法的availableSize )的情況下,Measure pass用於確定UIElement需要多少空間。 如果是Panel ,它也會調用其子項的Measure傳遞,但不會設置其子項的所需大小 (換句話說,子項的大小是面板測量傳遞的輸入)。 對於編配傳遞,它用於確定最終渲染UI元素的矩形,無論測量的大小如何。 如果是Panel ,它也會調用其子項上的Arrange傳遞,但就像測量傳遞一樣,它不會改變子項的所需大小 (它只會定義它們的渲染空間)。

要實現所需的行為:

  • 正確分割測量和排列傳遞之間的邏輯(在您的代碼中,所有邏輯都在編配傳遞中,而用於確定每個子項需要多少空間的代碼應放在測量傳遞中)
  • 使用適當的AttachedProperty (即RequiredHeight )代替所需的子項大小(除非將其設置為Auto ,否則無法控制子項大小,因此無需使用DesiredSize

由於我不確定我是否了解該小組的目的,我寫了一個例子:

一個。 創建一個新的Wpf解決方案( WpfApplication1 )並添加一個新的類文件(CustomPanel.cs *)

打開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。 打開項目MainWindow.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>

我嘗試簡化你的代碼:

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

}

幾點說明:

  • 您不應該在MeasureOverride中返回可用的大小 - 它可能是正無窮大,這將導致異常。 因為你基本上不在乎它的大小,只需返回新的大小(0,0)。
  • 至於你對孩子身高的問題 - 我認為它與實際的孩子有關 - 他們是否通過Style對象以某種方式限制大小?

編輯: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;
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM