【问题标题】:xaml collections syncing / designer supportxaml 集合同步/设计器支持
【发布时间】:2016-01-06 18:08:25
【问题描述】:

如果我有这个:

<Grid xmlns:local="clr-namespace:xaml_collections">
    <StackPanel>
        <StackPanel.DataContext>
            <local:AComposite>
                <local:AComposite.TheChildren>
                    <Rectangle
                        Height="85"
                        Width="85"
                        Fill="Red"
                        x:Name="foobar"
                        />
                </local:AComposite.TheChildren>
            </local:AComposite>
        </StackPanel.DataContext>

        <TextBlock DataContext="{Binding TheChildren[0]}">
            <Run Text="{Binding Height}"></Run>
        </TextBlock>

        <TextBlock DataContext="{Binding ChildrenByName[foobar]}">
            <Run Text="{Binding Height}"></Run>
        </TextBlock>
        </StackPanel>
    </Grid>

还有这个:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Shapes;
using System.Collections.Specialized;

namespace xaml_collections
{
public class AComposite : FrameworkElement
{
    public AComposite()
    {
        if (_TheChildren != null && _TheChildren is ObservableCollection<Rectangle>)
        {
            ((ObservableCollection<Rectangle>)_TheChildren)
                .CollectionChanged += AComposite_CollectionChanged;
        }
    }

    void AComposite_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e != null && e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
        {
            foreach (var anItem in e.NewItems)
            {
                if (anItem is FrameworkElement)
                {
                    FrameworkElement theFrameworkElementItem = (FrameworkElement)anItem;
                    _ChildrenByName.Add(theFrameworkElementItem.Name, theFrameworkElementItem);
                }
            }
        }
    }                   

    private Dictionary<String,FrameworkElement> _ChildrenByName = new Dictionary<String,FrameworkElement>();
    public Dictionary<String,FrameworkElement> ChildrenByName
    {
        get
        {
            return _ChildrenByName;
        }
        private set { }
    }

    private IList _TheChildren = new ObservableCollection<Rectangle>();
    public IList TheChildren
    {
        get
        {  
            return _TheChildren;
        }
        private set { }
    }
}

}

我可以在设计时看到绑定到Children[0]TextBlockTextBlock 中的Height 值为85。但我只能看到ChildrenByName[foobar] 的值。运行时的高度。有没有办法在设计时保持这些集合同步?

编辑

这似乎有效,感谢尼克米勒。这里的教训是假设不要尝试创建衍生集合。使用不复制集合的属性,而只是引用它。

public Dictionary<String,FrameworkElement> ByName
{
    get
        {
        return
        this.Children.AsQueryable().Cast<FrameworkElement>()
            .ToDictionary( element => element.Tag.ToString() );
        }
 }

还有这样的事情:

public List<FrameworkElement> Top3
{
    get
    {
        return
        this.Children.AsQueryable().Cast<FramworkElement>().
        OrderByDescending( element => element.Height )
        .Take(3).ToList();
    }
}

【问题讨论】:

  • 如果您添加带有虚拟数据的设计时视图模型,它将允许您查看 UI 在“真实世界”情况下的外观
  • @d.moncada 为什么我需要它?至少在一种情况下,我已经可以看到在 TheChildren 中初始化的“虚拟”数据。如果没有必要,我不希望有更多的“虚拟”东西四处飘荡。
  • 请问你为什么从FrameworkElement派生?
  • @NickMiller 好问题。我不完全确定,但基本上我希望能够使用 XAML 来创建这些类型的数据上下文,这似乎很自然,而不是,什么可能......更合适——DependencyObject?如果有意义的话,我还有一些未来的目标,即为 DataContexts AND 视觉表示使用相同的类层次结构。
  • 我问的原因是DataContext 通常是模型/视图模型的一部分,而不是应用程序的视图。您的AComposite 课程到底应该是什么?它应该是某种视觉元素的容器吗?

标签: c# wpf xaml


【解决方案1】:

首先,我不建议使用相同的数据创建多个集合。与其尝试同步这些集合,不如专注于利用 WPF 中内置的数据绑定引擎。

根据您的 cmets,听起来您想要以下内容:

这使用了您的 Composite 类,但经过调整以使用 DependencyProperties。这些特殊类型的属性对于在设计时工作非常有用,并且还允许您的属性参与数据绑定系统。如果您还没有,请参阅Dependency Properties Overview

Composite.cs

using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Shapes;

namespace _34639801
{
    [ContentProperty("Children")]
    public class Composite : FrameworkElement
    {
        public static readonly DependencyProperty ChildrenProperty = DependencyProperty.Register("Children",
            typeof(IList),
            typeof(Composite),
            new PropertyMetadata(default(IList)));

        [Category("Common")]
        public IList Children
        {
            get { return (IList)GetValue(ChildrenProperty); }
            set { SetValue(ChildrenProperty, value); }
        }

