英文:
WPF Drawing a list of rectangles in a collection
问题
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 Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="FrameworkElement.Width" Value="{Binding Width}"/>
<Setter Property="FrameworkElement.Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
请注意,XAML部分的更改是将属性绑定从 ScaledRectangle.Left
, ScaledRectangle.Top
, ScaledRectangle.Width
和 ScaledRectangle.Height
更改为 Left
, Top
, Width
和 Height
。这样做是因为您要更改 ScaledRectangle
为一个集合,而不再是单个属性。在视图模型中,您将需要在新的对象中使用 Left
, Top
, Width
和 Height
属性,以便XAML可以正确绑定并绘制每个矩形。
英文:
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
得分: 0
你必须修改你的 ItemsView
以支持处理 Rect
的集合,而不是单个 Rect
:
ItemsView.cs
public class ItemsView : Control
{
public IEnumerable<Rect> DataSources
{
get => (IEnumerable<Rect>)GetValue(DataSourcesProperty);
set => SetValue(DataSourcesProperty, value);
}
public static readonly DependencyProperty DataSourcesProperty = DependencyProperty.Register(
"DataSources",
typeof(IEnumerable<Rect>),
typeof(ItemsView),
new PropertyMetadata(default(IEnumerable<Rect>), OnDataSourcesChanged));
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 OnDataSourcesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ItemsView;
this_.UnloadRectangles(e.OldValue as IEnumerable<Rect>);
this_.LoadRectangles(e.NewValue as IEnumerable<Rect>);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ItemsHost = GetTemplateChild("PART_ItemsHost") as Panel;
LoadRectangles(this.DataSources);
}
private void UnloadRectangles(IEnumerable<Rect> items)
{
if (items is null || this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in items)
{
if (this.ContainerIndexTable.TryGetValue(rectangleDefinition, out int containerIndex))
{
this.ItemsHost.Children.RemoveAt(containerIndex);
}
}
}
private void LoadRectangles(IEnumerable<Rect> items)
{
if (items is null || this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in items)
{
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 DataSources="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
英文:
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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论