是否有可能在XAML中绑定Canvas的Children属性?

我有点惊讶,不可能通过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> 

有没有办法做到这一点,而不诉诸代码隐藏的方法? 我已经做了一些关于这个主题的search,但是对于这个特定的问题还没有提出太多的东西。

我不喜欢我目前的方法,因为它通过使视图意识到它的ViewModel而把我的不错的Model-View-ViewModel弄糟了。

我不相信它可能使用绑定与儿童财产。 我其实今天试图做到这一点,它就像你一样对我产生了误解。

帆布是一个非常基本的容器。 它确实不是为这种工作而devise的。 你应该看看其中的一个ItemsControls。 您可以将ViewModel的ObservableCollection数据模型绑定到它们的ItemsSource属性,并使用DataTemplates来处理每个项目在控件中的呈现方式。

如果找不到令人满意的呈现项目的ItemsControl,则可能需要创build一个自定义控件来执行所需的操作。

 <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> 

其他人已经给出了如何做你真正想做的事情的可扩展的答复。 我只是解释你为什么不能直接绑定Children

问题很简单 – 数据绑定目标不能是只读属性, Panel.Children是只读的。 那里的collections品没有特别的处理。 相比之下, ItemsControl.ItemsSource是一个读/写属性,即使它是集合types – 这是一个.NET类罕见的情况,但需要支持绑定scheme。

ItemsControl被devise用于从其他集合(甚至非UI数据集合)创buildUI控件的dynamic集合。

您可以模板一个ItemsControlCanvas上绘制。 理想的方法是将后台面板设置为Canvas ,然后设置直接子节点上的Canvas.LeftCanvas.Top属性。 我无法得到这个工作,因为ItemsControl包装容器的子项,很难设置这些容器的Canvas属性。

相反,我使用一个Grid作为所有项目的容器,并在各自的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版本一样。 我创build它来certificate你可以使用你自己的自定义类。

最后一个细节:您可以将ItemsSource属性绑定到您想要的任何集合。 例如:

 <ItemsControls ItemsSource="{Binding Document.Items}"><!--etc, etc...--> 

有关ItemsControl及其工作方式的更多详细信息,请查看以下文档: MSDN Library Reference ; 数据模板 ; WPF博士的ItemsControl系列 。

 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}"/>