Пользовательский класс WPF Panel для автоматического определения размера

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

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

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

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

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

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

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

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

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

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

Решено !! Я получил это работает. Как я и подозревал, мне нужно было вызывать Measure () дважды для каждого ребенка (один раз, чтобы оценить DesiredSize, и второй, чтобы дать каждому ребенку его правильный рост). Мне кажется странным, что макет в WPF был бы спроектирован таким странным образом, когда он разделен на два прохода, но проход Измерения фактически делает две вещи: мерыа также Размеры детей и проход Arrange почти ничего не делают, кроме физического расположения детей. Очень странно.

Выложу рабочий код внизу.

Во-первых, оригинальный (неработающий) код:

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 три раза), но это работает. Любые оптимизации алгоритма приветствуются.

Ответы на вопрос(0)

Ваш ответ на вопрос