How to Display Items in Canvas Through Binding

How to display items in Canvas through Binding

Set the ItemsPanel to a Canvas and bind the containers instead of the TextBlock in the DataTemplate

<ItemsControl ItemsSource="{Binding Path=ItemsToShowInCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Binding WPF Canvas Children to an ObservableCollection

I think you can do this with ItemsControl + ItemsPanelTemplate. Like this:

<ItemsControl ItemsSource="{Binding YourCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

To read more about this approach refer to Dr.WPF: ItemsControl: A to Z (P is for Panel)

binding different types of objects to canvas

There`s a great answer to somehow related question that we can extend to make it work with your problem.

Say we have two types that can be dropped to Canvas:

public class TextClass
{
public string Text { get; set; }
}

public class RectangleClass
{
public Brush FillBrush { get; set; }
}

To facilitate the use of collection to bind to we can use the code from answer I mentioned but change ItemTemplate for our custom DataTemplateSelector:

 <ItemsControl Name="icMain">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplateSelector>
<TestWPF:CustomTemplateSelector>
<TestWPF:CustomTemplateSelector.TextTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.TextTemplate>
<TestWPF:CustomTemplateSelector.RectangleTemplate>
<DataTemplate>
<Rectangle Height="25" Width="25" Fill="{Binding FillBrush}" />
</DataTemplate>
</TestWPF:CustomTemplateSelector.RectangleTemplate>
</TestWPF:CustomTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>

And that`s the template selector I used:

public class CustomTemplateSelector: DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate RectangleTemplate { get; set; }

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextClass)
return TextTemplate;
else if (item is RectangleClass)
return RectangleTemplate;
else return base.SelectTemplate(item, container);
}
}

Well, all that`s left is to bind our collection. I used simple List in code behind just for test:

List<object> aggregation = new List<object>()
{
new TextClass() { Text = "Some test text" },
new RectangleClass() { FillBrush = new SolidColorBrush(Colors.Tomato)}
};

icMain.ItemsSource = aggregation;

This code shows some test text and yummy tomato rectangle. These sample objects do not have any positioning logic, but I figured you have that already.

Binding to Canvas

If you want your Canvas defined in XAML to include the entire Canvas in your class as a single item you can write:

<Canvas>
<ContentPresenter Content="{Binding ClassCanvas}" />
... other items here ...
</Canvas>

If you want your Canvas defined in XAML to include all the same UIElements as the Canvas defined in your class, it is impossible because a UIElement can have only one UIElement parent. So if the Canvas defined in the class is the parent of a given UIElement, the Canvas defined in XAML can't be.

If you want your Canvas to display data from each UIElement in the Canvas defined in your class, you can do this with an ItemsControl with a Canvas panel and a DataTemplate:

<ItemsControl ItemsSource="{Binding ClassCanvas.Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl>
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding (Canvas.Left)}" />
<Setter Property="Canvas.Left" Value="{Binding (Canvas.Top)}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
... display of UIElement here, either using VisualBrush or custom display
</ControlTemplate>
<Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemsContainerStyle>
</ItemsControl>

Note that this code will only scan the Children property once since it is not an INotifyCollectionChanged collection.

If you want to bind to a property in your class that contains a collection of UIElements with their Canvas.Top and Canvas.Left properties set, you can easily do this if the container is an ObservableCollection instead of a Canvas.

The Canvas class was never designed for use within your data layer. I would strongly recommend you switch to using an ObservableCollection there and only use Canvas as part of your view.

Bind collection of lines to canvas in WPF

Here is an example how you could do it:

The line is defined as follows:

public class Line
{
public Point From { get; set; }

public Point To { get; set; }
}

MainWindow.xaml:

<Window x:Class="WpfApplication20.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding From.X}" Y1="{Binding From.Y}"
X2="{Binding To.X}" Y2="{Binding To.Y}"
Stroke="DarkGray" StrokeThickness="3"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
public ObservableCollection<Line> Lines { get; private set; }

public MainWindow()
{
Lines = new ObservableCollection<Line>
{
new Line { From = new Point(100, 20), To = new Point(180, 180) },
new Line { From = new Point(180, 180), To = new Point(20, 180) },
new Line { From = new Point(20, 180), To = new Point(100, 20) },
new Line { From = new Point(20, 50), To = new Point(180, 150) }
};

InitializeComponent();
DataContext = this;
}
}

In the above example, the lines are static, i.e. if you update the From and To positions, the UI will not update. If you want the UI to update, you must implement INotifyPropertyChanged for the Line class.

This sample shows a window that looks like this:

screen shot

How to display the Canvas.Left I Canvas.Top property in the content button?

There are at least two obvious and straightforward ways to do this:

Create the content inline with TextBlock:

<Button Background="AliceBlue"
Canvas.Top="20"
Canvas.Left="40">
<Button.Content>
<TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=Button}}">
<Run Text="Top"/>
<Run Text="{Binding (Canvas.Top)}"/>
<Run Text="Left"/>
<Run Text="{Binding (Canvas.Left)}"/>
</TextBlock>
</Button.Content>
</Button>

Use a converter:

class CanvasTopLeftConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value is UIElement element) ?
$"Top {Canvas.GetTop(element)} Left {Canvas.GetLeft(element)}" : Binding.DoNothing;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Button Background="AliceBlue"
Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource canvasTopLeftConverter}}"
Canvas.Top="20"
Canvas.Left="40">
</Button>

Note that when binding to an attached property as the source, you need to put parentheses around the path expression, otherwise WPF will only look on the data context object itself for the name in the path.

Binding collection of objects to Canvas in MVVM?

To display animations when the items are added to a panel you can use Fluid Layout from the Blend SDK. (Some demonstration video; sadly doesn't show the generated code for those without Blend)

For methods to bind to a canvas see this question.

I would not recommend doing this imperatively.



Related Topics



Leave a reply



Submit