Привязка к одному элементу внутри CompositeCollection - PullRequest
6 голосов
/ 01 декабря 2009

Я пытаюсь создать список серверов для просмотра в сети так, чтобы он отображал древовидную структуру, которая выглядит следующим образом:

-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 ]

2 голосов
/ 05 декабря 2009

Я опубликовал вопрос, очень похожий на ваш, относительно CompositeCollections: Почему CompositeCollection не может быть заморожен?

Это явно ошибка в WPF, хотите верьте, хотите нет. Вот сообщение сотрудника MS, принимающего столько же: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b15cbd9d-95aa-47c6-8068-7ae9f7dca88a

CompositeCollection не замораживается, но должно быть. Это затрудняет объединение нестатических элементов в одну коллекцию. Это общий сценарий для многих вещей. Например, элемент «Выбрать один» в верхней части поля со списком, заполненный другими объектами с привязкой к данным, был бы хорош, но вы не можете сделать это декларативно.

В любом случае, извините, это не ответ, но, надеюсь, это поможет вам понять, почему это не работает так, как вы думали.

1 голос
/ 10 декабря 2009

Разве вы не можете просто представить новую коллекцию из вашей ViewModel, с которой дерево может связываться?

Что-то вроде:

public Server LocalServer;
public ObservableCollection<Server> RemoteServers;

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

Ведь ваша ViewModel - это модель View . Это должно быть именно то, что нужно для представления.

0 голосов
/ 22 августа 2014

Наконец, через несколько лет мои навыки WPF достаточно хороши, чтобы решить эту проблему;)

Вот SingleElement, как вы обрисовали в общих чертах в вашем вопросе. Это реализуется путем создания подкласса CollectionContainer и помещения связанного элемента в коллекцию. Зарегистрировав обработчик изменений, мы можем даже обновить CollectionContainer при изменении привязки. Для оригинального CollectionProperty мы указываем обработчик приведения, чтобы пользователи нашего класса не связывались со свойством collection. Если вы хотите улучшить защиту, вы можете использовать пользовательскую коллекцию вместо 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); }
    }
}

Вы можете использовать его в точности так, как вы указали в своем вопросе, за исключением того, что вам необходимо отрегулировать отсутствие DataContext в CompositeCollection:

<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>
0 голосов
/ 06 декабря 2009

Ну, это самое близкое, что я могу подойти к вашим требованиям. Вся функциональность не содержится в одном 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; }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...