Is it possible to bind a Canvas's Children property in XAML?
<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>
Others have given extensible replies on how to do what you actually want to do already. I'll just explain why you couldn't bind Children
directly.
The problem is very simple - data binding target cannot be a read-only property, and Panel.Children
is read-only. There is no special handling for collections there. In contrast, ItemsControl.ItemsSource
is a read/write property, even though it is of collection type - a rare occurence for a .NET class, but required so as to support the binding scenario.
ItemsControl
is designed for creating dynamic collections of UI controls from other collections, even non-UI data collections.
You can template an ItemsControl
to draw on a Canvas
. The ideal way would involve setting the backing panel to a Canvas
and then setting the Canvas.Left
and Canvas.Top
properties on the immediate children. I could not get this to work because ItemsControl
wraps its children with containers and it is hard to set the Canvas
properties on these containers.
Instead, I use a Grid
as a bin for all of the items and draw them each on their own Canvas
. There is some overhead with this approach.
<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's the code behind that I used to set up 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 just like the System
version. I created it to demonstrate that you can use your own custom classes.
One final detail: You can bind the ItemsSource property to any collection you want. For example:
<ItemsControls ItemsSource="{Binding Document.Items}"><!--etc, etc...-->
For further details about ItemsControl and how it works, check out these documents: MSDN Library Reference; Data Templating; Dr WPF's series on ItemsControl.