我有点惊讶的是,无法通过XAML为Canvas.Children设置绑定。我不得不采用类似于此的代码隐藏方法:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DesignerViewModel dvm = this.DataContext as DesignerViewModel;
dvm.Document.Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
foreach (UIElement element in dvm.Document.Items)
designerCanvas.Children.Add(element);
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<UIElement> collection = sender as ObservableCollection<UIElement>;
foreach (UIElement element in collection)
if (!designerCanvas.Children.Contains(element))
designerCanvas.Children.Add(element);
List<UIElement> removeList = new List<UIElement>();
foreach (UIElement element in designerCanvas.Children)
if (!collection.Contains(element))
removeList.Add(element);
foreach (UIElement element in removeList)
designerCanvas.Children.Remove(element);
}
我更愿意在XAML中设置一个像这样的绑定:
<Canvas x:Name="designerCanvas"
Children="{Binding Document.Items}"
Width="{Binding Document.Width}"
Height="{Binding Document.Height}">
</Canvas>
有没有办法在不采用代码隐藏方法的情况下实现这一目标?我已经完成了一些关于这个主题的谷歌搜索,但是对于这个特定的问题并没有提出太多的信息。
我不喜欢我当前的方法,因为它通过让View知道它的ViewModel来破坏我的漂亮Model-View-ViewModel。
答案 0 :(得分:140)
<ItemsControl ItemsSource="{Binding Path=Circles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White" Width="500" Height="500" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{Binding Path=Color, Converter={StaticResource colorBrushConverter}}" Width="25" Height="25" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
答案 1 :(得分:25)
其他人已经就如何做你真正想做的事做了可扩展的回复。我将解释为什么你不能直接绑定Children
。
问题非常简单 - 数据绑定目标不能是只读属性,Panel.Children
是只读的。那里的藏品没有特殊处理。相比之下,ItemsControl.ItemsSource
是一个读/写属性,即使它是集合类型 - 对于.NET类来说很少见,但需要这样才能支持绑定方案。
答案 2 :(得分:19)
ItemsControl
用于从其他集合创建动态UI控件集合,甚至是非UI数据集合。
您可以将ItemsControl
模板化为Canvas
。理想的方法是将支持面板设置为Canvas
,然后在直接子项上设置Canvas.Left
和Canvas.Top
属性。我无法使用它,因为ItemsControl
用容器包装它的子容器,并且很难在这些容器上设置Canvas
属性。
相反,我使用Grid
作为所有项目的bin,并在每个项目Canvas
上绘制它们。这种方法有一些开销。
<ItemsControl x:Name="Collection" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyPoint}">
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Ellipse Width="10" Height="10" Fill="Black" Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
以下是我用来设置源集合的代码:
List<MyPoint> points = new List<MyPoint>();
points.Add(new MyPoint(2, 100));
points.Add(new MyPoint(50, 20));
points.Add(new MyPoint(200, 200));
points.Add(new MyPoint(300, 370));
Collection.ItemsSource = points;
MyPoint
是一个自定义类,其行为与System
版本相同。我创建它是为了证明您可以使用自己的自定义类。
最后一个细节:您可以将ItemsSource属性绑定到您想要的任何集合。例如:
<ItemsControls ItemsSource="{Binding Document.Items}"><!--etc, etc...-->
有关ItemsControl及其工作原理的更多详细信息,请查看以下文档:MSDN Library Reference; Data Templating; Dr WPF's series on ItemsControl
答案 3 :(得分:12)
internal static class CanvasAssistant
{
#region Dependency Properties
public static readonly DependencyProperty BoundChildrenProperty =
DependencyProperty.RegisterAttached("BoundChildren", typeof (object), typeof (CanvasAssistant),
new FrameworkPropertyMetadata(null, onBoundChildrenChanged));
#endregion
public static void SetBoundChildren(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(BoundChildrenProperty, value);
}
private static void onBoundChildrenChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if (dependencyObject == null)
{
return;
}
var canvas = dependencyObject as Canvas;
if (canvas == null) return;
var objects = (ObservableCollection<UIElement>) e.NewValue;
if (objects == null)
{
canvas.Children.Clear();
return;
}
//TODO: Create Method for that.
objects.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
foreach (object item in args.NewItems)
{
canvas.Children.Add((UIElement) item);
}
if (args.Action == NotifyCollectionChangedAction.Remove)
foreach (object item in args.OldItems)
{
canvas.Children.Remove((UIElement) item);
}
};
foreach (UIElement item in objects)
{
canvas.Children.Add(item);
}
}
}
使用:
<Canvas x:Name="PART_SomeCanvas"
Controls:CanvasAssistant.BoundChildren="{TemplateBinding SomeItems}"/>
答案 4 :(得分:9)
我不相信它可以使用与Children属性的绑定。我今天实际上试图这样做,它对我有误,就像你做的那样。
Canvas是一个非常简陋的容器。它真的不是为这种工作而设计的。您应该查看许多ItemsControls中的一个。您可以将ViewModel的ObservableCollection数据模型绑定到其ItemsSource属性,并使用DataTemplates来处理每个项在控件中的呈现方式。
如果找不到以令人满意的方式呈现项目的ItemsControl,则可能必须创建一个能够满足您需要的自定义控件。