        //Get children by name.
        public Shape this[string name]
        {
            get
            {
                foreach (Shape s in Children)
                {
                    if (s.Name.Equals(name))
                    {
                        return s;
                    }
                }
                return null;
            }
        }

        public Composite()
        {
            SetCurrentValue(ChildrenProperty, new ObservableCollection<Shape>());
        }
    }
}

MainWindow.xaml

<Window x:Class="_34639801.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_34639801"
        mc:Ignorable="d"
        Title="MainWindow" Height="456.061" Width="575.91">
    <Grid>
        <Grid.DataContext>
            <local:Composite>
                <local:Composite.Children>
                    <Rectangle Width="40" Height="20" Fill="Red" Stroke="Black" Name="R1"/>
                    <Rectangle Width="50" Height="20" Fill="Cyan" Stroke="Blue" Name="R2"/>
                    <Rectangle Width="40" Height="20" Fill="Green" Stroke="Black" Name="R3"/>
                    <Rectangle Width="90" Height="10" Fill="Blue" Stroke="Black" Name="R4"/>
                    <Rectangle Width="40" Height="80" Fill="Yellow" Stroke="Black" Name="R5"/>
                    <Rectangle Width="40" Height="10" Fill="Magenta" Stroke="Black" Name="R6"/>
                    <Rectangle Width="30" Height="30" Fill="Orange" Stroke="Black" Name="Square"/>
                </local:Composite.Children>
            </local:Composite>
        </Grid.DataContext>

        <ListBox ItemsSource="{Binding Children}" Margin="10,10,0,10" HorizontalContentAlignment="Stretch" HorizontalAlignment="Left" Width="337">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Black" BorderThickness="1,1,1,1">
                        <Grid>
                            <TextBlock Margin="6,0,0,0" Text="{Binding Name, StringFormat=Name: {0}}"/>
                            <TextBlock Margin="6,14,0,0" Text="{Binding Width, StringFormat=Width: {0}}"/>
                            <TextBlock Margin="6,28,0,0" Text="{Binding Height, StringFormat=Height: {0}}"/>
                            <TextBlock Margin="6,42,0,0" Text="{Binding Fill, StringFormat=Fill: {0}}"/>
                            <TextBlock Margin="6,56,0,0" Text="{Binding Stroke, StringFormat=Stroke: {0}}"/>
                            <Border BorderBrush="Black" BorderThickness="1,0,0,0" Margin="110,0,0,0" HorizontalAlignment="Stretch" Padding="10">
                                <ContentPresenter Content="{Binding}"/>
                            </Border>
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBox Margin="352,10,10,377">
            <TextBox.Text>
                <MultiBinding StringFormat="{}Name: {0}, Dimensions: {1} x {2}">
                    <Binding Path="Children[6].Name"/>
                    <Binding Path="Children[6].Width"/>
                    <Binding Path="Children[6].Height"/>
                </MultiBinding>
            </TextBox.Text>
        </TextBox>

        <TextBox Margin="352,54,10,333">
            <TextBox.Text>
                <MultiBinding StringFormat="{}Name: {0}, Dimensions: {1} x {2}">
                    <Binding Path="[Square].Name"/>
                    <Binding Path="[Square].Width"/>
                    <Binding Path="[Square].Height"/>
                </MultiBinding>
            </TextBox.Text>
        </TextBox>
    </Grid>
</Window>

使用“同步”集合的方法是使用Composite.cs 中的索引器。我已经尝试了几种方法来以与您设置它的方式类似的方式同步集合,但都存在问题并且复杂性增加。我相信在你的情况下它不同步的原因是因为你没有发出INotifyPropertyChanged.PropertyChanged 事件的信号。

我鼓励您找到手动同步多个集合的替代解决方案,尤其是当它们具有相同数据时。让 WPF 绑定引擎为您处理事情要好得多。

【讨论】:

  • 非常好。彻底和消息灵通。最后一个警告;是否可以实现一个返回类型为 Shape 的属性,该属性在获取访问权限时将对子项进行排序并返回具有最大高度的子项?
  • 是的,您可以调用该属性ChildWithGreatestHeight,然后使用get 访问器来获取所需的孩子。我不建议在那里进行排序,因为它相当非传统。相反,只需进行搜索,这就是 LINQ 派上用场的地方。
  • 所以您认为该属性将与整个 Children 集合保持同步? -- RE: 这一切都可以在没有依赖属性的情况下完成吗,例如可能为 Shape ChildWithGreatestHeight() 等常规 CLR 属性正确实现 NotifyPropertyChanged return Children.Orderby(Shape => Shape.Height).Last()
  • 之所以保持同步是因为没有同步要做。这些属性只是访问唯一的Children 集合。我有点不清楚在这种情况下DependencyProperties 是否是必要的,但它们确实会让设计师了解情况。根据this answer 看来您可以使用INotifyPropertyChanged 作为替代方案,但我没有测试它。
  • 没问题,我很乐意提供帮助。请记住,如果您发现此答案解决了您的问题,请接受。
猜你喜欢
  • 2010-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-15
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多