2010-07-17 20 views
5

Escribo un ItemsControl personalizado (un contenedor de documentos con pestañas), donde cada elemento (pestaña) puede eliminarse de la UI cuando el usuario lo cierra. Sin embargo, no puedo eliminarlo directamente de la colección ItemsControl.Items, porque los elementos pueden estar enlazados a datos. Así que tengo que eliminarlo de ItemsSource, que puede ser cualquier cosa (ICollection, DataTable, DataSourceProvider ...).WPF: la mejor manera de eliminar un elemento de ItemsSource

En el contexto de mi aplicación, sé cuál será el tipo real de ItemsSource, pero quiero que ese control sea más genérico para poder reutilizarlo más adelante.

Así que estoy buscando una manera de eliminar un elemento de una fuente de datos, sin saber su tipo. Podría utilizar la reflexión, pero se siente sucia ... Hasta ahora la mejor solución que se me ocurrió es usar dynamic:

internal void CloseTab(TabDocumentContainerItem tabDocumentContainerItem) 
    { 
     // TODO prompt user for confirmation (CancelEventHandler ?) 

     var item = ItemContainerGenerator.ItemFromContainer(tabDocumentContainerItem); 

     // TODO find a better way... 
     try 
     { 
      dynamic items = ItemsSource; 
      dynamic it = item; 
      items.Remove(it); 
     } 
     catch(RuntimeBinderException ex) 
     { 
      Trace.TraceError("Oops... " + ex.ToString()); 
     } 
    } 

Pero no estoy muy contenta con ella, estoy seguro de que debe haber una mejor manera. Cualquier sugerencia sera apreciada !

Respuesta

2

OK, encontré una solución ...

  • Si el ItemsSource es enlace de datos, que sea provocar un evento (para su uso con código subyacente) o invocar un comando (para uso con un modelo de vista) para eliminar el elemento de la colección ItemsSource.

  • Si no está enlazado a datos, yo levanto un evento para pedir al usuario para su confirmación, y retirar el contenedor directamente desde Items

    public static readonly DependencyProperty CloseTabCommandProperty = 
        DependencyProperty.Register(
         "CloseTabCommand", 
         typeof(ICommand), 
         typeof(TabDocumentContainer), 
         new UIPropertyMetadata(null)); 
    
    public ICommand CloseTabCommand 
    { 
        get { return (ICommand)GetValue(CloseTabCommandProperty); } 
        set { SetValue(CloseTabCommandProperty, value); } 
    } 
    
    public event EventHandler<RequestCloseTabEventArgs> RequestCloseTab; 
    public event EventHandler<TabClosingEventArgs> TabClosing; 
    
    internal void CloseTab(TabDocumentContainerItem tabDocumentContainerItem) 
    { 
        if (ItemsSource != null) // Databound 
        { 
         object item = ItemContainerGenerator.ItemFromContainer(tabDocumentContainerItem); 
         if (item == null || item == DependencyProperty.UnsetValue) 
         { 
          return; 
         } 
         if (RequestCloseTab != null) 
         { 
          var args = new RequestCloseTabEventArgs(item); 
          RequestCloseTab(this, args); 
         } 
         else if (CloseTabCommand != null) 
         { 
          if (CloseTabCommand.CanExecute(item)) 
          { 
           CloseTabCommand.Execute(item); 
          } 
         } 
        } 
        else // Not databound 
        { 
         if (TabClosing != null) 
         { 
          var args = new TabClosingEventArgs(tabDocumentContainerItem); 
          TabClosing(this, args); 
          if (args.Cancel) 
           return; 
         } 
         Items.Remove(tabDocumentContainerItem); 
        } 
    } 
    
-3

La práctica del diseño dicta que deberías saber realmente cuál es tu ItemsSource y ser capaz de eliminarlo directamente de allí. El enlace luego actualiza automáticamente la vista, por supuesto.

