Pourquoi le TextBlock n’est-il pas l’OriginalSource sur l’événement routé?

Je montre un menu contextuel pour les éléments dans un ListView . Le menu contextuel est attaché au TextBlock de ListView comme suit.

         

Le menu contextuel s’affiche correctement et RoutedUIEvent est également déclenché. Le problème est que, dans le rappel exécuté, ExecutedRoutedEventArgs.OriginalSource est un ListViewItem et non le TextBlock.

J’ai essayé de définir la propriété IsHitTestVisible ainsi que l’ BackgroundBackground (voir ci-dessous), car MSDN indique que OriginalSource est déterminé par le test de réussite.

Notez que j’utilise un GridView en tant que vue dans ListView. C’est la raison pour laquelle je souhaite accéder au TextBlock (pour obtenir l’index de la colonne)

Fenêtre principale

                             

MainWindow.xaml.cs

 using System.Diagnostics; using System.Windows; using System.Windows.Input; namespace WpfApp1 { public class Data { public ssortingng Member1 { get; set; } } public partial class MainWindow : Window { public static RoutedCommand Test = new RoutedCommand(); public MainWindow() { InitializeComponent(); CommandBindings.Add(new CommandBinding(Test, (s, e) => { Debugger.Break(); })); } } } 

L’un des aspects frustrants de votre question, ou plutôt… de WPF en ce qui concerne le scénario présenté dans votre question, est que WPF semble mal conçu pour ce scénario particulier. En particulier:

  1. Les propriétés DisplayMemberBinding et CellTemplate ne fonctionnent pas ensemble. C’est-à-dire que vous pouvez spécifier l’un ou l’autre, mais pas les deux. Si vous spécifiez DisplayMemberBinding , il est prioritaire et n’offre aucune personnalisation de la mise en forme de l’affichage, si ce n’est d’appliquer des parameters dans un style pour le TextBlock utilisé implicitement.
  2. DisplayMemberBinding ne participe pas au comportement habituel de modèle de données implicite trouvé ailleurs dans WPF. En d’autres TextBlock , lorsque vous utilisez cette propriété, le contrôle utilise explicitement TextBlock pour afficher les données, en liant la valeur à la propriété TextBlock.Text . Donc, vous feriez bien mieux de vous lier à une valeur de ssortingng ; WPF ne cherchera aucun autre modèle de données pour vous, si vous essayez d’utiliser un type différent.

Cependant, malgré ces frustrations, j’ai pu trouver deux voies différentes pour répondre à votre question. Un chemin se concentre directement sur votre demande exacte, tandis que l’autre fait un pas en arrière et (j’espère) aborde le problème plus vaste que vous essayez de résoudre.

Le deuxième chemin résulte en un code plus simple que le premier, et IMHO est meilleur pour cette raison ainsi que parce qu’il n’implique pas de sortingpoter l’arbre visuel ni les détails d’implémentation pour savoir où les différents éléments de cet arbre sont relatifs. Donc, je vais montrer cela en premier (c’est-à-dire dans un sens compliqué, c’est en fait le “premier” chemin, pas le “second” :)).

Tout d’abord, vous aurez besoin d’un petit cours d’assistance:

 class GridColumnDisplayData { public object DisplayValue { get; set; } public ssortingng ColumnProperty { get; set; } } 

Ensuite, vous aurez besoin d’un convertisseur pour produire des instances de cette classe pour vos cellules de grid:

 class GridColumnDisplayDataConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return new GridColumnDisplayData { DisplayValue = value, ColumnProperty = (ssortingng)parameter }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

Le XAML ressemble à ceci:

                                  

Cela permet de mapper les objects de Data sur leurs valeurs de propriété individuelles, ainsi que sur le nom de ces valeurs de propriété. Ainsi, lorsque le modèle de données est appliqué, MenuItem peut lier le CommandParameter à ce nom de valeur de propriété, de sorte qu’il soit accessible dans le gestionnaire.

Notez que plutôt que d’utiliser DisplayMemberBinding , cela utilise CellTemplate et déplace la liaison de membre d’affichage dans le Content de ContentPresenter dans le modèle. Cela est nécessaire en raison de la gêne susmentionnée; sans cela, il est impossible d’appliquer un modèle de données défini par l’utilisateur à l’object GridColumnDisplayData défini par l’utilisateur, afin d’afficher correctement sa propriété DisplayValue .

Il y a un peu de redondance ici, car vous devez vous lier au chemin de la propriété, ainsi que spécifier le nom de la propriété en tant que paramètre de convertisseur. Et malheureusement, ce dernier est sujet à des erreurs typographiques, car rien au moment de la compilation ou de l’exécution ne permet de déceler une disparité. Je suppose que dans une construction Debug, vous pouvez append de la reflection pour récupérer la valeur de la propriété à l’aide du nom de la propriété indiqué dans le paramètre converter et vous assurer que cette propriété est identique à celle indiquée dans le chemin de liaison.

Dans votre question et vos commentaires, vous avez exprimé le souhait de remonter dans l’arbre pour trouver plus directement le nom de la propriété. Par exemple, dans le paramètre de commande, passez la référence à l’object TextBlock , puis utilisez-la pour revenir au nom de la propriété liée. En un sens, cela est plus fiable, car cela va directement au nom de propriété lié. Par contre, il me semble que dépendre de la structure exacte de l’arbre visuel et des liaisons qui s’y trouvent est plus fragile. À long terme, il semble probable que les coûts de maintenance seront plus élevés.

Cela dit, j’ai trouvé un moyen d’atteindre cet objective. Tout d’abord, comme dans l’autre exemple, vous aurez besoin d’une classe d’assistance pour stocker les données:

 public class GridCellHelper { public object DisplayValue { get; set; } public UIElement UIElement { get; set; } } 

Et de même, un convertisseur (cette fois, IMultiValueConverter ) pour créer des instances de cette classe pour chaque cellule:

 class GridCellHelperConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { return new GridCellHelper { DisplayValue = values[0], UIElement = (UIElement)values[1] }; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

Et enfin, le XAML:

                                        

Dans cette version, vous pouvez voir que le modèle de cellule est utilisé pour configurer une valeur DataContext contenant à la fois la valeur de la propriété liée et la référence au TextBlock . Ces valeurs sont ensuite décompressées par les éléments individuels du modèle, à savoir la propriété TextBlock.Text et la propriété MenuItem.CommandParameter .

L’inconvénient évident est que, comme le membre d’affichage doit être lié à l’ intérieur du modèle de cellule à déclarer, le code doit être répété pour chaque colonne. Je n’ai pas trouvé de moyen de réutiliser le modèle, en lui passant le nom de la propriété. (L’autre version a un problème similaire, mais c’est une implémentation beaucoup plus simple, donc le copier / coller ne semble pas si onéreux).

Mais il envoie de manière fiable la référence TextBlock à votre gestionnaire de commandes, ce que vous avez demandé. Donc, il y a ça. 🙂