我正在尝试生成一个用于在网络上浏览的服务器列表,以便生成一个如下所示的树视图:
-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="<Click to add...>" />
<CollectionContainer Collection="{Binding RemoteServers}" />
</CompositeCollection>
</TreeViewItem.ItemsSource>
</TreeViewItem>
</CompositeCollection>
我觉得必须有一个我缺少的基本元素,这使我无法在这里指定我想要的东西。那个单品有孩子。我确实尝试过使用ContentPresenter,但无论出于何种原因,它都无法扩展,即使它选择了HierarchicalDataTemplate
来正确显示标题。
更新
所以现在,我已经在视图模型上公开了一个属性,该属性将单个元素包装在一个集合中,以便CollectionContainer
可以绑定它。不过,我真的很想听听人们如何做到这一点的想法。这似乎非常重要。
答案 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="<Click to add ...>"/>
<CollectionContainer Collection="{Binding DataContext.RemoteServers, Source={x:Reference wTree}}"/>
</CompositeCollection>
</TreeViewItem.ItemsSource>
</TreeViewItem>
</CompositeCollection>
</TreeView.Resources>
<TreeView.ItemsSource>
<StaticResource ResourceKey="Items"/>
</TreeView.ItemsSource>
</TreeView>