【问题标题】:Binding Collection of Collection to one Canvas Element将 Collection 集合绑定到一个 Canvas 元素
【发布时间】:2016-02-17 22:56:51
【问题描述】:

我的最后一个问题被标记为重复,所以我尝试说明有什么区别。

有没有什么方法可以绑定图层并且不为每个图层创建一个 Canvas 元素?

如果没有,那么它几乎可以工作,但是: 多个画布重叠。如果我设置背景属性,下面的画布将不可见。即使背景设置为透明,鼠标事件也仅由顶部的画布获取。

如果我将 ClipToBounds 属性设置为 True(并且不设置宽度和高度),则标记不可见。宽度和高度与主画布不同。如何将这些属性绑定到主画布宽度和高度。我知道每一层都有相同的尺寸,所以我认为在每一层中存储重复信息并不好。

编辑: 我为我的误会道歉。我尽量澄清:

我想解决的问题(问题)是:

有什么方法可以绑定Layers,不为每个图层创建一个Canvas元素吗?

现在我有 mainCanvas + 多个 innerCanvases。可能只是 mainCanvas 吗?对渲染性能有影响吗?

如何设置内部 Canvas 的 Width 和 Height,使其与主 Canvas 具有相同的尺寸,而无需绑定?

mainCanvas 自动填充所有空间,但 innerCanvases 不会。 ClipToBounds=True 必须在 innerCanvases 上设置。 试过 Horizo​​ntalAligment=Stretch 但它不起作用。

重叠:好的,我想我漏掉了什么。

如果我根本不设置背景,它应该可以正常工作。对我来说很有趣的是,不设置背景与背景=透明的效果不同。**

对不起我的英语。

编辑:感谢您的回答

我认为如果我的代码不复杂化会更好,至少现在是这样。正如你所说,我发现了如何绑定到 ActualWidth:

<Canvas Width="{Binding ElementName=mainCanvas, Path=ActualWidth}"/>

或在 mainCanvas 上设置 ClipToBounds=True,而不是在内部设置。我只是希望在 mainCanvas 尺寸之外具有位置 X、Y 的标记不可见。这就是为什么我需要设置 innerCanvases 的宽度、高度。

现在一切正常,标记为答案。

这是我的代码:

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);
        }
    }
}

LayerClass.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;
    }
}

MainWindow.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;
    }
}

MainWindow.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>

【问题讨论】:

  • 如果你想要不透明的背景,你应该把它放在图层堆栈后面/外部的元素/图层(例如边框)上,并使画布本身保持透明。另外,请查看Canvas IsItemsHost=true 属性,因为它可能会有所不同。
  • 如果可能的话,我只想使用一个 mainCanvas。第二件事:设置 innerCanvases 以填充 mainCanvas 的所有空间,因此它们将始终具有相同的尺寸。

标签: c# wpf xaml canvas


【解决方案1】:

有什么方法可以绑定Layers,不为每个图层创建一个Canvas元素吗?

现在我有 mainCanvas + 多个 innerCanvases。可能只是 mainCanvas 吗?对渲染性能有影响吗?

当然可以实现代码,这样您就没有内部Canvas 元素。但不是通过绑定到Layers。您必须维护所有 MarkerClass 元素的顶级集合,并绑定到该集合。请参阅下面的示例。

我怀疑您会在 渲染 性能方面看到很大差异,但请注意,实现将 XAML 代码换成 C# 代码。 IE。 XAML 更少,但 C# 更多。维护项目的镜像集合肯定会增加您自己代码中的开销(尽管 WPF 在内部执行类似操作的可能性不在范围之内……我不知道),但是在添加和删除元素时会产生这种成本。渲染它们至少应该像嵌套集合场景一样快。

但请注意,我怀疑它会明显更快。 UI 元素的深层层次结构是 WPF 中的规范,框架经过优化以高效处理。

无论如何,恕我直言,最好让框架处理解释高级抽象。这就是我们首先使用高级语言和框架的原因。如果代码让您无法更好地表示正在建模的数据,请不要浪费任何时间尝试“优化”代码。只有当您以幼稚的方式实现代码时,才能追求它 100% 正确运行,并且您仍然有一个可衡量的性能问题,并且有一个明确、可实现的性能目标。

如何设置内部 Canvas 的 Width 和 Height,使其与主 Canvas 具有相同的尺寸,而无需绑定?

mainCanvas 自动填充所有空间,但 innerCanvases 不会。 ClipToBounds=True 必须在 innerCanvases 上设置。试过 Horizo​​ntalAligment=Stretch 但它不起作用。

通过扩大内部Canvas 对象边界以填充父元素,您试图实现什么?如果您确实需要这样做,您应该能够将内部Canvas 元素的WidthHeight 属性绑定到父级的ActualWidthActualHeight 属性。但除非内部 Canvas 被赋予某种格式或其他东西,否则您将无法看到实际的 Canvas 对象,我不希望这些元素的宽度和高度有任何实际效果。

重叠:好的,我想我漏掉了什么。

如果我根本不设置背景,它应该可以正常工作。对我来说很有趣的是,不设置 Background 与 Background=Transparent 的工作方式不同。

在我看来,完全没有背景填充与将 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 场景。可能存在印刷错误,甚至是重大的逻辑错误,尽管我当然试图避免这种情况。与您在 Internet 上找到的任何代码一样,使用风险自负。 :)

【讨论】:

    猜你喜欢
    • 2014-05-03
    • 1970-01-01
    • 1970-01-01
    • 2011-10-28
    • 2013-08-27
    • 2013-01-10
    • 1970-01-01
    • 2022-01-11
    • 2010-09-20
    相关资源
    最近更新 更多