本文介绍了将收藏集绑定到一个画布元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



是否有任何方法可以绑定图层并且不创建Canvas元素每一个人?



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



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



编辑:
抱歉,误解了。我尝试更加澄清:



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



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



现在我有mainCanvas和多个innerCanvases。可能只是mainCanvas吗?



如何设置内部Canvas的宽度和高度,以便它们具有与主Canvas相同的尺寸,而无需绑定?



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



重叠部分:好的,我想我错过了一些东西。



如果我完全不设置Background,它应该会正常工作。对我来说很有趣,没有设置Background不能和Background = Transparent一样。**



对不起,我的英语。



编辑:谢谢您的回答



我认为如果不使我的代码复杂化会更好, 至少现在(是。我发现了如何按照您所说的那样绑定到ActualWidth:

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

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



现在一切都在工作,标记为答案。



这是我的代码:



ViewModel.cs

 公共类ViewModel 
{
public ObservableCollection< LayerClass>
层{get;组; }

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

 公共类LayerClass 
{
public ObservableCollection< MarkerClass>标记
{get;组; }

public LayerClass()
{
Markers = new ObservableCollection< MarkerClass>();
}
}

MarkerClass.cs

 公共类MarkerClass 
{
public int X
{get;组; }

public int Y
{get;组; }

公共MarkerClass(int x,int y)
{
X = x;
Y = y;
}
}

MainWindow.xaml.cs

 公共局部类MainWindow:窗口
{
private ViewModel _viewModel = new ViewModel();

public MainWindow()
{
InitializeComponent();
}

private void Window_Loaded(object sender,RoutedEventArgs e)
{
this.DataContext = _viewModel;
}

private void Ellipse_MouseEnter(对象发送者,MouseEventArgs e)
{
Ellipse s =(Ellipse)sender;
s.Fill = Brushes.Green;
}

private void Ellipse_MouseLeave(object sender,MouseEventArgs e)
{
Ellipse s =(Ellipse)sender;
s.Fill =刷子。黑色;
}
}

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> < ;!-明确的名称空间来解决StackOverflow XML格式错误->
< Setter Property = Canvas.Left Value = {Binding Path = X}>< / Setter>
< Setter Property = Canvas.Top Value = {Binding Path = Y}>< / Setter>
< / p:样式>
< /ItemsControl.ItemContainerStyle>
< / ItemsControl>
< / DataTemplate>
< /ItemsControl.ItemTemplate>
< / ItemsControl>
< / Window>


解决方案

当然可以实现代码,以免内部没有画布元素。但不能绑定到 Layers 。您必须维护所有 MarkerClass 元素的顶级集合,并绑定到该元素。请参见下面的示例。



我怀疑您会发现渲染性能存在很大差异,但请注意,该实现使用XAML代码进行交易C#代码。即XAML少了,但是C#多了。维护项目的镜像集合肯定会增加您自己的代码的开销(尽管这并不超出WPF在内部进行类似操作的可能性,我不知道),但这是在添加和删除元素时要付出的代价。呈现它们的速度至少应与嵌套收集方案中的速度一样。



但是请注意,我怀疑这样做会明显更快。 UI元素的深层次结构是WPF中的规范,并且对该框架进行了优化以有效地进行处理。



在任何情况下,恕我直言,最好让框架处理解释高级抽象。这就是为什么我们首先使用高级语言和框架的原因。如果代码使您无法更好地表示要建模的数据,则不要浪费任何时间尝试优化代码。仅当当您以朴素的方式实施代码时,它才能100%正确地工作,并且您仍然遇到一个可衡量的性能问题,并且目标明确,可以实现。


$ b

通过内部 Canvas 对象边界扩展为填充父元素?如果确实需要执行此操作,则应该可以绑定内部 Canvas 元素的 Width Height 属性设置为父级的 ActualWidth ActualHeight 属性。但是,除非给内部的 Canvas 提供某种格式或其他格式,否则您将看不到实际的 Canvas

对我来说,这很有意义完全没有背景填充与将Alpha通道设置为0的背景填充不同。



这将大大复杂化WPF的点击测试代码,检查鼠标下每个元素的每个像素,看看该像素是否透明。我以为WPF可以对实心笔刷填充场景进行特殊处理,但是随后人们会抱怨说,实心但透明的笔刷无法进行命中测试,而其他具有透明像素的笔刷却不能,即使它们是透明的。 p>

请注意,您可以格式化对象而无需使其参与命中测试。只需将其 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:样式>
< /ItemsControl.ItemContainerStyle>
< / ItemsControl>
< / Window>

C#:

  class ViewModel 
{
public ObservableCollection< MarkerClass>标记{组; }
public ObservableCollection< LayerClass>图层{get;组; }

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>层=(ObservableCollection< LayerClass>)发送方;

开关(e.Action)
{
case NotifyCollectionChangedAction.Add:
_InsertMarkers(layers,e.NewItems.Cast< LayerClass>(),e.​​NewStartingIndex );
休息时间;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
_RemoveMarkers(layers,e.OldItems.Count,e.OldStartingIndex);
_InsertMarkers(图层,e.NewItems.Cast< LayerClass>(),e.​​NewStartingIndex);
休息时间;
case NotifyCollectionChangedAction.Remove:
_RemoveMarkers(layers,e.OldItems.Count,e.OldStartingIndex);
休息时间;
case NotifyCollectionChangedAction.Reset:
Markers.Clear();
休息时间;
}
}

私人void _RemoveMarkers(ObservableCollection< LayerClass>层,整数计数,整数removeAt)
{
int removeMarkersAt = _MarkerCountForLayerIndex(layers, removeAt);

而(计数> 0)
{
LayerClass layer = layers [removeAt ++];

layer.Markers.CollectionChanged-= _LayerMarkersCollectionChanged;
Markers.RemoveRange(removeMarkersAt,layer.Markers.Count);
}
}

私人无效_InsertMarkers(ObservableCollection< LayerClass>层,IEnumerable< LayerClass> newLayers,int insertLayersAt)
{
int insertMarkersAt = _MarkerCountForyer (层,insertLayersAt);

foreach(newLayers中的LayerClass层)
{
layer.Markers.CollectionChanged + = _LayerMarkersCollectionChanged;
Markers.InsertRange(layer.Markers,insertMarkersAt);
insertMarkersAt + = layer.Markers.Count;
}
}

private void _LayerMarkersCollectionChanged(object sender,NotifyCollectionChangedEventArgs e)
{
ObservableCollection< MarkerClass>标记=(ObservableCollection< MarkerClass>)发送者;
int layerIndex = _GetLayerIndexForMarkers(markers);

开关(e.Action)
{
case NotifyCollectionChangedAction.Add:
Markers.InsertRange(e.NewItems.Cast< MarkerClass>(),_MarkerCountForLayerIndex(Layers ,layerIndex));
休息时间;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
Markers.RemoveRange(layerIndex,e.OldItems.Count);
Markers.InsertRange(e.NewItems.Cast< MarkerClass>(),_MarkerCountForLayerIndex(Layers,layerIndex));
休息时间;
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
Markers.RemoveRange(layerIndex,e.OldItems.Count);
休息时间;
}
}

private int _GetLayerIndexForMarkers(ObservableCollection< MarkerClass>标记)
{
for(int i = 0; i< Layers.Count ; i ++)
{
if(Layers [i] .Markers == markers)
{
return i;
}
}

抛出新的ArgumentException(未找到具有给定标记集合的图层);
}

私有静态int _MarkerCountForLayerIndex(ObservableCollection< LayerClass> layers,int layerIndex)
{
返回layers.Take(layerIndex).Sum(layer => layer.Markers.Count);
}
}

静态类扩展
{
public static void InsertRange< T>(此ObservableCollection< T>来源,IEnumerable< T>项目,int insertAt)
{
foreach(项目中的T t)
{
source.Insert(insertAt ++,t);
}
}

public static void RemoveRange< T>(此ObservableCollection< T来源,int索引,int计数)
{ int i =索引+计数-1; i> =索引; i--)
{
source.RemoveAt(i);
}
}
}

