I'd like to draw lines between multiple ellipses that are in a itemscontrol.
The endresult should look like this.
The problem is that all the circles are drawn on runtime. They're in a itemscontrol, and the code for it looks like this.
<ItemsControl x:Name="SpaceObjectsIC"
Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding SpaceObjects, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="LightGray" Width="800" Height="600" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{Binding Color, Converter={StaticResource ColorConverter}}"
Stroke="{Binding Border, Converter={StaticResource ColorConverter}}"
StrokeThickness="{Binding BorderThickness}"
Height="{Binding Diameter}" Width="{Binding Diameter}" Tag="{Binding Name}"
ClipToBounds="True">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}" />
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see I'm using a datatemplate which only can use one element. I've no idea how to explain in XAML that I'd like to draw lines between these specific ellipses.
Only the circles with so called 'neighbours' need lines. If you look at the xaml you can see that a list called SpaceObjects is bound. Every spaceobject with neighbours knows these neighbours (which are also spaceobjects). This means that finding the x and y position of the circles is easy. I just don't know how to dynamically draw these lines between them without breaking MVVM rules, and I also don't want to use code behind.
The datacontext of the xaml is a clise named SpaceViewModel which contains a ObservableCollection with the spaceobjects.
The SpaceObject code looks like this (I've left out the properties, but just know that they do exist):
public class SpaceObject : ISpaceObject, INotifyPropertyChanged
{
private decimal _x;
private decimal _y;
private List<SpaceObject> _spaceNeighbours = new List<SpaceObject>();
}
So given that I've got all the data that I need, how do I make Xaml do what I want to do?
Remove , UpdateSourceTrigger=PropertyChanged from your itemssource binding.
Make SpaceObjects an ObservableCollection
Move your current Datatemplate to resources of the itemscontrol. Associate that with your current viewmodel type using Datatemplate DataType="{x:Type YourPlanetVM}"
Define a new viewmodel with properties specify line start X and Y and end X and Y.
Calculate x and y start and end and set these on those viewmodels. Add these to the observablecollection first because that influences z-index. You want your lines under the planets, presumably.
Position planets using ItemContainerStyle
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding X}"/>
<Setter Property="Canvas.Left" Value="{Binding Y}"/>
Your LineVM will need those set to 0 It will also need properties to bind to those of a Line:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.shapes.line?view=netcore-3.1
You will want a datatemplate for linevm has those start and end x,y and stroke ( the brush ) and strokethickness.
I suggest using an ellipsegeometry for your path. This centres around the X,Y co-ordinates rather than positioning top left. A datatemplate for something like this ( but with some hard coded values ) looks like:
<Path
Height="2"
Width="2"
Fill="Black">
<Path.Data>
<EllipseGeometry
RadiusX="1"
RadiusY="1"
/>
</Path.Data>
</Path>