HI! I want to design view which will contain multiple objects in different locations. For example - it would be great if viewmodel could have field like list of objects (rectangles) and when i change/add members to list, new rectangles appear on view in specified positions. How do i create such view/viewmodel?
You could have an ICollectionView
or ObservableCollection<T>
property in your ViewModel and bind the ItemsSource
property of an ItemsControl
to this property. Then this will display all the items in your collection(view). However, it will typically display them in a StackPanel
as this is the default item container for an ItemsControl
. As far as I understood your question, you want the items to be placed anywhere on your screen. This could be done by using a Canvas
as the ItemsControl
's ItemsPanel
, and then binding the Canvas.Left
and Canvas.Top
properties to properties in your ViewModels. Of course, every item would need a Left
and Top
property then (and maybe also a Width
and Height
property).
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
}
And your 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>
In a final step, you might want the Left
and Top
properties to be relative to the size of the Canvas
, so that the items remain at the relative positions if the size of the Canvas
changes. This is some more work:
<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>
The description of the code is already in the XAML comments. However, I should note that I have used the ExpressionConverter
from Kent Boogart's Converter collection. I copied and pasted the above code from one of my applications, so there might be some inconsistencies in there because of quickly adjusting the properties to your scenario. However, the principle should be clear, I think. Good luck!