绑定到CompositeCollection内的单个元素

时间:2009-12-01 14:50:08

标签: wpf xaml mvvm treeview

我正在尝试生成一个用于在网络上浏览的服务器列表,以便生成一个如下所示的树视图:

-Local Server
 - Endpoint 1
 - Endpoint 2
-Remote
 - <Double-click to add a server...>
 - Remote Server 1
   - Endpoint 1
   - Endpoint 2
 - Remote Server 2
   - Endpoint 1
   - Endpoint 2

我的ViewModel如下所示:

...
public Server LocalServer;
public ObservableCollection<Server> RemoteServers;
...

那么,如何通过绑定到单个对象对象列表来构建xaml中的列表?我可能会以完全错误的方式思考它,但我的大脑真正希望能做的是这样的事情:

<CompositeCollection>
  <SingleElement Content="{Binding LocalServer}"> 
  <!-- ^^ something along the lines of a ContentPresenter -->
  <TreeViewItem Header="Remote">
    <TreeViewItem.ItemsSource>
      <CompositeCollection>
        <TreeViewItem Header="&lt;Click to add...&gt;" />
        <CollectionContainer Collection="{Binding RemoteServers}" />
      </CompositeCollection>
    </TreeViewItem.ItemsSource>
  </TreeViewItem>
</CompositeCollection>

我觉得必须有一个我缺少的基本元素,这使我无法在这里指定我想要的东西。那个单品有孩子。我确实尝试过使用ContentPresenter,但无论出于何种原因,它都无法扩展,即使它选择了HierarchicalDataTemplate来正确显示标题。


更新

所以现在,我已经在视图模型上公开了一个属性,该属性将单个元素包装在一个集合中,以便CollectionContainer可以绑定它。不过,我真的很想听听人们如何做到这一点的想法。这似乎非常重要。

4 个答案:

答案 0 :(得分:2)

我发布了一个与你的问题非常相似的问题:Why is CompositeCollection not Freezable?

这显然是WPF中的一个错误,信不信由你。以下是MS员工承认的帖子:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b15cbd9d-95aa-47c6-8068-7ae9f7dca88a

CompositeCollection不是freezable,但应该是。这使得将非静态元素组合成一个集合变得困难。这是许多事情的常见情况。例如,填充其他数据绑定对象的组合框顶部的“选择一个”元素会很好,但是你不能以声明方式进行。

无论如何,我很抱歉这不是一个答案,但希望它可以帮助你理解为什么这不符合你的想法。

答案 1 :(得分:1)

你不能只从树可以绑定的ViewModel中公开一个新的集合吗?

类似的东西:

public Server LocalServer;
public ObservableCollection<Server> RemoteServers;

public IEnumerable ServerTree { return new[] { LocalServer, RemoteServers } }

毕竟您的ViewModel是查看模型。它应该完全暴露视图所需的内容。

答案 2 :(得分:0)

嗯,这是我能满足您要求的最接近的。所有功能都不包含在一个TreeView中,也不是绑定到复合集合,但这可能仍然是你和我之间的秘密;)

<Window x:Class="CompositeCollectionSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:CompositeCollectionSpike">
<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="TreeView">
            <Setter Property="BorderThickness" Value="0"/>
        </Style>
        <HierarchicalDataTemplate DataType="{x:Type local:Server}"
                                  ItemsSource="{Binding EndPoints}">
            <Label Content="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </StackPanel.Resources>
    <TreeView ItemsSource="{Binding LocalServer}"/>
    <TreeViewItem DataContext="{Binding RemoteServers}"
                  Header="{Binding Description}">
        <StackPanel>
            <Button Click="Button_Click">Add Remote Server</Button>
            <TreeView ItemsSource="{Binding}"/>
        </StackPanel>
    </TreeViewItem>
</StackPanel>

using System.Collections.ObjectModel;
using System.Windows;

