Как выровнять элементы WPF в Horizontal WrapPanel, чтобы они выстраивались на основе произвольной вертикальной точки в каждом элементе?

Я пытаюсь создать представление в WPF и с трудом понимаю, как его настроить. Вот что я пытаюсь построить:

  • Моя ViewModel предоставляет свойство IEnumerable, называемое Items
  • Каждый элемент представляет собой событие на временной шкале, и каждый из них реализует ITimelineItem.
  • ViewModel для каждого элемента имеет собственный DataTemplate для его отображения.
  • Я хочу отобразить все элементы на временной шкале, соединенные линией. Я думаю, что для этого хорошо подойдет WrapPanel внутри ListView.
  • Однако высота каждого элемента будет варьироваться в зависимости от отображаемой информации. Некоторые элементы будут иметь графические объекты прямо на линии (например, круг, ромб или что-то еще), а некоторые имеют аннотации над или под линией.

Так что мне это кажется сложным. Кажется, что каждый элемент на временной шкале должен отображать свой собственный сегмент линии. Хорошо. Но расстояние между верхней частью элемента и линией (и нижней частью элемента в строке) может варьироваться. Если один элемент имеет линию на 50 пикселей вниз от верха, а следующий элемент имеет линию на 100 пикселей вниз от верха, то первому элементу требуется отступ в 50 пикселей, чтобы сегменты линий складывались.

Я думаю, что мог бы решить эту проблему, однако нам нужно добавить отступ только в том случае, если эти два элемента находятся в одной строке в WrapPanel! Допустим, есть 5 элементов, а на экране есть место только для 3-х... WrapPanel поместит два других на следующую строку. Это нормально, но это означает, что только первые 3 должны сочетаться друг с другом, а последние 2 должны сочетаться друг с другом.

Вот от чего у меня болит голова. Есть ли другой подход, на который я мог бы посмотреть?

EDIT: я склоняюсь к замене WrapPanel пользовательской панелью, которая наследуется от Canvas и переопределяет методы MeasureOverride и ArrangeOverride.


person Scott Whitlock    schedule 22.05.2010    source источник


Ответы (2)


Идеальным решением для этого, вероятно, было бы использование Adorners. На AdornerLayer у вас будет собственный Adorner для каждого элемента на панели (вы определяете его в DataTemplate). Вы сможете восстановить размеры и положение каждого элемента (ограничивающая рамка украшения). Также на AdornerLayer вы затем нарисуете линии между этими ограничительными прямоугольниками. Используя это решение, вы можете размещать свои элементы так, как хотите (используя любую панель), и элементы по-прежнему будут связаны линиями.

Давным-давно я использовал этот подход для визуализатора графиков. Узлы с произвольными элементами UIElement, которые можно компоновать любым способом. Элементы, соединенные линиями.

Чтобы все элементы идеально выровнялись, вы можете использовать Canvas в качестве корневой панели в своем ItemTemplate. Canvas может быть размером 0x0 единиц. Затем вы должны расположить свои элементы вокруг этого холста. Canvas становится вашей точкой отсчета. Все, что находится в верхней части строки, получит отрицательные значения Canvas.Top. Другой подход заключается в использовании отрицательных полей, привязанных к высоте верхнего и нижнего элементов управления, окружающих строку. Используйте IValueConverter (Height*-1), чтобы инвертировать высоту.

person bitbonk    schedule 22.05.2010
comment
Это определенно полезное решение для рисования линий. Спасибо! Но я все еще ищу способ расположить предметы так, чтобы все они выстроились там, где должны. Все линии должны быть горизонтальными и прямыми, так что это элементы, которые должны двигаться, чтобы компенсировать это. - person Scott Whitlock; 23.05.2010
comment
Adorners также решают мои визуальные индикаторы перетаскивания! Спасибо, что научили меня чему-то новому! - person Scott Whitlock; 23.05.2010
comment
Я обновил свой пост с предложением решить вашу проблему с альгинтом. - person bitbonk; 23.05.2010

Это не решит вашу проблему, но я уверен, что это упростит ее:

Попробуйте определить DataTemplate, который выглядит примерно так:

<Grid>
   <Grid.RowDefinitions>
      <RowDefinition Height="Auto" SharedSizeGroup="Top"/>
      <RowDefinition Height="10"/>
      <RowDefinition Height="Auto" SharedSizeGroup="Bottom"/>
   </Grid.RowDefinitions>
   <ContentControl Grid.Row="0" Content="{Binding TopAnnotations}/>
   <Rectangle Fill="Black" Grid.Row="1" Margin="0,4,0,4"/>
   <ContentControl Grid.Row="1" Height="10" Content="{Binding GraphicObject}"/>
   <ContentControl Grid.Row="2" Content="{Binding BottomAnnotations}"/>
</Grid>  

Теперь создайте ItemsControl, ItemsPanelTemplate которого является горизонтальным WrapPanel с прикрепленным свойством Grid.IsSharedSizeScope, установленным на True. Конечно, вам также потребуется определить шаблоны данных для всех свойств аннотаций и графических объектов.

У вас все еще есть проблема, заключающаяся в том, что когда элементы в WrapPanel переносятся, объекты выше и ниже временной шкалы имеют одинаковые отступы независимо от того, сколько им на самом деле нужно, чтобы поместиться в их текущей строке WrapPanel. Таким образом, чтобы получить эффект, который, я думаю, вы ищете, вам все равно придется возиться с мерой и расположением - в основном, вместо создания одного WrapPanel, вы создадите что-то, что помещает эти элементы в StackPanel в каждой "строке". ", каждая из которых представляет собой собственную область действия общего размера. Но на самом деле вам не нужно беспокоиться об измерении и расположении высоты элементов временной шкалы. Вам нужна только их ширина (чтобы ваша логика могла определить, когда обернуть). Grid позаботится о расчете высоты для вас.

person Robert Rossney    schedule 22.05.2010
comment
Интересная идея... спасибо! Это немного ограничивает то, что вся графика должна быть одинакового размера (посередине), поэтому, если бы одна использовала графику в два раза больше, все аннотации на других в той же строке не были бы правильными. против графики. Что, если бы, например, у одного элемента была только большая графика и не было аннотаций? Как я уже сказал, это немного более ограничительно, чем я думал, но это интересный подход. - person Scott Whitlock; 23.05.2010
comment
На самом деле, вы можете настроить автоматический размер среднего столбца и сделать его принадлежащим своему собственному SharedSizeGroup. Это все еще будет работать; вам просто нужно дать Rectangle или что-то еще, что вы используете для создания строки VerticalAlignment из Center. Он будет центрирован по вертикали в строке, а размер строки будет равен высоте самого большого изображения, которое она содержит. - person Robert Rossney; 23.05.2010