Can I link the Canvas Children property in XAML?

I am a little surprised that it is not possible to set binding for Canvas.Children through XAML. I had to resort to an approach with code that looks something like this:

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

I would rather just set the binding in XAML as follows:

 <Canvas x:Name="designerCanvas" Children="{Binding Document.Items}" Width="{Binding Document.Width}" Height="{Binding Document.Height}"> </Canvas> 

Is there a way to do this without resorting to a code-based approach? I did some searches on this topic, but did not come up much for this particular problem.

I don't like my current approach because it combines my beautiful Model-View-ViewModel, allowing the View View ViewModel.

+56
c # data-binding wpf canvas
May 20 '09 at 19:36
source share
5 answers

I do not consider it possible to use binding to the Children property. I actually tried to do it today, and it attacked me as if it were you.

Canvas is a very rudimentary container. It really is not intended for this kind of work. You should learn one of the many ElementsControls. You can bind the ViewModel ObservableCollection of data models to the ItemsSource property and use DataTemplates to handle how each item is displayed in the control.

If you cannot find an ItemsControl that satisfies satisfactory results, you may need to create a custom control that will do what you need.

+8
May 20 '09 at 20:05
source share
 <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> 
+139
Jun 23 '09 at 1:18
source share

Others gave extensive answers on how to do what you really want to do already. I’ll just explain why you couldn’t directly bind Children .

The problem is very simple: the purpose of data binding cannot be read-only, and Panel.Children is read-only. There is no special handling for collections. In contrast, ItemsControl.ItemsSource , a read / write property, even if it is a collection type, is a rare occurrence for the .NET class, but is required to support a binding script.

+24
Jul 30 '09 at 21:57
source share

ItemsControl designed to create dynamic collections of user interface controls from other collections, even for data collections other than the UI.

You can create an ItemsControl template for drawing on Canvas . The ideal way is to set the support panel on Canvas and then set the Canvas.Left and Canvas.Top for immediate children. I could not get this to work because the ItemsControl wrapping its children with containers, and it is difficult to set the Canvas properties in these containers.

Instead, I use Grid as bin for all objects and draw them each on my Canvas . With this approach, there is some overhead.

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

Here is the code I used to create the source collection:

 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 is a custom class that behaves like a version of System . I created it to demonstrate that you can use your own custom classes.

One detail: you can bind the ItemsSource property to any collection you want. For example:

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

For more information about ItemsControl and how it works, check out these documents: MSDN Library Reference ; Data Templates Dr WPF series on ItemsControl .

+18
May 20 '09 at 20:35
source share
 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); } } } 

And using:

 <Canvas x:Name="PART_SomeCanvas" Controls:CanvasAssistant.BoundChildren="{TemplateBinding SomeItems}"/> 
+11
Sep 20 '10 at 12:37
source share



All Articles