namespace CompositeCollectionSpike
{
    public partial class Window1 : Window
    {
        private ViewModel viewModel;
        public Window1()
        {
            InitializeComponent();
            viewModel = new ViewModel
                            {
                                LocalServer =new ServerCollection{new Server()},
                                RemoteServers =
                                    new ServerCollection("Remote Servers") {new Server(),
                                        new Server(), new Server()},
                            };
            DataContext = viewModel;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            viewModel.LaunchAddRemoteServerDialog();
        }
    }

    public class ViewModel:DependencyObject
    {
        public ServerCollection LocalServer { get; set; }
        public ServerCollection RemoteServers { get; set; }

        public void LaunchAddRemoteServerDialog()
        {}
    }

    public class ServerCollection:ObservableCollection<Server>
    {
        public ServerCollection(){}

        public ServerCollection(string description)
        {
            Description = description;
        }
        public string Description { get; set; }
    }

    public class Server
    {
        public static int EndpointCounter;
        public static int ServerCounter;
        public Server()
        {
            Name = "Server"+ ++ServerCounter;
            EndPoints=new ObservableCollection<string>();
            for (int i = 0; i < 2; i++)
            {
                EndPoints.Add("Endpoint"+ ++EndpointCounter);
            }
        }
        public string Name { get; set; }
        public ObservableCollection<string> EndPoints { get; set; }
    }
}

答案 3 :(得分:0)

最后,几年之后,我的WPF技能足以解决这个问题;)

这里有SingleElement,就像您在问题中所述。它通过继承CollectionContainer并将绑定元素放入集合中来实现。通过注册更改处理程序,我们甚至可以在绑定更改时更新CollectionContainer。对于原始CollectionProperty,我们指定一个强制处理程序来防止我们类的用户弄乱集合属性,如果你想改进保护,你可以使用自定义集合而不是ObservableCollection。作为奖励,我展示了如何通过使用占位符值使SingleElement消失,虽然从技术上来说,它更像是一个&#34; OptionalSingleElement&#34;。

public class SingleElement : CollectionContainer
{
    public static readonly object EmptyContent = new object();

    public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
        "Content", typeof(object), typeof(SingleElement), new FrameworkPropertyMetadata(EmptyContent, HandleContentChanged));

    static SingleElement()
    {
        CollectionProperty.OverrideMetadata(typeof(SingleElement), new FrameworkPropertyMetadata { CoerceValueCallback = CoerceCollection });
    }

    private static object CoerceCollection(DependencyObject d, object baseValue)
    {
        return ((SingleElement)d)._content;
    }

    private static void HandleContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var content = ((SingleElement)d)._content;

        if (e.OldValue == EmptyContent && e.NewValue != EmptyContent)
            content.Add(e.NewValue);
        else if (e.OldValue != EmptyContent && e.NewValue == EmptyContent)
            content.RemoveAt(0);
        else // (e.OldValue != EmptyContent && e.NewValue != EmptyContent)
            content[0] = e.NewValue;
    }

    private ObservableCollection<object> _content;

    public SingleElement()
    {
        _content = new ObservableCollection<object>();
        CoerceValue(CollectionProperty);
    }

    public object Content
    {
        get { return GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }
}

你可以像在问题中所说的那样使用它,除了你必须调整CompositeCollection中缺少DataContext:

<TreeView x:Name="wTree">
    <TreeView.Resources>
        <CompositeCollection x:Key="Items">
            <local:SingleElement Content="{Binding DataContext.LocalServer, Source={x:Reference wTree}}"/>
            <TreeViewItem Header="Remote">
                <TreeViewItem.ItemsSource>
                    <CompositeCollection>
                        <TreeViewItem Header="&lt;Click to add ...&gt;"/>
                        <CollectionContainer Collection="{Binding DataContext.RemoteServers, Source={x:Reference wTree}}"/>
                    </CompositeCollection>
                </TreeViewItem.ItemsSource>
            </TreeViewItem>
        </CompositeCollection>
    </TreeView.Resources>
    <TreeView.ItemsSource>
        <StaticResource ResourceKey="Items"/>
    </TreeView.ItemsSource>
</TreeView>