将集合的集合绑定到一个Canvas元素

时间:2015-11-16 18:35:07

标签: c# wpf xaml canvas

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

有没有办法绑定图层并且不为每个图层创建一个Canvas元素?

如果没有,那么它几乎可以工作但是: 多幅画布重叠。如果我设置了背景属性,则下面的画布将不可见。即使Background设置为Transparent,鼠标事件也只能由Canvas在顶部拍摄。

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

编辑: 抱歉,误解了。我试着更清楚一点:

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

有没有办法绑定图层并且不为每个图层创建一个Canvas元素?

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

如何设置内画布的宽度和高度,以便它们与主画布具有相同的尺寸,而不会绑定?

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

重叠:好的,我想我错过了什么。

如果我根本不设置背景,它应该可以正常工作。对我来说,没有设置背景并不像Background = Transparent那样有效。**

抱歉我的英文。

编辑:感谢您的回答

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

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

或在mainCanvas上设置ClipToBounds = True,而不是内部的。我只是想要在mainCanvas维度之外具有位置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);
        }
    }
}

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>

1 个答案:

答案 0 :(得分:0)

  

有没有办法绑定图层并且不为每个图层创建一个Canvas元素?

     

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

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

我怀疑你会发现呈现性能有很大差异,但请注意,该实现交换了C#代码的XAML代码。即XAML更少,但C#更多。维护项目的镜像集合肯定会增加你自己的代码的开销(尽管它不在WPF内部做类似事情的可能性范围之外......我不知道),但是在添加和删除元素时会产生这种成本。在嵌套集合场景中,渲染它们至少应该很快。

但请注意,我怀疑它会明显加快。 UI元素的深层次结构是WPF中的标准,并且框架已经过优化以便有效地处理它。

在任何情况下,恕我直言,最好让框架处理解释高级抽象。这就是我们首先使用更高级语言和框架的原因。不要浪费任何时间试图“优化”代码,如果它让你远离更好的代表你正在建模的数据。只有在你以天真的方式实现代码的情况下,才能实现这一点,它可以100%正确地工作,并且你仍然有一个可衡量的性能问题,并且具有明确的,可实现的性能目标。

  

如何设置内画布的宽度和高度,以便它们与主画布具有相同的尺寸,而不会绑定?

     

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

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

  

重叠:好的,我想我错过了什么。

     

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

对我来说,完全没有背景填充与使用alpha通道设置为0的背景填充不同。

这会使WPF的命中测试代码大大复杂化,必须检查鼠标下每个元素的每个像素,看看该像素是否透明。我认为WPF可以特别设置固体刷填充方案,但是人们会抱怨固体但透明的刷子会禁用命中测试,而其他具有透明像素的刷子则不会,即使它们是透明的。

请注意,您可以在不参与命中测试的情况下格式化对象。只需将其Canvas属性设置为IsHitTestVisible即可。然后它可以在屏幕上呈现,但不会响应或干扰鼠标点击。


以下是如何使用单个False对象实现代码的代码示例:

<强> XAML:

Canvas

<强> C#:

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

警告:我没有彻底测试上面的代码。我只在原始代码示例的上下文中运行它,原始代码示例只会将预先填充的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方案已经过测试。虽然我当然试图避免这种情况,但可能存在印刷甚至是重要的逻辑错误。与您在互联网上找到的任何代码一样,使用风险自负。 :)