通过基本的WPF教新手?(我不要嘲笑它.)

Tom*_*m W 12 wpf

问题是听起来的.无论我多么努力,多久我都会尝试理解WPF,我觉得我正撞在墙上.我喜欢Winforms,一切都很有意义.

作为一个例子,我正在尝试编写一个简单的应用程序,它允许我布置一堆二维路径(由折线表示)并拖动它们的顶点,并使顶点信息与演示者同步(即,我想,一个ViewModel)

因此问题是:

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

IDE在这里完全没有给我任何帮助.XAML中可用的许多属性对我来说都没有意义.因为似乎隐含地做了很多,所以没有明显的地方可以告诉窗户该做什么.

在我定期并告知RTFM之前,我想再次强调我已经多次研究过WPF的基本概念.我对它的了解不比我第一次发布时所做的更多.它似乎完全无法穿透.给出一种行为类型的示例并不以任何方式适用于稍微不同的行为,因此您将回到原点.我希望重复和有针对性的例子可能会在某些时候打开我的脑海.

Ric*_*key 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>
Run Code Online (Sandbox Code Playgroud)

以下是支持类:

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();
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

此解决方案使用行为,这是实现与MVVM交互的理想选择.

如果您不熟悉行为,请安装Expression Blend 4 SDK并添加以下命名空间:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Run Code Online (Sandbox Code Playgroud)

并添加System.Windows.Interactivity到您的项目.

  • 谢谢,这是一个非常详细的例子.您花时间发布它非常感谢.我已经对**vorrtex**说了同样的话,因为它们都非常有帮助. (2认同)

vor*_*olf 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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该类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; }
}
Run Code Online (Sandbox Code Playgroud)

它引用了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; }
}
Run Code Online (Sandbox Code Playgroud)

它包含点和线的样本数据

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>
Run Code Online (Sandbox Code Playgroud)

这里有很多技巧.首先,这些ItemsControls不是基于垂直StackPanel,而是基于Canvas.在ItemsControl同一个目标上放置必要物品的坐标点施加一个特殊的容器模板.但是ItemsControl线条不需要这样的模板,在某些方面它很奇怪.最后两个DataTemplates是显而易见的.

PointView.xaml

<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/>
Run Code Online (Sandbox Code Playgroud)

左边和顶部边距等于的一半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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,您的示例使用2个xaml文件和3个视图模型完成.