Пользовательская автокалибровка класс Панели WPF

Я пытаюсь записать пользовательское Panel класс для WPF, путем переопределения MeasureOverride и ArrangeOverride но, в то время как это является главным образом рабочим, я испытываю одну странную проблему, которую я не могу объяснить.

В частности, после того, как я звоню Arrange на моих дочерних объектах в ArrangeOverride после выяснения, каковы их размеры должны быть, они не измеряют к размеру, который я даю им и, кажется, измеряю к размеру, переданному их Measure метод внутри MeasureOverride.

Я пропускаю что-то в том, как эта система, как предполагается, работает? Мое понимание является тем вызовом Measure просто заставляет ребенка оценивать DesiredSize на основе предоставленного availableSize, и не должен влиять на его фактический заключительный размер.

Вот мой полный код (Панель, btw, предназначается для расположения дочерних элементов самым эффективным пространством способом, давая меньше пространства строкам, которым не нужен он и разделяющий остающееся пространство равномерно среди остальных - это в настоящее время только поддерживает вертикальную ориентацию, но я планирую добавление горизонтального, после того как я получаю его работающий правильно):

Править: Спасибо за ответы. Я исследую их более тесно через мгновение. Однако позвольте мне разъяснить, как мой намеченный алгоритм работает, так как я не объяснил это.

В первую очередь, лучший способ думать, что я делаю, состоит в том, чтобы вообразить Сетку с каждым набором строк к *. Это делит пространство равномерно. Однако в некоторых случаях элементу подряд, возможно, не понадобится все то пространство; если это верно, я хочу занять любое оставшееся место и дать его тем строкам, которые могли использовать пространство. Если никаким строкам не нужно никакое дополнительное пространство, я просто пытаюсь расположить вещи с интервалами равномерно (это что extraSpace делает, это только для того случая).

Я делаю это в двух передачах. Окончательная точка первичной обработки должна определить заключительный "нормальный размер" строки - т.е. размер строк, которые будут уменьшены (учитывая размер, меньший, чем его желаемый размер). Я делаю это путем продвижения через самый маленький объект в самый большой и корректировки расчетного нормального размера на каждом шаге путем добавления оставшегося пространства от каждого небольшого объекта до каждого последующего большего объекта, пока больше объектов не "соответствует" и затем повреждается.

В следующей передаче я использую это нормальное значение, чтобы определить, может ли объект соответствовать или нет, просто путем взятия Min из нормального размера с желаемым размером объекта.

(Я также изменил анонимный метод на функцию лямбды для простоты.)

Редактирование 2: Мой алгоритм, кажется, работает отлично при определении надлежащего размера детей. Однако дети просто не принимают свой данный размер. Я попробовал Гоблина, предложил MeasureOverride путем передачи PositiveInfinity и возврата Размера (0,0), но это заставляет детей привлекать себя, как будто нет никаких пространственных ограничений вообще. Часть это не очевидно об этом, - то, что это происходит из-за вызова с Measure. Документация Microsoft относительно этого предмета нисколько не является четкой, поскольку я перечитывал по каждому классу и описанию свойства несколько раз. Однако это теперь ясно тот вызов Measure действительно на самом деле влияет на рендеринг ребенка, таким образом, я попытаюсь разделить логику среди двух функций как предложенный BladeWise.

Решенный!! Я получил его работа. Как я подозревал, я должен был назвать Меру () дважды на каждом ребенке (однажды для оценки 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 три раза), но это работает. Любая оптимизация к алгоритму ценилась бы.

9
задан devios1 9 June 2010 в 15:24
поделиться

2 ответа

Давайте посмотрим, правильно ли я понял, как должна работать Panel:

  • Она должна определить желаемый размер каждого UIElement дочернего элемента
  • В зависимости от этих размеров она должна определить, есть ли свободное пространство
  • Если такое пространство есть, то размер каждого UIElement должен быть скорректирован так, чтобы заполнить все пространство (т.е. каждый размер элемента будет увеличен на часть оставшегося пространства)

Если я правильно понял, то ваша текущая реализация не может решить эту задачу, т.к. нужно изменить желаемый размер самих элементов. т.е. размер каждого элемента будет увеличен на часть оставшегося пространства)

Если я правильно понял, ваша текущая реализация не может решить эту задачу, поскольку вам нужно изменить желаемый размер самих дочерних элементов, а не только их размер рендера (что делается передачами Measure и Arrange).

Следует помнить, что передача Measure используется для определения того, сколько места потребуется UIElement, учитывая ограничение на размер (availableSize, переданное методу). В случае Panel, он также вызывает передачу Measure для своих дочерних элементов, но не устанавливает желаемый размер своих дочерних элементов (другими словами, размер дочерних элементов является входным для передачи measure панели). Что касается передачи Arrange, то она используется для определения прямоугольника, в который будет окончательно выведен элемент пользовательского интерфейса, независимо от измеренного размера. В случае Panel, она вызывает передачу Arrange и для своих дочерних элементов, но, как и передача measure, она не изменит желаемый размер дочерних элементов (она просто определит их пространство рендеринга).

Для достижения требуемого поведения:

  • Правильно разделите логику между передачами Measure и Arrange (в вашем коде вся логика находится в передаче Arrange, а код, используемый для определения требуемого пространства для каждого дочернего элемента, должен быть помещен в передачу Measure)
  • Используйте правильное AttachedProperty (i. например, RequiredHeight) вместо желаемого размера дочерних элементов (у вас нет контроля над размером дочернего элемента, если он не установлен в Auto, поэтому нет необходимости брать DesiredSize)

Поскольку я не уверен, что понял назначение панели, я написал пример:

a. Создайте новое Wpf решение (WpfApplication1) и добавьте новый файл класса (CustomPanel.cs*)

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. Откройте файл проекта 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>
8
ответ дан 4 December 2019 в 21:08
поделиться

Я попытался немного упростить ваш код:

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 - он может быть положительной бесконечностью, что вызовет исключение. А поскольку вам в принципе все равно, какой размер, просто верните new Size(0,0).
  • Что касается вашей проблемы с высотой дочерних элементов - я думаю, что это связано с самими дочерними элементами - они как-то ограничены в размере через Style или свойства в отношении HorizontalAlignment?

EDIT: версия 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;
    }

}
2
ответ дан 4 December 2019 в 21:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: