WPF/Silverlight将画布绑定到视图模型元素的集合



是否可以使用DataTemplate将点集合渲染为一堆线(具有数据绑定和拖放功能)?

以下是详细信息:

我的视图模型中有多个对象。这些对象最终在画布上具有以绝对像素坐标指定的位置。我需要能够在画布上拖放这些项目,并更新它们的坐标。一些对象由一个点表示,另一些则是线段的集合。我正在使用MVVM(Jounce)。我的视图模型是否应该公开一个以某种方式绑定坐标的ObservableCollection<Shape>?这感觉不对。或者,有没有一种方法可以在这里使用DataTemplates在给定线段集合的每个线段的末端绘制带有点的线?下面是一个示例ViewModel:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using Jounce.Core.ViewModel;
namespace CanvasBindTest.ViewModels
{
    /// <summary>
    /// Sample view model showing design-time resolution of data
    /// </summary>
    [ExportAsViewModel(typeof(MainViewModel))]
    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            var start = new PointView { X = 0, Y = 0 };
            var middle = new PointView { X = 1132 / 2, Y = 747 / 2 };
            var end = new PointView() { X = 1132, Y = 747 };
            var lineView = new LineView(new[] { start, middle, end });
            Lines = new LinesView(new[] { lineView });
        }
        public LinesView Lines { get; private set; }
    }
    public class LinesView : BaseViewModel
    {
        public ObservableCollection<LineView> Lines { get; private set; }
        public LinesView(IEnumerable<LineView> lines)
        {
            Lines = new ObservableCollection<LineView>(lines);
        }
    }
    public class LineView : BaseViewModel
    {
        public ObservableCollection<PointView> Points { get; private set; }
        public LineView(IEnumerable<PointView> points)
        {
            Points = new ObservableCollection<PointView>(points);
        }
    }
    public class PointView : BaseViewModel
    {
        private int x, y;
        public int X
        {
            get { return x; }
            set { x = value; RaisePropertyChanged(() => X); }
        }
        public int Y { 
            get { return y; }
            set { y = value; RaisePropertyChanged(() => Y); }
        }
    }
}

这是View,它是一个包装在ItemsControl中的画布,带有背景图像。视图模型坐标相对于背景图像的未缩放大小:

<UserControl x:Class="CanvasBindTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModels="clr-namespace:CanvasBindTest.ViewModels" 
    mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <DataTemplate x:Key="SkylineTemplate" DataType="viewModels:LineView">
            <ItemsControl ItemsSource="{Binding Points}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <!--I have a collection of points here, how can I draw all the lines I need and keep the end-points of each line editable?-->
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </DataTemplate>
    </UserControl.Resources>
    <Grid d:DataContext="{d:DesignInstance viewModels:MainViewModel, IsDesignTimeCreatable=True}">
        <ScrollViewer x:Name="Scroll">
            <ItemsControl ItemsSource="{Binding Lines}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas>
                            <Canvas.Background>
                                <ImageBrush Stretch="Uniform" ImageSource="Properties/dv629047.jpg"/>
                            </Canvas.Background>
                        </Canvas>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</UserControl>

您的LineView必须是LineViewModel,这样会更正确。

我描述了点和线的机制,我想你会自己理解的。

主控制

  • 使用ItemsControl
  • ItemsControl.PanelControl必须是Canvas
  • ItemsSource-您收藏的PointWiewModel
  • 为类型PointWiewModel制作两个DataTemplate
  • 制作PointView控件并将其放入相应的DataTemplate中

PointView控件

  • 双向绑定Canvas.X附加属性到PointViewModel.X属性
  • 双向绑定Canvas.Y附加属性到PointViewModel.Y属性
  • 添加在拖动PointView控件时更改Canvas.XCanvas.Y的逻辑

结果

之后,您可以拖动(例如)PointVew控件,视图模型中的属性将因双向绑定而更新。

假设我正确理解你想要什么。

添加了问题的答案

Silverlight 5支持它。这意味着所有项目都将被放置在Canvas控件上。一些关于ItemsControl的文章。

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas></Canvas>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

CCD_ 18是第二个用户控件。

