2011-01-22 22 views
12

Câu hỏi là như nó âm thanh. Không có vấn đề làm thế nào cứng và thường xuyên như thế nào tôi cố gắng hiểu WPF, tôi cảm thấy như tôi đang đánh đầu của tôi chống lại một bức tường. Tôi thích Winforms, nơi mọi thứ đều có ý nghĩa. Ví dụ, tôi đang cố gắng viết một ứng dụng đơn giản cho phép tôi bố trí một loạt các đường dẫn 2 chiều (được biểu diễn bằng các đường polylines) và kéo các đỉnh của chúng xung quanh, và có thông tin đỉnh được đồng bộ với một người dẫn chương trình (có nghĩa là, tôi cho rằng, một ViewModel)Huấn luyện viên newbie thông qua WPF cơ bản? (Tôi không Grok Nó.)

vì vậy, vấn đề là như sau:

  • làm cho cửa sổ nhận ra một thể hiện của IExtendedObjectPresenter như một nguồn dữ liệu;
  • Từ bộ sưu tập IExtendedObject, vẽ một polyline cho mỗi IExtendedObject;
  • Đối với mỗi đỉnh trong đối tượng mở rộng, được biểu thị bằng bộ sưu tập IExtendedObject.Points, đặt một đỉnh đa giác tại các tọa độ được chỉ định.

IDE không giúp tôi chút nào ở đây. Không ai trong số nhiều thuộc tính có sẵn trong âm thanh XAML có ý nghĩa đối với tôi. Bởi vì có vẻ như được thực hiện hoàn toàn, không có nơi nào rõ ràng để chỉ nói cho cửa sổ biết phải làm gì.

Trước khi tôi được đề nghị và nói với RTFM, tôi muốn nhấn mạnh lại rằng tôi đã nghiên cứu các khái niệm cơ bản của WPF rất nhiều lần. Tôi không biết gì nhiều hơn tôi khi nó được phát hành lần đầu tiên. Nó có vẻ hoàn toàn không thể hiểu được. Các ví dụ được đưa ra cho một loại hành vi không theo bất kỳ cách nào áp dụng cho một loại hành vi thậm chí hơi khác nhau, vì vậy bạn quay trở lại hình vuông. Tôi hy vọng rằng sự lặp lại và mục tiêu exampes có thể chuyển một ánh sáng trên trong đầu của tôi tại một số điểm.

+0

@Tom Tôi khuyên bạn nên thử một số thứ trong mã thay vì trong Xaml nếu Xaml đang thúc đẩy bạn hạt. –

+0

@Tom Tôi khuyên bạn nên bắt đầu với điều khiển 'Canvas' và chơi với các yếu tố bổ sung có lập trình cho nó và thay đổi vị trí của chúng. Bạn có thể có được một cảm giác để làm việc với đường dẫn, nhưng không có thêm đầu nồi hơi của Xaml để tranh với cùng một lúc. –

+0

Bất kỳ phiếu bầu nào để đóng, vui lòng giải thích lý do. Đó là một câu hỏi hợp lệ. Cảm ơn bạn. –

Trả lời

26

Tôi thông cảm với bạn. Thực sự hiểu WPF mất một thời gian dài và nó có thể rất bực bội để thực hiện những điều đơn giản nhất. Nhưng lặn vào một vấn đề mà không phải là dễ dàng cho các chuyên gia chỉ yêu cầu cho sự cố. Bạn cần phải giải quyết các nhiệm vụ đơn giản và đọc rất nhiều mã cho đến khi mọi thứ bắt đầu có ý nghĩa. Donald Knuth nói rằng bạn không thực sự biết tài liệu cho đến khi bạn tập công.

Tôi đã giải quyết được vấn đề của bạn và tôi thừa nhận có rất nhiều khái niệm trước khi thực hiện việc này một cách rõ ràng và gọn gàng trên MVVM khiến nó khó hơn nhiều. Đối với những gì nó có giá trị, đây là một giải pháp không mã phía sau cho vấn đề của bạn đó là tinh thần của MVVM.

Đây là 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> 

và đây là các lớp hỗ trợ:

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

giải pháp này sử dụng các hành vi, đó là lý tưởng cho việc triển khai thực hiện tương tác với MVVM.

Nếu bạn không quen thuộc với các hành vi, Cài đặt Expression Blend 4 SDK và thêm namespace này:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 

và thêm System.Windows.Interactivity để dự án của bạn.

+2

Cảm ơn bạn, đây là một ví dụ tuyệt vời và rất chi tiết. Việc bạn dành thời gian để đăng nó được rất nhiều đánh giá cao. Tôi đã nói như vậy với ** vorrtex ** vì cả hai đều rất hữu ích. –

+1

@Rick Tôi ước tôi có thể upvote hai lần !! Cảm ơn bạn đã dành thời gian để giải thích khái niệm này và cung cấp một câu trả lời chi tiết. Noooooow tôi hiểu rồi. – dFlat

+0

Rất tiếc. Trong khi tôi đồng ý rằng đây không phải là một câu hỏi phù hợp cho StackOverflow vì nó là tắt chủ đề> quá rộng Tôi thực sự phải thừa nhận rằng câu trả lời của bạn kéo nó xung quanh. Công cụ tuyệt vời, người đàn ông. Cảm ơn bạn. Làm việc ra khỏi hộp và hữu ích. – anhoppe

13

Tôi sẽ chỉ cách tạo ứng dụng WPF với mẫu MVVM cho 2D-Poliline với các đỉnh có thể kéo được.

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

Lớp ViewModelBase chỉ chứa một thực hiện của giao diện INotifyPropertyChanged. Điều này là cần thiết để phản ánh những thay đổi của thuộc tính clr trên biểu diễn trực quan.

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

Nó có tham chiếu đến các điểm, vì vậy những thay đổi sẽ được nhận tự động.

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

Nó chứa một dữ liệu mẫu các điểm và đường

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> 

Dưới đây là rất nhiều thủ đoạn. Trước hết, các số ItemsControls này không dựa trên dọc StackPanel, nhưng trên Canvas.Các ItemsControl của các điểm áp dụng một mẫu container đặc biệt với một mục tiêu để đặt các mục trên tọa độ cần thiết. Nhưng ItemsControl của dòng không yêu cầu các mẫu như vậy, và nó là lạ tại một số điểm. Hai DataTemplates cuối cùng là hiển nhiên.

PointView.xaml

<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/> 

Left và Top lề đều bình đẳng với một nửa của WidthHeight. Chúng tôi có một số Fill trong suốt vì thuộc tính này không có giá trị mặc định và các sự kiện của chuột không hoạt động.

Điều đó gần như tất cả. Chỉ có chức năng kéo-thả-thả.

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

Vì vậy, mẫu của bạn được thực hiện sử dụng 2 file XAML và 3 viewmodels.

+0

Cảm ơn bạn, đây là một ví dụ tuyệt vời và rất chi tiết. Việc bạn dành thời gian để đăng nó được rất nhiều đánh giá cao. Tôi đã nói như vậy với ** Rick Sladkey ** vì cả hai đều rất hữu ích. –

Các vấn đề liên quan