Comment sont construites les ressources dynamics et leur utilisation dans les menus contextuels

Les ressources dynamics sont-elles vraiment dynamics? Si je définis un DynamicResource, je réalise qu’une expression est créée (où?) N’est pas traduite en une ressource avant l’exécution, cependant, ce que je ne comprends pas, c’est si cette dynamic, une fois construite, est maintenant “statique”

Par exemple, si je crée un menu contextuel via une ressource dynamic, les menus créés au moment de l’exécution lors de l’access sont-ils alors statiques, même s’ils sont liés?

Si oui, comment puis-je créer un menu contextuel dynamic en XAML?

C’est un sujet très complexe car il existe de nombreux types de dynamisme au sein de WPF. Je vais commencer par un exemple simple pour vous aider à comprendre certains concepts de base dont vous avez besoin, puis une explication sur les différentes manières de mettre à jour et / ou remplacer dynamicment un ContextMenu et sur l’intégration de DynamicResource dans l’image.

Exemple initial: mise à jour dynamic de ContextMenu référencé via StaticResource

Disons que vous avez ce qui suit:

           ...   

** Notez l’utilisation de StaticResource pour l’instant.

Ce XAML va:

  • Construisez un object ContextMenu avec trois MenuItems et ajoutez-le à Window.Resources
  • Construire un object Ellipse avec une référence au ContextMenu
  • Construire un object TextBox avec une référence au ContextMenu

Comme Ellipse et TextBox font tous deux référence au même ContextMenu, la mise à jour du ContextMenu modifiera les options disponibles pour chacun. Par exemple, ce qui suit va append “Carottes” au menu quand un bouton est cliqué.

 public void Button_Click(object sender, EventArgs e) { var menu = (ContextMenu)Resources["Vegetables"]; menu.Items.Add(new MenuItem { Header = "Carrots" }); } 

En ce sens, chaque ContextMenu est dynamic: ses éléments peuvent être modifiés à tout moment et les modifications prendront effet immédiatement. Cela est vrai même lorsque le ContextMenu est réellement ouvert (déroulé) à l’écran.

ContextMenu dynamic mis à jour via la liaison de données

Une autre façon dont un seul object ContextMenu est dynamic est qu’il répond à la liaison de données. Au lieu de définir des éléments de menu individuels, vous pouvez vous lier à une collection, par exemple:

    

Cela suppose que VegetableList est déclaré en tant que ObservableCollection ou tout autre type implémentant l’interface INotifyCollectionChanged. Toute modification apscope à la collection mettra instantanément à jour le ContextMenu, même s’il est ouvert. Par exemple:

 public void Button_Click(object sender, EventArgs e) { VegetableList.Add("Carrots"); } 

Notez que ce type de mise à jour de la collection ne doit pas nécessairement être effectué dans le code: vous pouvez également lier la liste de légumes à un ListView, un DataGrid, etc. afin que des modifications puissent être apscopes par l’utilisateur final. Ces modifications apparaîtront également dans votre ContextMenu.

Basculement des menus contextuels à l’aide du code

Vous pouvez également remplacer le ContextMenu d’un élément par un ContextMenu complètement différent. Par exemple:

              ...   

Le menu peut être remplacé dans le code comme ceci:

 public void Button_Click(object sender, EventArgs e) { Oval.ContextMenu = (ContextMenu)Resources.Find("Fruits"); } 

Notez qu’au lieu de modifier le ContextMenu existant, nous basculons vers un ContextMenu complètement différent. Dans cette situation, les deux ContextMenus sont construits immédiatement lors de la construction initiale de la fenêtre, mais le menu Fruits n’est utilisé qu’après son basculement.

Si vous voulez éviter de construire le menu Fruits jusqu’à ce que cela soit nécessaire, vous pouvez le construire dans le gestionnaire Button_Click au lieu de le faire dans XAML:

 public void Button_Click(object sender, EventArgs e) { Oval.ContextMenu = new ContextMenu { ItemsSource = new[] { "Apples", "Bananas" } }; } 

