I am migrating an old application and am trying to implement MVVM. The old application has a similar interface to the one described in this post. Although it is slightly simpler. I am not too worried about following MVVM exactly, but I would like to use this as a chance to practice.
I have the following class:
public class LineViewModel
{
public ObservableCollection<LinePoint> Points {get;}
public Geometry LineGeometry {get;}
}
In my application view model I have a collection of lines and I would like to draw these on a Canvas, but I would also like to draw the points that make up each line.
Here is my XAML so far:
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:LineViewModel}">
<Path StrokeThickness="2.0" Stroke="Black" Data="{Binding LineGeometry}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:LinePoint}">
<Ellipse StrokeThickness="1.0" Stroke="Black" Fill="MistyRose" Width="8.0" Height="8.0" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I can see the line being drawn and this seems to be working fine. As you can see I have added the template for the points but I do not know how to setup the XAML to draw these. How can I draw the points as well?
Pulling out the Points from the line and exposing these from the application view model seems counter intuitive since it is the LineViewModel that looks after the points in a line - not the application. I would rather not disassociate the points from the lines if I can help it.
Thanks
I'd suggest to create a view model with a single property for the points of a line
class LineViewModel
{
public PointCollection LinePoints { get; set; }
}
class ViewModel
{
public IEnumerable<LineViewModel> Lines { get; set; }
}
Then you could have a nested ItemsControl in the DataTemplate of your "outer" ItemsControl like shown below, which uses a Polyline
to draw the line and a collection of Path
elements with EllipseGeometries
for the circles.
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Polyline Points="{Binding LinePoints}"
StrokeThickness="2.0" Stroke="Black"/>
<ItemsControl ItemsSource="{Binding LinePoints}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path StrokeThickness="1.0" Stroke="Black" Fill="MistyRose">
<Path.Data>
<EllipseGeometry Center="{Binding}"
RadiusX="4" RadiusY="4"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In case you have to use your LineViewModel as is, you may write the ItemTemplate like shown below, with a binding converter that converts your LinePoint
to Point
for the Center
of the EllipseGeometry.
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Path StrokeThickness="2.0" Stroke="Black" Data="{Binding LineGeometry}" />
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path StrokeThickness="1.0" Stroke="Black" Fill="MistyRose">
<Path.Data>
<EllipseGeometry
Center="{Binding
Converter={StaticResource LinePointConverter}}"
RadiusX="4" RadiusY="4"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>