2011-01-22 54 views
12

问题就像听起来一样。无论我多么努力地尝试理解WPF,我都觉得我正在撞墙。我喜欢Winforms,一切都很合理。通过基本的WPF教新手吗? (我不喜欢它。)

作为一个例子,我试图编写一个简单的应用程序,使我能够布置一堆2-D路径(用多段线表示)并拖动它们的顶点并使顶点信息与演示者(即,我想,一个ViewModel)

所以,问题是这样的:

  • 使该窗口识别的IExtendedObjectPresenter作为数据源的实例;
  • IExtendedObject的集合中,为每个IExtendedObject绘制一条折线;
  • 对于扩展对象中的每个顶点(由集合IExtendedObject.Points表示),在指定的坐标处放置一个折线顶点。

IDE在这里根本没有任何帮助。 XAML中提供的许多属性对我来说都没有意义。因为这么多似乎是隐含的,所以没有明显的地方告诉窗口要做什么。

在我被提到并告诉RTFM之前,我想再次强调我已经多次研究了WPF的基本概念。我对它的了解比我在第一次发布时所做的更多。它似乎完全无法通过。为某种行为提供的例子并不适用于甚至稍有不同的行为,所以你回到了原点。我希望重复和有针对性的检查可能会在某种程度上让我头脑发热。

+0

@Tom我会建议在代码中尝试一些事情,而不是在Xaml中,如果Xaml驱使你疯了。 – 2011-01-22 13:29:57

+0

@Tom我会建议从一个`Canvas`控件开始,并以编程方式向它添加元素并改变它们的位置。你可以感受到与路径一起工作的感觉,但是如果没有Xaml的头部锅炉同时进行对抗的话。 – 2011-01-22 13:42:49

+0

无论谁投票结束,请解释原因。这是一个有效的问题。谢谢。 – 2011-01-22 13:59:35

回答

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到您的项目。

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; } 
} 

它具有点引用,所以更改将自动接收。

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"/> 

Left和Top利润率等于WidthHeight的一半。我们有一个透明的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周的ViewModels完成。