Dans cet exemple, chaque fois que vous cliquez sur le bouton, un nouveau ContextMenu sera construit et atsortingbué à l’ovale. Tout menu contextuel défini dans Window.Resources existe toujours mais n’est pas utilisé (à moins qu’un autre contrôle l’utilise).

Basculement des menus contextuels à l’aide de DynamicResource

L’utilisation de DynamicResource vous permet de basculer entre ContextMenus sans lui atsortingbuer explicitement du code. Par exemple:

          ...   

Étant donné que XAML utilise DynamicResource au lieu de StaticResource, la modification du dictionnaire met à jour la propriété ContextMenu de Ellipse. Par exemple:

 public void Button_Click(object sender, EventArgs e) { Resources["Vegetables"] = new ContextMenu { ItemsSource = new[] {"Zucchini", "Tomatoes"} }; } 

Le concept clé ici est que DynamicResource vs StaticResource ne contrôle que lorsque le dictionnaire est recherché. Si StaticResource est utilisé dans l’exemple ci-dessus, l’atsortingbution à Resources["Vegetables"] ne mettra pas à jour la propriété ContextMenu de Ellipse.

Par contre, si vous mettez à jour le ContextMenu lui-même (en modifiant sa collection Items ou via la liaison de données), peu importe que vous utilisiez DynamicResource ou StaticResource: chaque fois que vous modifiez le ContextMenu, il est immédiatement visible.

Mise à jour de différents éléments ContextMenu à l’aide de la liaison de données

Le meilleur moyen de mettre à jour un ContextMenu en fonction des propriétés de l’élément sur lequel vous avez fait un clic droit est d’utiliser la liaison de données:

   ...  

Ainsi, l’élément de menu “Supprimer” sera automatiquement grisé, à moins que son indicateur IsDeletable ne soit défini. Aucun code n’est nécessaire (ni même souhaitable) dans ce cas.

Si vous souhaitez masquer l’élément au lieu de le griser, définissez Visibility au lieu de IsEnabled:

  

Si vous souhaitez append / supprimer des éléments d’un ContextMenu en fonction de vos données, vous pouvez effectuer une liaison à l’aide d’une CompositeCollection. La syntaxe est un peu plus complexe, mais elle rest assez simple:

              

En supposant que “MyChoicesList” soit un ObservableCollection (ou toute autre classe qui implémente INotifyCollectionChanged), les éléments ajoutés / supprimés / mis à jour dans cette collection seront immédiatement visibles dans le ContextMenu.

Mise à jour des éléments individuels de ContextMenu sans liaison de données

Dans la mesure du possible, vous devez contrôler vos éléments ContextMenu à l’aide de la liaison de données . Ils fonctionnent très bien, sont presque infaillibles et simplifient grandement votre code. Ce n’est que si la liaison de données ne fonctionne pas qu’il est logique d’utiliser du code pour mettre à jour vos éléments de menu. Dans ce cas, vous pouvez créer votre ContextMenu en gérant l’événement ContextMenu.Opened et en effectuant des mises à jour dans cet événement. Par exemple:

     

Avec ce code:

 public void Vegetables_Opened(object sender, RoutedEventArgs e) { var menu = (ContextMenu)sender; var data = (MyDataClass)menu.DataContext var oldCarrots = ( from item in menu.Items where (ssortingng)item.Header=="Carrots" select item ).FirstOrDefault(); if(oldCarrots!=null) menu.Items.Remove(oldCarrots); if(ComplexCalculationOnDataItem(data) && UnrelatedCondition()) menu.Items.Add(new MenuItem { Header = "Carrots" }); } 

Sinon, ce code pourrait simplement changer menu.ItemsSource si vous utilisiez la liaison de données.

Basculement des menus contextuels à l’aide de déclencheurs

