2009-12-01 54 views
7

我试图生产服务器的列表,在网络上浏览,使得它产生一个树状图,看起来像这样:绑定到一个单一的元素的CompositeCollection内

-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 

我的视图模型看起来是这样的:

... 
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可以绑定到它的视图模型的属性。不过,我真的很想听到大家关于如何做到这一点的想法。这似乎非常重要。

+1

之前发现这太问题,我试图弄清楚这一点,并最终做同样的事情..在我的视图模型中添加了一个集合属性。 – 2010-04-14 21:55:48

回答

2

我张贴关于CompositeCollections非常类似你这样的问题:Why is CompositeCollection not Freezable?

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

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

无论如何,我很抱歉,这不是一个答案,但希望它可以帮助你明白为什么这不是你认为它应该如何工作。

+2

这实际上不是我想要解决的问题。对于你描述的情况,我通常使用CollectionViewSource来承载动态元素。然后,CollectionViewSource沿着静态元素在CollectionContainer中很好地放置,并且每个人都可以很好地一起玩。 - 我后面的问题是如何动态地将视图模型中的单个元素绑定到CompositeCollection中。不过,您绝对可以获得我的签名以使CompositeCollection可以冻结。 – MojoFilter 2009-12-06 04:09:34

+0

@MojoFilter:一个'CollectionContainer'如何包含'CollectionViewSource'?通过'Collection'属性似乎不可能。 – 2013-10-31 14:20:05

+0

@ O.R.Mapper:这正是你如何做到的。在大多数情况下,您可以使用对“CollectionViewSource”的绑定作为“Collection”属性值的静态资源。 – MojoFilter 2013-10-31 18:19:20

0

嗯,这是最接近我能达到你的要求。所有的功能不包含一个TreeView控件内,也不是绑定到compositecollection,但可以保持你和我之间的秘密;)

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

你就不能暴露树可以绑定到ViewModel的新集合?

喜欢的东西:

public Server LocalServer; 
public ObservableCollection<Server> RemoteServers; 

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

所有视图模型之后是查看模型。它应该正确地揭示视图所需要的东西。

+0

我可以,但是视图需要注入一个项目的用户界面想法,以在树本身内点击以添加新的远程节点。这不仅仅是一个视图概念,而是一个视图模型概念,为了保持单元测试的可行性和简单性,它确实需要以这种方式分离。将集合中的本地服务器包装起来真的很好解决了,但我真的很想看到如何绑定到单个元素的想法。 – MojoFilter 2009-12-10 14:29:00

0

最后,只是几年后,我WPF技能是不够好,解决这一;)

这里有一个SingleElement就像你在你的问题概括。它通过子类化CollectionContainer并将绑定元素放入集合中来实现。通过注册更改处理程序,我们甚至可以在绑定更改时更新CollectionContainer。对于原始CollectionProperty,我们指定一个强制处理程序来防止我们的类的用户混淆收集属性,如果您想要改进保护,您可以使用自定义集合而不是ObservableCollection。作为奖励,我展示了如何使用占位符值使SingleElement消失,尽管从技术上讲它更像是一个“OptionalSingleElement”。

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> 
相关问题