将集合的集合绑定到一个画布元素
本文关键字:集合 一个 元素 布元素 绑定 | 更新日期: 2023-09-27 18:22:11
我的最后一个问题被标记为重复,所以我试图说明区别。
有没有任何方法可以绑定Layers而不为每个Layers创建Canvas元素?
如果没有,那么它几乎有效,但是:多个画布重叠。如果我设置了Background属性,下面的画布将不可见。即使"背景"设置为"透明",鼠标事件也仅由顶部的画布获取。
如果我将ClipToBounds属性设置为True(并且不设置宽度和高度),则标记不可见。"宽度"answers"高度"与主画布不同。如何将这些属性绑定到"主画布宽度"answers"高度"。我知道每个层都有相同的尺寸,所以我认为在每个层中存储重复的信息是不好的。
编辑:抱歉有误会。我试着澄清一下:
我想解决的问题是:
是否有任何方法可以绑定Layers而不为每个Layers创建Canvas元素
现在我有了mainCanvas+多个innerCanvas。它可能只是mainCanvas吗?它对渲染性能有任何影响吗?
如何设置内部画布的宽度和高度,使其与主画布具有相同的尺寸,而不绑定
mainCanvas会自动填充所有空间,但innerCanvas不会。ClipToBounds=True必须在innerCanvas上设置。Tried HorizontalAliment=拉伸,但不起作用。
重叠:好吧,我想我错过了什么
如果我根本不设置背景,它可以正常工作,因为它应该。对我来说很有趣的是,不设置背景与背景=透明不一样。**
对不起我的英语。
编辑:感谢您的回答
我认为如果我不使代码复杂化会更好,至少现在是这样。我发现了如何绑定到ActualWidth,正如你所说:
<Canvas Width="{Binding ElementName=mainCanvas, Path=ActualWidth}"/>
或者在mainCanvas上设置ClipToBounds=True,而不是在内部设置。我只是想让主画布尺寸之外的位置X、Y的标记不可见。这就是为什么我需要设置内部画布的宽度,高度。
现在一切正常,标记为答案。
这是我的代码:
ViewModel.cs
public class ViewModel
{
public ObservableCollection<LayerClass> Layers
{ get; set; }
public ViewModel()
{
Layers = new ObservableCollection<LayerClass>();
for (int j = 0; j < 10; j++)
{
var Layer = new LayerClass();
for (int i = 0; i < 10; i++)
{
Layer.Markers.Add(new MarkerClass(i * 20, 10 * j));
}
Layers.Add(Layer);
}
}
}
分层分类.cs
public class LayerClass
{
public ObservableCollection<MarkerClass> Markers
{ get; set; }
public LayerClass()
{
Markers = new ObservableCollection<MarkerClass>();
}
}
MarkerClass.cs
public class MarkerClass
{
public int X
{ get; set; }
public int Y
{ get; set; }
public MarkerClass(int x, int y)
{
X = x;
Y = y;
}
}
主窗口.xaml.cs
public partial class MainWindow : Window
{
private ViewModel _viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = _viewModel;
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
Ellipse s = (Ellipse)sender;
s.Fill = Brushes.Green;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
Ellipse s = (Ellipse)sender;
s.Fill = Brushes.Black;
}
}
主窗口.xaml
<Window x:Class="TestSO33742236WpfNestedCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestSO33742236WpfNestedCollection"
Loaded="Window_Loaded"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=Layers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="LightBlue">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type c:LayerClass}">
<ItemsControl ItemsSource="{Binding Path=Markers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="myCanvas"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="20" Height="20" Fill="Black" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<p:Style> <!-- Explicit namespace to workaround StackOverflow XML formatting bug -->
<Setter Property="Canvas.Left" Value="{Binding Path=X}"></Setter>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}"></Setter>
</p:Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
是否有任何方法可以绑定Layers而不为每个Layers创建Canvas元素
现在我有了mainCanvas+多个innerCanvas。它可能只是mainCanvas吗?它对渲染性能有任何影响吗?
当然,可以实现代码,这样就不会有内部的Canvas
元素。但不是通过与CCD_ 2结合。您必须维护所有MarkerClass
元素的顶级集合,并绑定到该集合。请参阅下面的示例。
我怀疑您是否会看到渲染性能的很大差异,但请注意,该实现以XAML代码换取C#代码。也就是说,XAML少了,但C#多了。维护项目的镜像集合肯定会增加您自己代码的开销(尽管WPF在内部做类似的事情并不是不可能的…我不知道),但在添加和删除元素时会产生这种开销。呈现它们应该至少像在嵌套集合场景中一样快。
然而,请注意,我怀疑它是否会明显更快。UI元素的深层结构是WPF中的规范,并且框架经过优化以有效地处理这一点。
无论如何,IMHO最好让框架处理解释高级抽象。这就是为什么我们首先使用更高级的语言和框架。不要浪费任何时间试图"优化"代码,如果这会使您无法更好地表示正在建模的数据。只有当你以天真的方式实现代码时,它才能100%正确地工作,而且你仍然有一个可衡量的性能问题,有一个明确、可实现的性能目标。
如何设置内部画布的宽度和高度,使其与主画布具有相同的尺寸,而不绑定
mainCanvas会自动填充所有空间,但innerCanvas不会。ClipToBounds=True必须在innerCanvas上设置。Tried HorizontalAliment=拉伸,但不起作用。
通过扩展内部Canvas
对象边界以填充父元素,您试图实现什么?如果您真的需要这样做,那么您应该能够将内部Canvas
元素的Width
和Height
属性绑定到父级的ActualWidth
和ActualHeight
属性。但是,除非内部Canvas
被赋予某种格式或其他东西,否则你将无法看到实际的Canvas
对象,我也不希望这些元素的宽度和高度有任何实际效果。
重叠:好吧,我想我错过了什么
如果我根本不设置背景,它可以正常工作,因为它应该。对我来说很有趣的是,不设置"背景"与"背景=透明"不一样。
对我来说,完全没有背景填充与alpha通道设置为0的背景填充不同是有道理的。
必须检查鼠标下每个元素的每个像素,以查看该像素是否透明,这将使WPF的命中测试代码变得非常复杂。我想WPF可能会对实心笔刷填充场景进行特殊处理,但人们会抱怨实心但透明的笔刷会禁用命中测试,而其他具有透明像素的笔刷则不会,即使它们是透明的。
请注意,您可以格式化对象,而无需让它参与命中测试。只需将其IsHitTestVisible
属性设置为False
。然后它可以在屏幕上渲染,但不会响应或干扰鼠标单击。
以下是如何使用单个Canvas
对象实现代码的代码示例:
XAML:
<Window x:Class="TestSO33742236WpfNestedCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestSO33742236WpfNestedCollection"
Loaded="Window_Loaded"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=Markers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="LightBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type c:MarkerClass}">
<Ellipse Width="20" Height="20" Fill="Black" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<p:Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}"></Setter>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}"></Setter>
</p:Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
C#:
class ViewModel
{
public ObservableCollection<MarkerClass> Markers { get; set; }
public ObservableCollection<LayerClass> Layers { get; set; }
public ViewModel()
{
Markers = new ObservableCollection<MarkerClass>();
Layers = new ObservableCollection<LayerClass>();
Layers.CollectionChanged += _LayerCollectionChanged;
for (int j = 0; j < 10; j++)
{
var Layer = new LayerClass();
for (int i = 0; i < 10; i++)
{
Layer.Markers.Add(new MarkerClass(i * 20, 10 * j));
}
Layers.Add(Layer);
}
}
private void _LayerCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
ObservableCollection<LayerClass> layers = (ObservableCollection<LayerClass>)sender;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_InsertMarkers(layers, e.NewItems.Cast<LayerClass>(), e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
_RemoveMarkers(layers, e.OldItems.Count, e.OldStartingIndex);
_InsertMarkers(layers, e.NewItems.Cast<LayerClass>(), e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
_RemoveMarkers(layers, e.OldItems.Count, e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
Markers.Clear();
break;
}
}
private void _RemoveMarkers(ObservableCollection<LayerClass> layers, int count, int removeAt)
{
int removeMarkersAt = _MarkerCountForLayerIndex(layers, removeAt);
while (count > 0)
{
LayerClass layer = layers[removeAt++];
layer.Markers.CollectionChanged -= _LayerMarkersCollectionChanged;
Markers.RemoveRange(removeMarkersAt, layer.Markers.Count);
}
}
private void _InsertMarkers(ObservableCollection<LayerClass> layers, IEnumerable<LayerClass> newLayers, int insertLayersAt)
{
int insertMarkersAt = _MarkerCountForLayerIndex(layers, insertLayersAt);
foreach (LayerClass layer in newLayers)
{
layer.Markers.CollectionChanged += _LayerMarkersCollectionChanged;
Markers.InsertRange(layer.Markers, insertMarkersAt);
insertMarkersAt += layer.Markers.Count;
}
}
private void _LayerMarkersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<MarkerClass> markers = (ObservableCollection<MarkerClass>)sender;
int layerIndex = _GetLayerIndexForMarkers(markers);
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Markers.InsertRange(e.NewItems.Cast<MarkerClass>(), _MarkerCountForLayerIndex(Layers, layerIndex));
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
Markers.RemoveRange(layerIndex, e.OldItems.Count);
Markers.InsertRange(e.NewItems.Cast<MarkerClass>(), _MarkerCountForLayerIndex(Layers, layerIndex));
break;
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
Markers.RemoveRange(layerIndex, e.OldItems.Count);
break;
}
}
private int _GetLayerIndexForMarkers(ObservableCollection<MarkerClass> markers)
{
for (int i = 0; i < Layers.Count; i++)
{
if (Layers[i].Markers == markers)
{
return i;
}
}
throw new ArgumentException("No layer found with the given markers collection");
}
private static int _MarkerCountForLayerIndex(ObservableCollection<LayerClass> layers, int layerIndex)
{
return layers.Take(layerIndex).Sum(layer => layer.Markers.Count);
}
}
static class Extensions
{
public static void InsertRange<T>(this ObservableCollection<T> source, IEnumerable<T> items, int insertAt)
{
foreach (T t in items)
{
source.Insert(insertAt++, t);
}
}
public static void RemoveRange<T>(this ObservableCollection<T> source, int index, int count)
{
for (int i = index + count - 1; i >= index; i--)
{
source.RemoveAt(i);
}
}
}
注意事项:我还没有彻底测试过上面的代码。我只在原始代码示例的上下文中运行过它,该示例只将预先填充的LayerClass
对象添加到Layers
集合中,因此只测试了Add
场景。可能会有排版错误,甚至是严重的逻辑错误,当然我已经尽力避免了。与您在互联网上找到的任何代码一样,使用风险自负。:)