是否可以使用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.X
和Canvas.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);
}
}
}
}