2010-07-17 5 views
5

J'écris un ItemsControl personnalisé (un conteneur de document à onglets), où chaque élément (onglet) peut se retirer de l'interface utilisateur lorsque l'utilisateur le ferme. Toutefois, je ne peux pas le supprimer directement de la collection ItemsControl.Items, car les éléments peuvent être databound. Je dois donc le retirer du ItemsSource, qui peut être n'importe quoi (ICollection, DataTable, DataSourceProvider ...).WPF - Meilleure façon de supprimer un élément de ItemsSource

Dans le contexte de mon application, je sais quel sera le type réel du ItemsSource, mais je veux que ce contrôle soit plus générique pour que je puisse le réutiliser plus tard. Je cherche donc un moyen de supprimer un élément d'une source de données, sans connaître son type. Je pourrais utiliser la réflexion, mais il se sent sale ... Jusqu'à présent, la meilleure solution que je suis venu avec l'aide est 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()); 
     } 
    } 

Mais je ne suis pas vraiment satisfait, je suis sûr qu'il doit y avoir un meilleure façon. Toute suggestion serait appréciée !

Répondre

2

OK, j'ai trouvé une solution ...

  • Si le ItemsSource est databound, je soit déclencher un événement (pour une utilisation avec le code-behind) ou exécuter une commande (pour une utilisation avec un ViewModel) pour supprimer l'élément de la collection ItemsSource.

  • Si ce n'est pas DataBound, je soulever un événement pour inviter l'utilisateur à confirmer, et je retirer le récipient directement à partir 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 pratique en matière de conception veut que vous sachiez vraiment ce qu'est votre ItemsSource et que vous puissiez le supprimer directement. La liaison met alors automatiquement à jour la vue bien sûr.

Toutefois, si vous avez l'intention tout à fait sur une sorte de fonctionnalité générique pour l'enlèvement, la coulée de votre ItemsSource-ICollection ou ICollection<T> puis appeler Remove sonne comme une façon meilleure/plus fiable pour aller à l'utilisation des fonctions dynamiques de .NET.

+0

En fait, une bonne conception assurerait la 'ItemsControl' n'a pas connaissance des types dans 'ItemsSource' du tout. –

+0

Je serais d'accord si j'avais le contrôle total sur cette propriété ItemsSource ... mais je ne le fais pas. Il fait partie de WPF, est de type 'object' et peut être tout ce qui supporte l'énumération. Et mon contrôle est conçu pour accepter n'importe quoi, je ne veux pas me limiter à un type spécifique –

+1

Down-vote pourquoi ?? – Noldorin

-1

Comme vous l'avez constaté, votre ItemsControl n'a pas une connaissance intrinsèque des éléments liés - ces types sont fournis par les consommateurs de votre contrôle. Et vous ne pouvez pas modifier directement la collection, car elle peut être liée aux données.

L'astuce consiste à s'assurer que chaque élément est enveloppé par une classe personnalisée (conteneur d'éléments) de votre choix. Votre ItemsControl vous pouvez fournir cela dans la méthode GetContainerForItemOverride. À partir de là, vous pouvez définir les propriétés de votre conteneur d'éléments personnalisés auxquelles vous vous liez ensuite dans votre modèle par défaut. Par exemple, vous pouvez avoir une propriété appelée State qui change entre Docked, Floating et Closed. Votre modèle utiliserait cette propriété pour déterminer comment - et si - afficher l'élément.

Ainsi, vous ne changerez pas du tout la source de données sous-jacente. Au lieu de cela, vous allez modifier une couche spécifique au contrôle en plus des éléments de données sous-jacents qui vous donnent les informations dont vous avez besoin pour implémenter votre contrôle.

+0

Kent, merci pour votre réponse. J'ai déjà créé un conteneur personnalisé et remplacé GetContainerForItemOverride. Mais le fait est que je veux vraiment retirer l'objet de la collection sous-jacente, je ne veux pas le cacher. Peut-être que je devrais simplement déléguer l'implémentation de "close" à l'utilisateur, en gérant un événement (code-behind) ou en lier une commande (ViewModel) –

+0

@Thomas: pas de problème. Pouvez-vous expliquer pourquoi vous devez retirer l'article? Peut-être qu'une vue de collection filtrée suffirait? Ou peut-être votre élément devrait exécuter une commande lorsque l'utilisateur le ferme, et la suppression est à la hauteur du code consommateur. Il me semble que votre désir de créer un contrôle générique ne peut pas fonctionner si vous faites le retrait vous-même. –

+0

Le contrôle affiche une liste de documents ouverts (feuilles de calcul SQL dans ce cas). Il est lié à une liste de feuilles de travail exposées par un ViewModel. Lorsque je clique sur le bouton de fermeture d'un onglet (partie du modèle de TabDocumentContainerItem), le conteneur appelle CloseTab sur son parent (le TabDocumentContainer), qui supprime le document de la collection de feuille de calcul. En fait, j'ai trouvé une solution, je l'afficherai dans quelques minutes –

9

Le ItemCollection renvoyé par ItemsControl.Items ne vous permettra pas d'appeler Supprimer directement, mais il implémente IEditableCollectionView et vous permet d'appeler la méthode Remove dans cette interface.

Cela fonctionnera uniquement si la vue de collection liée à ItemsSource implémente IEditableCollectionView elle-même. La vue de collection par défaut sera pour la plupart des collections modifiables, mais pas pour les objets qui implémentent ICollection mais pas IList.

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

Réponse intéressante, je ne savais pas à propos de cette interface. Cependant je pense que je m'en tiendrai à la solution que j'ai trouvée (voir ma réponse), parce que c'est plus adéquat dans mon cas. En outre, si ItemsSource est un IEnumerable, CanRemove renvoie false, mais ViewModel peut avoir accès à la collection réelle et être en mesure de supprimer l'élément. –

+0

Une solution géniale. Vous venez de corriger un mauvais bug pour moi: http://avalondock.codeplex.com/workitem/13168 – basarat

Questions connexes