Ответ 1
Я сочувствую вам. На самом деле понимание WPF занимает много времени, и может быть очень сложно выполнить самые простые вещи. Но погружение в проблему, которая нелегкая для экспертов, только требует неприятностей. Вам нужно решить более простые задачи и прочитать много кода, пока все не начнет иметь смысл. Дональд Кнут говорит, что вы действительно не знаете материал, пока не выполняете упражнения.
Я решил вашу проблему, и я признаю, что существует много предварительных концепций, когда это делается чисто и наложения на MVVM делает это намного сложнее. Для чего это стоит, вот нулевое решение для решения вашей проблемы, которое находится в духе MVVM.
Вот XAML:
<Grid>
<Grid.Resources>
<local:PolylineCollection x:Key="sampleData">
<local:Polyline>
<local:Coordinate X="50" Y="50"/>
<local:Coordinate X="100" Y="100"/>
<local:Coordinate X="50" Y="150"/>
</local:Polyline>
</local:PolylineCollection>
</Grid.Resources>
<Grid DataContext="{StaticResource sampleData}">
<ItemsControl ItemsSource="{Binding Segments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="Black" StrokeThickness="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding ControlPoints}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Margin="-10,-10,0,0" Width="20" Height="20" Stroke="DarkBlue" Fill="Transparent">
<i:Interaction.Behaviors>
<local:ControlPointBehavior/>
</i:Interaction.Behaviors>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
и вот вспомогательные классы:
public class Coordinate : INotifyPropertyChanged
{
private double x;
private double y;
public double X
{
get { return x; }
set { x = value; OnPropertyChanged("X", "Point"); }
}
public double Y
{
get { return y; }
set { y = value; OnPropertyChanged("Y", "Point"); }
}
public Point Point
{
get { return new Point(x, y); }
set { x = value.X; y = value.Y; OnPropertyChanged("X", "Y", "Point"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propertyNames)
{
foreach (var propertyName in propertyNames)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Polyline : List<Coordinate>
{
}
public class Segment
{
public Coordinate Start { get; set; }
public Coordinate End { get; set; }
}
public class PolylineCollection : List<Polyline>
{
public IEnumerable<Segment> Segments
{
get
{
foreach (var polyline in this)
{
var last = polyline.FirstOrDefault();
foreach (var coordinate in polyline.Skip(1))
{
yield return new Segment { Start = last, End = coordinate };
last = coordinate;
}
}
}
}
public IEnumerable<Coordinate> ControlPoints
{
get
{
foreach (var polyline in this)
{
foreach (var coordinate in polyline)
yield return coordinate;
}
}
}
}
public class ControlPointBehavior : Behavior<FrameworkElement>
{
private bool mouseDown;
private Vector delta;
protected override void OnAttached()
{
var canvas = AssociatedObject.Parent as Canvas;
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
var mousePosition = e.GetPosition(canvas);
var elementPosition = (AssociatedObject.DataContext as Coordinate).Point;
delta = elementPosition - mousePosition;
AssociatedObject.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var mousePosition = e.GetPosition(canvas);
var elementPosition = mousePosition + delta;
(AssociatedObject.DataContext as Coordinate).Point = elementPosition;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.ReleaseMouseCapture();
};
}
}
В этом решении используются поведения, которые идеально подходят для реализации интерактивности с MVVM.
Если вы не знакомы с поведением, установите пакет Expression Blend 4 SDK и добавьте эти пространства имен:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
и добавьте System.Windows.Interactivity
к вашему проекту.