2009-12-01 7 views
7

Estoy tratando de producir una lista de servidores para explorar en una red tal que produce una vista de árbol que se parece a esto:La unión a un solo elemento dentro de un 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 

Mi modelo de vista es el siguiente:

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

Entonces, ¿cómo hace uno para la construcción de la lista en XAML con una unión a un único objeto y una lista de objetos? Yo podría estar pensando en ello por completo el camino equivocado, pero lo que mi cerebro realmente quiere ser capaz de hacer es algo como esto:

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

Siento que debe haber un elemento fundamental que me falta que me mantiene de poder especificar lo que quiero aquí. Ese solo artículo tiene hijos. Intenté usar un ContentPresenter, pero por alguna razón, no era expansible aunque recogió el HierarchicalDataTemplate para mostrar el título correctamente.


actualización

Así que por ahora, he descubierto una propiedad en el modelo de vista que envuelve el elemento individual en una colección de modo que un CollectionContainer puede unirse a ella. Sin embargo, me gustaría escuchar las ideas de la gente sobre cómo hacer esto. Parece terriblemente fundamental.

+1

Antes de encontrar esta cuestión de forma, que estaba tratando de resolver esto también, y terminé haciendo lo mismo .. agregué una propiedad de colección en mi modelo de vista. –

Respuesta

2

He publicado una pregunta muy similar a la suya con respecto CompositeCollections: Why is CompositeCollection not Freezable?

Esto es al parecer un error en WPF, aunque no lo crean. Aquí hay una publicación de un empleado de MS que admite tanto: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b15cbd9d-95aa-47c6-8068-7ae9f7dca88a

La CompositeCollection no es freezable, pero debería ser. Esto dificulta la combinación de elementos no estáticos en una sola colección. Es un escenario común para muchas cosas. Por ejemplo, un elemento "Seleccionar uno" en la parte superior de un cuadro combinado lleno de otros objetos de datos sería agradable, pero no puede hacerlo declarativamente.

De todos modos, siento que esta no es una respuesta, pero espero que te ayude a ver por qué esto no está funcionando como creías que debería.

+2

Ese no es realmente el problema que estoy tratando de resolver. Para la situación que está describiendo, generalmente uso un CollectionViewSource para alojar los elementos dinámicos. Ese CollectionViewSource se sienta muy bien en un CollectionContainer junto a los elementos estáticos y todos juegan bien juntos. - La pregunta que busqué aquí es cómo vincular dinámicamente un único elemento del modelo de vista directamente en CompositeCollection. Definitivamente tienes mi firma para hacer CompositeCollection freezable, sin embargo. – MojoFilter

+0

@MojoFilter: ¿Cómo puede un 'CollectionContainer' contener un' CollectionViewSource'? No parece posible a través de la propiedad 'Colección'. –

+0

@ O.R.Mapper: Así es exactamente como lo haces. En la mayoría de los casos, utilizará un enlace a 'CollectionViewSource' como un recurso estático para el valor de la propiedad' Collection'. – MojoFilter

0

Bueno, esto es lo más cerca que puedo cumplir con sus requisitos. Toda la funcionalidad no está contenida dentro de un TreeView, ni está ligada a una compositecollection, pero que puede seguir siendo un secreto entre tú y yo;)

<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

¿No puedes exponer una nueva colección de tu ViewModel a la que el árbol se puede vincular?

Algo así como:

public Server LocalServer; 
public ObservableCollection<Server> RemoteServers; 

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

Después de todo su modelo de vista es un Ver Modelo. Debería exponer exactamente lo que necesita la vista.

+0

Podría, pero la vista necesita inyectar esta idea de interfaz de usuario de un elemento para hacer clic dentro del árbol para agregar nuevos nodos remotos. Eso es en gran medida un concepto de visualización en lugar de un concepto de modelo de vista y para mantener las pruebas unitarias viables y simples, realmente debe separarse de esa manera. Envolver el servidor local en una colección realmente se resuelve solo, pero realmente me gustaría ver una idea de cómo vincularlo a ese único elemento. – MojoFilter

0

Finalmente, justo después de algunos años, mis conocimientos de WPF son lo suficientemente buenos para resolver éste;)

He aquí una SingleElement como que se indica en su pregunta. Se implementa subclasificando un CollectionContainer y colocando el elemento enlazado dentro de la colección. Al registrar un controlador de cambios, incluso podemos actualizar CollectionContainer cuando cambia el enlace. Para CollectionProperty original, especificamos un controlador de coerción para evitar que los usuarios de nuestra clase entren en problemas con la propiedad de la colección. Si desea mejorar la protección, puede usar una colección personalizada en lugar de una ObservableCollection. Como una bonificación, muestro cómo hacer que SingleElement desaparezca utilizando un valor de marcador de posición, aunque técnicamente sería más un "elemento opcional opcional".

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

Se puede utilizar exactamente igual que usted declaró que en su pregunta, excepto que usted tiene que ajustar a la falta de un DataContext en el 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> 
Cuestiones relacionadas