问题听起来如此。无论我多么努力,多久我都会尝试理解WPF,我觉得我正撞在墙上。我喜欢Winforms,一切都很有意义。
作为一个例子,我正在尝试编写一个简单的应用程序,它允许我布置一堆二维路径(由折线表示)并拖动它们的顶点,并使顶点信息与演示者同步(也就是说,我想,一个ViewModel)
因此问题是:
IExtendedObjectPresenter
的实例作为数据源; IExtendedObject
的集合中,为每个IExtendedObject
绘制一条折线; IExtendedObject.Points
表示,将折线顶点放置在指定的坐标处。IDE在这里完全没有给我任何帮助。 XAML中可用的很多属性对我来说都没有意义。因为似乎隐含地做了很多,所以没有明显的地方可以告诉窗户该做什么。
在我定期并告知RTFM之前,我想再次强调我已经多次研究过WPF的基本概念。我对它的了解不比我第一次发布时所做的更多。它似乎完全无法穿透。给出一种行为类型的示例并不以任何方式适用于稍微不同的行为,因此您将回到原点。我希望重复和有针对性的例子可能会在某些时候打开我的脑袋。
答案 0 :(得分:26)
我同情你。真正理解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
添加到您的项目中。
答案 1 :(得分:13)
我将展示如何使用具有可拖动顶点的2D-Poliline构建具有MVVM模式的WPF应用程序。
<强> PointViewModel.cs 强>
public class PointViewModel: ViewModelBase
{
public PointViewModel(double x, double y)
{
this.Point = new Point(x, y);
}
private Point point;
public Point Point
{
get { return point; }
set
{
point = value;
OnPropertyChanged("Point");
}
}
}
类ViewModelBase
仅包含接口INotifyPropertyChanged
的实现。这对于反映视觉表现的clr属性的变化是必要的。
<强> LineViewModel.cs 强>
public class LineViewModel
{
public LineViewModel(PointViewModel start, PointViewModel end)
{
this.StartPoint = start;
this.EndPoint = end;
}
public PointViewModel StartPoint { get; set; }
public PointViewModel EndPoint { get; set; }
}
它引用了Points,因此将自动接收更改。
<强> MainViewModel.cs 强>
public class MainViewModel
{
public MainViewModel()
{
this.Points = new List<PointViewModel>
{
new PointViewModel(30, 30),
new PointViewModel(60, 100),
new PointViewModel(50, 120)
};
this.Lines = this.Points.Zip(this.Points.Skip(1).Concat(this.Points.Take(1)),
(p1, p2) => new LineViewModel(p1, p2)).ToList();
}
public List<PointViewModel> Points { get; set; }
public List<LineViewModel> Lines { get; set; }
}
它包含点和线的样本数据
<强> MainVindow.xaml 强>
<Window.Resources>
<ItemsPanelTemplate x:Key="CanvasPanelTemplate">
<Canvas/>
</ItemsPanelTemplate>
<Style x:Key="PointListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Point.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Point.Y}"/>
</Style>
<DataTemplate x:Key="LineTemplate">
<Line X1="{Binding StartPoint.Point.X}" X2="{Binding EndPoint.Point.X}" Y1="{Binding StartPoint.Point.Y}" Y2="{Binding EndPoint.Point.Y}" Stroke="Blue"/>
</DataTemplate>
<DataTemplate x:Key="PointTemplate">
<view:PointView />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Lines}" ItemsPanel="{StaticResource CanvasPanelTemplate}" ItemTemplate="{StaticResource LineTemplate}"/>
<ItemsControl ItemsSource="{Binding Points}" ItemContainerStyle="{StaticResource PointListBoxItem}" ItemsPanel="{StaticResource CanvasPanelTemplate}"
ItemTemplate="{StaticResource PointTemplate}"/>
</Grid>
这里有很多技巧。首先,这些ItemsControls
不是基于纵向StackPanel
,而是基于Canvas
。 ItemsControl
点应用一个特殊的容器模板,目标是将项目放在必要的坐标上。但ItemsControl
行不需要这样的模板,而且在某些时候它很奇怪。最后两个DataTemplates是显而易见的。
<强> PointView.xaml 强>
<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/>
左边和上边距等于Width
和Height
的一半。我们有一个透明的Fill
,因为此属性没有默认值,并且鼠标事件不起作用。
这几乎都是。只有拖放功能仍然存在。
<强> PointView.xaml.cs 强>
public partial class PointView : UserControl
{
public PointView()
{
InitializeComponent();
this.MouseLeftButtonDown += DragSource_MouseLeftButtonDown;
this.MouseMove += DragSource_MouseMove;
}
private bool isDraggingStarted;
private void DragSource_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.isDraggingStarted = true;
}
private void DragSource_MouseMove(object sender, MouseEventArgs e)
{
if (isDraggingStarted == true)
{
var vm = this.DataContext as PointViewModel;
var oldPoint = vm.Point;
DataObject data = new DataObject("Point", this.DataContext);
DragDropEffects effects = DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
if (effects == DragDropEffects.None) //Drag cancelled
vm.Point = oldPoint;
this.isDraggingStarted = false;
}
}
<强> MainVindow.xaml.cs 强>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
this.AllowDrop = true;
this.DragOver += DropTarget_DragOver;
}
private void DropTarget_DragOver(object sender, DragEventArgs e)
{
var vm = e.Data.GetData("Point") as PointViewModel;
if (vm != null)
vm.Point = e.GetPosition(this);
}
}
所以你的样本是使用2个xaml文件和3个视图模型完成的。