注意:我已经描述了一种使用MVVM绘制点数组的方法。您可以拖动画布上的每个点,并在视图模型中接收新坐标。(也许我的描述在这个阶段有点混乱,所以我已经从中删除了LineViews)

为了制作一条线,你必须连接你的点。这会更困难,所以我建议你只做一个有点的变体。

当您熟悉它时,您可以将ItemsControl移动到模板化控件中。制作自己的ItemSource集合,并在它们将更改位置时通过这些点绘制路径。

您还可以搜索一些开源图形控件,看看它们是如何用点绘制曲线的。事实上,他们通常像我描述的那样用Path来做这件事。

对不起,但我不会写更多,因为它会成为一篇文章,但不是一个答案)

这是一个有趣的问题,所以如果我有空的话,我可能会写一篇文章。关于模板控件,您可以在此处阅读。

这花了多少XAML真是太恶心了。我将寻找一种使用样式和模板来清理它的方法。另外,我需要把线画到点的中心,这应该不难。目前,以下是行之有效的方法。我最终创建了一个Collection<Pair<Point, Point>> ViewModel来绑定"Line"集合。否则,我会一点一点地看这条线,因为我找不到X2/Y2,所以无法画出一条线。

谢谢你的启发亚历山大。

这是XAML:

  <ItemsControl ItemsSource="{Binding Lines}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="viewModels:LineViewModel">
                <ItemsControl ItemsSource="{Binding LineSegments}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <Canvas />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <ItemsControl>
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <Canvas />
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl ItemsSource="{Binding Lines}">
                                    <ItemsControl.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <Canvas />
                                        </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Line X1="{Binding Item1.X}" X2="{Binding Item2.X}" Y1="{Binding Item1.Y}" Y2="{Binding Item2.Y}" Stroke="Black" StrokeThickness="2"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                                <ItemsControl ItemsSource="{Binding LineSegment}">
                                    <ItemsControl.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <Canvas />
                                        </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Ellipse Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="10" Height="10" Fill="Black">
                                                <Ellipse.RenderTransform>
                                                    <TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
                                                </Ellipse.RenderTransform>
                                            </Ellipse>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </ItemsControl>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

这是ViewModel:

namespace CanvasBindTest.ViewModels
{
    /// <summary>
    ///     Sample view model showing design-time resolution of data
    /// </summary>
    [ExportAsViewModel(typeof (MainViewModel))]
    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            var start = new PointViewModel {X = 0, Y = 0};
            var middle = new PointViewModel {X = 30, Y = 10};
            var end = new PointViewModel {X = 20, Y = 0};
            var simpleLine = new LineSegmentsViewModel(new[] {start, middle, end});
            Lines = new ObservableCollection<LineViewModel> {new LineViewModel(new[] {simpleLine})};
        }
        public ObservableCollection<LineViewModel> Lines { get; private set; }
    }
    public class LineViewModel : BaseViewModel
    {
        public LineViewModel(IEnumerable<LineSegmentsViewModel> lineSegments)
        {
            LineSegments = new ObservableCollection<LineSegmentsViewModel>(lineSegments);
        }
        public ObservableCollection<LineSegmentsViewModel> LineSegments { get; private set; }
    }
    public class LineSegmentsViewModel : BaseViewModel
    {
        public LineSegmentsViewModel(IEnumerable<PointViewModel> lineSegment)
        {
            LineSegment = new ObservableCollection<PointViewModel>(lineSegment);
            Lines = new Collection<Tuple<PointViewModel, PointViewModel>>();
            var tmp = lineSegment.ToArray();
            for (var i = 0; i < tmp.Length - 1; i++)
            {
                Lines.Add(new Tuple<PointViewModel, PointViewModel>(tmp[i], tmp[i+1]));
            }
        }
        public Collection<Tuple<PointViewModel, PointViewModel>> Lines { get; private set; }  
        public ObservableCollection<PointViewModel> LineSegment { get; private set; }
    }

    public class PointViewModel : BaseViewModel
    {
        private int x, y;
        public int X
        {
            get { return x; }
            set
            {
                x = value;
                RaisePropertyChanged(() => X);
            }
        }
        public int Y
        {
            get { return y; }
            set
            {
                y = value;
                RaisePropertyChanged(() => Y);
            }
        }
    }
}

最新更新