Я пытаюсь написать собственный класс 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 был бы спроектирован таким странным образом, когда он разделен на два прохода, но проход Measure фактически делает две вещи: измеряет дочерние элементы и размеров, и проход 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
три раза), но это работает. Любые оптимизации алгоритма приветствуются.