Sin embargo, si está absolutamente decidido a algún tipo de funcionalidad genérica para la eliminación, la emisión de su ItemsSource-ICollection o ICollection<T> y luego llamar Remove suena como una manera mejor/más fiable para ir de utilizar las características dinámicas de .NET.

+0

En realidad, el buen diseño aseguraría la 'ItemsControl' no tiene conocimiento de los tipos en 'ItemsSource' en absoluto. –

+0

Estoy de acuerdo si tuviera control total sobre esa propiedad ItemsSource ... pero no es así. Es parte de WPF, es de tipo 'objeto', y puede ser cualquier cosa que admita la enumeración. Y mi control está diseñado para aceptar cualquier cosa, no quiero limitarme a un tipo específico –

+1

¿Por qué? – Noldorin

-1

Como has encontrado, tu ItemsControl no tiene un conocimiento intrínseco de los artículos vinculados, esos tipos son proporcionados por los consumidores de tu control. Y no puede modificar la colección directamente porque puede estar unida a datos.

El truco es asegurarse de que cada elemento esté envuelto por una clase personalizada (contenedor de elementos) de su elección. Su ItemsControl puede proporcionar esto en el método GetContainerForItemOverride.

A partir de ahí, puede definir las propiedades en el contenedor de elementos personalizados al que se enlazará en su plantilla predeterminada. Por ejemplo, podría tener una propiedad llamada State que cambie entre Docked, Floating y Closed. Su plantilla usaría esta propiedad para determinar cómo, y si, mostrar el artículo.

Por lo que en realidad no va a cambiar la fuente de datos subyacente. En su lugar, cambiará una capa específica del control sobre los elementos de datos subyacentes que le brindan la información que necesita para implementar su control.

+0

Kent, gracias por su respuesta. Ya he creado un contenedor personalizado y reemplazado GetContainerForItemOverride. Pero el hecho es que realmente quiero eliminar el elemento de la colección subyacente, no quiero simplemente ocultarlo. Quizás debería simplemente delegar la implementación de "cerrar" al usuario, manejando un evento (código subyacente) o vinculando un comando (ViewModel) –

+0

@Thomas: no hay problema. ¿Puedes explicar por qué es necesario eliminar el artículo? Tal vez una vista de colección filtrada sería suficiente? O tal vez su artículo debería ejecutar un comando cuando el usuario lo cierra y la eliminación depende del código de consumo. Me parece que su deseo de crear un control genérico no puede funcionar si lo hace usted mismo. –

+0

El control muestra una lista de documentos abiertos (hojas de cálculo SQL en ese caso). Está vinculado a una lista de hojas de trabajo expuestas por un ViewModel. Cuando hago clic en el botón Cerrar en una pestaña (parte de la plantilla de TabDocumentContainerItem), el contenedor llama a CloseTab en su elemento principal (el TabDocumentContainer), que elimina el documento de la colección de la hoja de trabajo. En realidad encontré una solución, la publicaré en unos pocos minutos –

9

El ItemCollection devuelto por ItemsControl.Items no le permitirá llamar directamente a Remove, pero implementa IEditableCollectionView y le permite llamar al método Remove en esa interfaz.

Esto solo funcionará si la vista de colección vinculada a ItemsSource implementa . La vista de colección predeterminada será para la mayoría de las colecciones mutables, aunque no para los objetos que implementan ICollection pero no IList.

IEditableCollectionView items = tabControl.Items; //Cast to interface 
if (items.CanRemove) 
{ 
    items.Remove(tabControl.SelectedItem); 
} 
+1

Interesante respuesta, no sabía sobre esa interfaz. Sin embargo, creo que me atengo a la solución que encontré (ver mi respuesta), porque es más adecuada en mi caso. Además, si ItemsSource es un IEnumerable, CanRemove devolverá false, pero ViewModel podría tener acceso a la colección real y poder eliminar el elemento. –

+0

Awesome solution bro. Me acabas de arreglar un error grave: http://avalondock.codeplex.com/workitem/13168 – basarat

Cuestiones relacionadas