As Clemens pointed out in his comment the ItemsControl
is the right way to do this. As I have different UIElements
that are added to the canvas I needed to add an ItemsControl.ItemTemplateSelector
as well as an ItemsControlItemContainerStyleSelector
.
XAML
<AdornerDecorator ClipToBounds="True">
<ItemsControl ItemsSource="{Binding CanvasElementList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="FloralWhite"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyleSelector>
<local:CustomStyleSelector>
<local:CustomStyleSelector.CanvasStyle_TL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TL>
<local:CustomStyleSelector.CanvasStyle_TR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TR>
<local:CustomStyleSelector.CanvasStyle_BL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BL>
<local:CustomStyleSelector.CanvasStyle_BR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BR>
<local:CustomStyleSelector.LineStyle>
<Style TargetType="ContentPresenter">
</Style>
</local:CustomStyleSelector.LineStyle>
</local:CustomStyleSelector>
</ItemsControl.ItemContainerStyleSelector>
<ItemsControl.ItemTemplateSelector>
<local:CustomTemplateSelectors>
<local:CustomTemplateSelectors.LabelTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Text}"
FontWeight="{Binding FontWeight}"
FontSize="{Binding FontSize}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LabelTemplate>
<local:CustomTemplateSelectors.LineTemplate>
<DataTemplate>
<Line X1="{Binding X1}"
X2="{Binding X2}"
Y1="{Binding Y1}"
Y2="{Binding Y2}"
Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
StrokeDashArray="{Binding StrokeDashArray}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LineTemplate>
<local:CustomTemplateSelectors.CanvasElementTemplate>
<DataTemplate>
<ContentControl Width="{Binding Path=Width, Mode=TwoWay}" Height="{Binding Path=Height, Mode=TwoWay}"
Style="{StaticResource ResourceKey=DesignerItemStyle}"
MouseDoubleClick="ContentControl_MouseDoubleClick">
<Rectangle Fill="{Binding Color}"
Stroke="LightGray"
StrokeThickness="2"
Margin="0,5,0,5"
RadiusX="10"
RadiusY="10"
IsHitTestVisible="False"/>
</ContentControl>
</DataTemplate>
</local:CustomTemplateSelectors.CanvasElementTemplate>
</local:CustomTemplateSelectors>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</AdornerDecorator>
Code
In the corresponding .cs file there are these ObservableCollections
and the CompositeCollection
. The later is the element that binds to the ItemsControl. To add new elements you have to add elements to the Observable Collections
CanvasElementList4Canvas = new ObservableCollection<CanvasElement>();
LineList4Canvas = new ObservableCollection<CustomLine>();
LabelList4Canvas = new ObservableCollection<LabelTextBlock>();
CanvasElementList = new CompositeCollection();
CanvasElementList.Add(new CollectionContainer() { Collection = CanvasElementList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LineList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LabelList4Canvas });
To set up the binding the CustomLine
Class is shown here. The CanvasElement and the LabelTextBlock class are set up in the same way.
CustomLine
public class CustomLine : INotifyPropertyChanged
{
private double _X1;
private double _X2;
private double _Y1;
private double _Y2;
private int _strokeThickness = 3;
private Brush _stroke = Brushes.Black;
private DoubleCollection _strokeDashArray = new DoubleCollection() { 1.0, 0.0 };
public double X1 { get { return _X1; } set { if (_X1 != value) { _X1 = value; NotifyPropertyChanged("X1"); } } }
public double X2 { get { return _X2; } set { if (_X2 != value) { _X2 = value; NotifyPropertyChanged("X2"); } } }
public double Y1 { get { return _Y1; } set { if (_Y1 != value) { _Y1 = value; NotifyPropertyChanged("Y1"); } } }
public double Y2 { get { return _Y2; } set { if (_Y2 != value) { _Y2 = value; NotifyPropertyChanged("Y2"); } } }
public int StrokeThickness { get { return _strokeThickness; } set { if (_strokeThickness != value) { _strokeThickness = value; NotifyPropertyChanged("StrokeThickness"); } } }
public Brush Stroke { get { return _stroke; } set { if (_stroke != value) { _stroke = value; NotifyPropertyChanged("Stroke"); } } }
public DoubleCollection StrokeDashArray { get { return _strokeDashArray; } set { if (_strokeDashArray != value) { _strokeDashArray = value; NotifyPropertyChanged("StrokeDashArray"); } } }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and finally the custom selectors are needed to use the right template and the right style for within the canvas:
public class CustomTemplateSelectors : DataTemplateSelector
{
public DataTemplate CanvasElementTemplate { get; set; }
public DataTemplate LineTemplate { get; set; }
public DataTemplate LabelTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasElementTemplate;
else if (item is CustomLine)
return LineTemplate;
else if (item is LabelTextBlock)
return LabelTemplate;
else return base.SelectTemplate(item, container);
}
}
public class CustomStyleSelector : StyleSelector
{
public Style CanvasStyle_TL { get; set; }
public Style CanvasStyle_TR { get; set; }
public Style CanvasStyle_BL { get; set; }
public Style CanvasStyle_BR { get; set; }
public Style LineStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasStyle_TL;
else if (item is CustomLine)
return LineStyle;
else if (item is LabelTextBlock)
{
var tempItem = item as LabelTextBlock;
if (tempItem.Tag == "TL")
return CanvasStyle_TL;
else if (tempItem.Tag == "TR")
return CanvasStyle_TR;
else if (tempItem.Tag == "BL")
return CanvasStyle_BL;
else if (tempItem.Tag == "BR")
return CanvasStyle_BR;
else return base.SelectStyle(item, container);
}
else return base.SelectStyle(item, container);
}
}