Une autre technique couramment utilisée pour mettre à jour ContextMenus consiste à utiliser un déclencheur ou un DataTrigger pour basculer entre un menu contextuel par défaut et un menu contextuel personnalisé en fonction de la condition de déclenchement. Cela peut gérer les situations dans lesquelles vous souhaitez utiliser la liaison de données mais que vous devez remplacer le menu dans son ensemble plutôt que d’en mettre à jour certaines parties.

Voici une illustration de ce à quoi cela ressemble:

    ...   ...   ...  ...       

Dans ce scénario, il est toujours possible d’utiliser la liaison de données pour contrôler des éléments individuels à la fois dans NormalMenu et AlternateMenu.

Libérer les ressources ContextMenu lorsque le menu est fermé

Si les ressources utilisées dans un ContextMenu sont chères à conserver en RAM, vous pouvez les libérer. Si vous utilisez la liaison de données, cela est susceptible de se produire automatiquement, car le DataContext est supprimé à la fermeture du menu. Si vous utilisez du code à la place, vous devrez peut-être capturer l’événement Closed dans le ContextMenu pour libérer tout ce que vous avez créé en réponse à l’événement Opened.

Construction retardée de ContextMenu à partir de XAML

Si vous souhaitez coder en XAML un menu contextuel très complexe que vous ne souhaitez pas charger, deux techniques de base sont disponibles:

  1. Placez-le dans un ResourceDictionary séparé. Si nécessaire, chargez ce ResourceDictionary et ajoutez-le à MergedDictionaries. Tant que vous avez utilisé DynamicResource, la valeur fusionnée sera récupérée.

  2. Placez-le dans un ControlTemplate ou un DataTemplate. En réalité, le menu ne sera pas instancié avant la première utilisation du modèle.

Cependant, aucune de ces techniques ne provoquera le chargement lorsque le menu contextuel est ouvert – uniquement lorsque le modèle contenant est instancié ou lorsque le dictionnaire est fusionné. Pour ce faire, vous devez utiliser un ContextMenu avec un ItemsSource vide, puis atsortingbuer le ItemsSource à l’événement Opened. Cependant, la valeur du ItemsSource peut être chargée à partir d’un ResourceDictionary dans un fichier séparé:

     ... complex content here ...   

avec ce code dans l’événement Opened:

 var dict = (ResourceDictionary)Application.LoadComponent(...); menu.ItemsSource = dict["ComplexMenuContents"]; 

et ce code dans l’événement fermé:

 menu.ItemsSource = null; 

En fait, si vous n’avez qu’un seul x: Array, vous pouvez également ignorer le ResourceDictionary. Si l’élément le plus externe de votre XAML est x: Array, le code de l’événement Opened est simplement:

 menu.ItemsSource = Application.LoadComponent(....) 

Résumé des concepts critiques

DynamicResource est utilisé uniquement pour changer les valeurs en fonction des dictionnaires de ressources chargés et de leur contenu: Lors de la mise à jour du contenu des dictionnaires, DynamicResource met automatiquement à jour les propriétés. StaticResource ne les lit que lorsque le XAML est chargé.

Peu importe si DynamicResource ou StaticResource est utilisé, le ContextMenu est créé lorsque le dictionnaire de ressources est chargé et non lorsque le menu est ouvert.

Les menus contextuels sont très dynamics en ce sens que vous pouvez les manipuler à l’aide d’une liaison de données ou d’un code et que les modifications prennent effet immédiatement.

Dans la plupart des cas, vous devez mettre à jour votre ContextMenu en utilisant des liaisons de données et non en code.

Le remplacement complet des menus peut être effectué avec du code, des déclencheurs ou DynamicResource.

Si le contenu ne doit être chargé dans la RAM que lorsque le menu est ouvert, il peut être chargé à partir d’un fichier séparé dans l’événement Opened et effacé lors de l’événement Closed.