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

时间:2013-07-09 20:25:25

标签: wpf silverlight canvas collections shape

是否可以使用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>

2 个答案:

答案 0 :(得分:1)

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

我描述了积分的机制,对于我认为你会自己理解的行。

主要控制

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

PointView控件

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

<强>结果

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

假设我理解你想要什么。

添加了问题的答案

Silverlight 5支持它。这意味着所有项目都将放置在Canvas控件上。 Some article about ItemsControl

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

PointView是第二个用户控件。

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

为了制作一条线,你必须连接你的点。它会变得更加困难所以我建议你只用点数制作一个变体。

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

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

很抱歉,但我不会写更多,因为它会成为一篇文章而不是答案)

P.S:这是一个有趣的问题,所以如果我有空闲时间,我可能会写一篇文章。关于模板化控件,您可以阅读here

答案 1 :(得分:1)

绝对令人厌恶的是这需要多少XAML。我将寻找一种使用样式和模板进行清理的方法。此外,我需要画线到点的中心,这应该不难。目前,以下是有效的。我最终创建了一个Collection<Pair<Point, Point>> ViewModel来绑定“Line”集合。否则我会逐点看线,因为找不到X2 / Y2而无法画线。

感谢Alexander的灵感。

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