注意:我尚未彻底测试以上代码。我仅在原始代码示例的上下文中运行了该示例,该示例仅将预填充的 LayerClass 对象添加到 Layers 集合,因此仅测试了添加方案。虽然可能会出现印刷错误,甚至可能是重要的逻辑错误,但我当然尝试避免这种情况。与在Internet上找到的任何代码一样,使用时需自担风险。 :)


My last question was marked as duplicate so I try to show what is the difference.

Is there any way to bind Layers and don't create a Canvas element for each one?

If not, then it almost works but:Multiple canvases overlap. If I set Background property, canvases below will be not visible. Even if Background is set to Transparent, then the Mouse Events are taken only by the Canvas on top.

If I set ClipToBounds property to True (and don't set Width&Height) Markers are not visible. The Width and Height is not the same as main canvas. How to bind these Properties to Main Canvas Width and Height. I know that every Layer will have the same dimensions, so I don't think it would be good to store duplicate information in every Layer.

EDIT:Sorry for misunderstanding. I try to be more clarify:

Problems (questions) I want to solve are:

Is there any way to bind Layers and don't create a Canvas element for each one?

Now I have mainCanvas + multiple innerCanvases. Could it be just mainCanvas? Does it have any influence on rendering performance?

How to set Width and Height of inner Canvases, so they will have the same dimensions as main Canvas, without binding?

mainCanvas automatically fills all the space, but innerCanvases don't. ClipToBounds=True must be set on innerCanvases.Tried HorizontalAligment=Stretch but it is not working.

The overlapping: Okay, I think I missed something.

If I don't set Background at all it works fine, as it should. It was just funny for me that not setting Background doesn't work the same as Background=Transparent.**

Sorry for my English.

EDIT: Thanks for your answer

I think it will be better if I don't complicate my code, at least for now. I found out how to bind to ActualWidth as you said:

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

or set ClipToBounds=True on mainCanvas, not the inner ones. I just wanted markers that have Positions X, Y outside mainCanvas dimensions to not be visible. Thats why I needed to set Width, Height of innerCanvases.

Everything is working now, marked as answer.

Here is my code:

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>
解决方案

Certainly it is possible to implement the code so that you don't have inner Canvas elements. But not by binding to Layers. You would have to maintain a top-level collection of all of the MarkerClass elements, and bind to that. Please see below for an example of this.

I doubt you will see much difference in rendering performance, but note that the implementation trades XAML code for C# code. I.e. there's less XAML but a lot more C#. Maintaining the mirror collection of items certainly would add to overhead in your own code (though it's not outside the realm of possibility that WPF does something similar internally…I don't know), but that cost comes when adding and removing elements. Rendering them should be at least fast as in the nested collection scenario.

Note, however, that I doubt it would be noticeably faster. Deep hierarchies of UI elements is the norm in WPF, and the framework is optimized to handle that efficiently.

In any case, IMHO it is better to let the framework handle interpreting high-level abstractions. That's why we use higher-level languages and frameworks in the first place. Don't waste any time at all trying to "optimize" code if it takes you away from a better representation of the data you're modeling. Only pursue that if and when you have implemented the code the naïve way, it works 100% correctly, and you still have a measurable performance problem with a clear, achievable performance goal.

What is it you are trying to achieve by having the inner Canvas objects boundaries expand to fill the parent element? If you really need to do this, you should be able to bind the inner Canvas elements' Width and Height properties to the parent's ActualWidth and ActualHeight properties. But unless the inner Canvas is given some kind of formatting or something, you would not be able to see the actual Canvas object and I wouldn't expect the width and height of those elements to have any practical effect.

It makes sense to me that having no background fill at all would be different than having a background fill that has the alpha channel set to 0.

It would significantly complicate WPF's hit-testing code to have to check each pixel of each element under the mouse to see if that pixel is transparent. I suppose WPF could have special-cased the solid-brush fill scenario, but then people would complain that a solid-but-transparent brush disables hit-testing while other brushes with transparent pixels don't, even where they are transparent.

Note that you can format an object without having it participate in hit-testing. Just set its IsHitTestVisible property to False. Then it will can render on the screen, but will not respond to or interfere with mouse clicks.


Here's the code example of how you might implement the code using just a single Canvas object:

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

Caveat: I have not tested the code above thoroughly. I've only run it in the context of the original code example, which only ever adds pre-populated LayerClass objects to the Layers collection, and so only the Add scenario has been tested. There could be typographical or even significant logic bugs, though of course I've tried to avoid that. As with any code you find on the Internet, use at your own risk. :)

这篇关于将收藏集绑定到一个画布元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-12 14:00