Вы можете иметь свойство ICollectionView
или ObservableCollection<T>
в вашей ViewModel и привязать свойство ItemsSource
ItemsControl
к этому свойству. Затем отобразятся все элементы вашей коллекции (просмотр). Тем не менее, он обычно отображает их в StackPanel
, так как это контейнер по умолчанию для ItemsControl
. Насколько я понял ваш вопрос, вы хотите, чтобы элементы были размещены в любом месте на вашем экране. Это можно сделать, используя Canvas
в качестве ItemsPanel
*1009*, а затем связав свойства Canvas.Left
и Canvas.Top
со свойствами в ваших ViewModels. Разумеется, тогда каждому предмету потребуется свойство Left
и Top
(и, возможно, также свойство Width
и Height
).
public class ItemViewModel
{
public double Left { get; set; }
public double Top { get; set; }
public double Width { get; set; }
public double Height { get; set; }
// whatever you need...
}
public class CollectionViewModel
{
public ObservableCollection<ItemViewModel> Collection { get; }
// some code which fills the Collection with items
}
И ваш XAML:
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<Rectangle Width="{Binding Width}" Height="{Binding Height}"
Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
На последнем шаге вы можете захотеть, чтобы свойства Left
и Top
были относительно размера Canvas
, чтобы элементы оставались в относительных позициях, если размер Canvas
изменяется , Это еще работа:
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<Rectangle Width="{Binding Width}" Height="{Binding Height}">
<!-- Make the left position of the item depend on the ActualWidth of the Canvas,
the relative Left position (between 0 and 1) from the ItemViewModel, and the ActualWidth
of the item itself. This is needed because the Canvas.Left property defines the
position of the left side, not the center. Therefore, we calculate the position of
the center this way:
(Canvas.ActualWidth * ItemViewModel.Left) - (Item.ActualWidth / 2)
-->
<Canvas.Left>
<MultiBinding>
<MultiBinding.Converter>
<converters:ExpressionConverter Expression="{}({0} * {1}) - ({2} / 2)"/>
</MultiBinding.Converter>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Canvas}}"/>
<Binding Path="Left"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Canvas.Left>
<!-- the top position of the items is determined the same way as the left position
which is described above -->
<Canvas.Top>
<MultiBinding>
<MultiBinding.Converter>
<converters:ExpressionConverter Expression="{}({0} * {1}) - ({2} / 2)"/>
</MultiBinding.Converter>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Canvas}}"/>
<Binding Path="Top"/>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Canvas.Top>
</Rectangle>
</DataTemplate>
Описание кода уже есть в комментариях к XAML. Тем не менее, я должен отметить, что я использовал ExpressionConverter
из коллекции конвертера Кента Бугарта . Я скопировал и вставил приведенный выше код из одного из моих приложений, поэтому там могут быть некоторые несоответствия из-за быстрой настройки свойств в соответствии с вашим сценарием. Однако принцип должен быть ясен, я думаю. Удачи!