WPF Drawing a list of rectangles in a collection

318 Views Asked by At

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

1

There are 1 best solutions below

0
BionicCode On BEST ANSWER

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>