My WPF app has a ViewModel that has an ObservableCollection that holds objects of type Item
. Each Item has a color and a Rect that is drawn on the canvas:
Item Class:
public class Item
{
public Color ItemColor {get; set;}
public Rect ScaledRectangle {get; set;}
}
XAML:
<Grid>
<ItemsControl Name="Items" ItemsSource="{Binding Items, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ItemView Visibility="Visible">
<local:ItemView.Background>
<SolidColorBrush Color="{Binding ItemColor}"/>
</local:ItemView.Background>
</local:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding ScaledRectangle.Left}"/>
<Setter Property="Canvas.Top" Value="{Binding ScaledRectangle.Top}"/>
<Setter Property="FrameworkElement.Width" Value="{Binding ScaledRectangle.Width}"/>
<Setter Property="FrameworkElement.Height" Value="{Binding ScaledRectangle.Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In my ViewModel, all I have to do is add a new Item to the ObservableCollection to draw it on the screen.
This works really well but now I find I need to change the ScaledRectangle property to some kind of collection. I want to modify this XAML to draw each rectangle in the ScaledRectangles collection. Can I modify this XAML so I can keep the ViewModel functionality to something like viewModel.AddNewItem(newItem)
?
You must modify your ItemsView
to support handling of a collection of Rect
instead of a single Rect
:
ItemsView.cs
public class ItemsView : Control
{
public Item DataSource
{
get => (Item)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register(
"DataSource",
typeof(Item),
typeof(ItemsView),
new PropertyMetadata(default(Item), OnDataSourceChanged));
private Panel ItemsHost { get; set; }
private Dictionary<Rect, int> ContainerIndexTable { get; }
static ItemsView()
=> DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemsView), new FrameworkPropertyMetadata(typeof(ItemsView)));
public ItemsView()
=> this.ContainerIndexTable = new Dictionary<Rect, int>();
private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ItemsView;
this_.UnloadRectangles(e.OldValue as Item);
this_.LoadRectangles(e.NewValue as Item);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ItemsHost = GetTemplateChild("PART_ItemsHost") as Panel;
LoadRectangles(this.DataSource);
}
private void UnloadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
if (this.ContainerIndexTable.TryGetValue(rectangleDefinition, out int containerIndex))
{
this.ItemsHost.Children.RemoveAt(containerIndex);
}
}
}
private void LoadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
var container = new Rectangle()
{
Height = rectangleDefinition.Height,
Width = rectangleDefinition.Width,
Fill = new SolidColorBrush(item.ItemColor)
};
Canvas.SetLeft(container, rectangleDefinition.Left);
Canvas.SetTop(container, rectangleDefinition.Top);
int containerIndex = this.ItemsHost.Children.Add(container);
_ = this.ContainerIndexTable.TryAdd(rectangleDefinition, containerIndex);
}
}
}
Gernic.xaml
<Style TargetType="local:ItemsView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ItemsView">
<Canvas x:Name="PART_ItemsHost" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<local:ItemsView DataSource="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>