Canvas with Lines and Points

2019-03-06 09:15发布

问题:

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

回答1:

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>