IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Windows Presentation Foundation Discussion :

[MVVM] Utilisation des InputBindings


Sujet :

Windows Presentation Foundation

  1. #1
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut [MVVM] Utilisation des InputBindings
    Salut,

    Comme vous l'avez peut-être remarqué, la propriété Command d'un InputBinding n'est pas une DependencyProperty, si bien qu'on ne peut pas binder un InputGesture sur une commande du ViewModel. J'ai pas mal cherché comment on pouvait contourner cette limitation, par exemple en héritant de InputBinding ou en passant par une propriété attachée, mais en fait ça ne règle pas le problème, car les InputBindings déclarés dans le XAML n'héritent pas du DataContext

    J'ai trouvé quelques ressources intéressantes sur le sujet :
    http://social.msdn.microsoft.com/For...-1959cf36a8f7/ => la solution proposée est vraiment trop complexe à mettre en oeuvre...
    http://stackoverflow.com/questions/6...vm-application => idem
    http://groups.google.com/group/wpf-d...a4990bea96340f => Discussion très intéressante avec plusieurs gourous de WPF (Josh Smith, John Grossman...), mais au final aucune solution concrète

    Il me semble que ça ne devrait pas être très compliqué à faire, et pourtant, j'ai beau chercher, je ne trouve aucune solution simple

    Comment gérez-vous les raccourcis clavier ou souris en MVVM ? Y a-t-il une solution simple qui m'a échappé ?

    Merci d'avance !

  2. #2
    Rédacteur
    Avatar de Thomas Lebrun
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    9 161
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 9 161
    Points : 19 434
    Points
    19 434
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Comment gérez-vous les raccourcis clavier ou souris en MVVM ? Y a-t-il une solution simple qui m'a échappé ?
    Je n'ai jamasi eu l'occasion de mettre en place les raccourcis clavier avec MVVM mais je vois effectivement le problème sans avoir de solution viable

  3. #3
    Membre averti

    Profil pro
    Inscrit en
    Septembre 2005
    Messages
    214
    Détails du profil
    Informations personnelles :
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 214
    Points : 341
    Points
    341
    Par défaut
    J'ai également moi aussi rencontré le problème (la propriété Command n'est pas un DP)... Pour l'instant je m'en suis sorti en utilisant ponctuellement le code-behind, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    this.searchTextBox.InputBindings.Add(new KeyBinding(
      this.searchViewModel.SearchCommand,
      new KeyGesture(Key.Enter)));
    J'avoue que je ne suis pas du tout satisfait du résultat... Peut être que Command deviendra une DP dans .Net 4 :p
    www.japf.fr mon blog sur WPF et .Net

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Merci pour vos réponses

    Citation Envoyé par Jérem22 Voir le message
    J'ai également moi aussi rencontré le problème (la propriété Command n'est pas un DP)... Pour l'instant je m'en suis sorti en utilisant ponctuellement le code-behind, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    this.searchTextBox.InputBindings.Add(new KeyBinding(
      this.searchViewModel.SearchCommand,
      new KeyGesture(Key.Enter)));
    Effectivement ce serait une solution... mais je préfèrerais éviter de passer par le code-behind, et surtout de référencer explicitement le ViewModel dans la View...

    Citation Envoyé par Jérem22 Voir le message
    Peut être que Command deviendra une DP dans .Net 4 :p
    Ce serait un bon début, mais pas suffisant malheureusement... D'après ce que j'ai pu lire, les InputBindings ne font pas partie du visual tree ni du logical tree, et n'héritent pas du DataContext (ce qui explique le problème décrit dans le 3e post du premier lien que j'ai donné). J'ai l'impression qu'il y a comme un problème de conception dans WPF à ce niveau là . Dommage...

    Si jamais je trouve une solution viable, je vous tiendrai au courant

  5. #5
    Membre averti

    Profil pro
    Inscrit en
    Septembre 2005
    Messages
    214
    Détails du profil
    Informations personnelles :
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 214
    Points : 341
    Points
    341
    Par défaut
    Tu pourrais peut être regarder du côté d'Onyx (http://wpfonyx.codeplex.com/) qui est un framework pour utiliser MVVM.

    Peut être que le créateur du projet a prévu quelque chose de ce côté là (j'ai pas eus le temps d'essayer...).
    www.japf.fr mon blog sur WPF et .Net

  6. #6
    Rédacteur
    Avatar de The_badger_man
    Profil pro
    Développeur .NET
    Inscrit en
    Janvier 2005
    Messages
    2 745
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 745
    Points : 8 538
    Points
    8 538
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Effectivement ce serait une solution... mais je préfèrerais éviter de passer par le code-behind, et surtout de référencer explicitement le ViewModel dans la View...
    Pourquoi ? Tu as de toute façon forcément une référence sur le ViewModel étant donné que tu te bind dessus ?
    Et puis tu ne fais qu'écrire en C # se qui aurait dû l'être en XAML mais tout en restant dans la même classe (la vue). Est-ce vraiment casser la pattern que d'écrire du code pour suppléer le XAML si ce n'est pas possible de faire autrement?
    Les règles du forum
    Le trio magique : FAQ + Cours + fonction rechercher
    Mes articles
    Pas de questions par messages privés svp

    Software is never finished, only abandoned.

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par The_badger_man Voir le message
    Pourquoi ? Tu as de toute façon forcément une référence sur le ViewModel étant donné que tu te bind dessus ?
    Oui, mais normalement la vue n'a pas besoin de connaitre explicitement le type du ViewModel. On l'affecte simplement au DataContext, et le binding récupère dynamiquement les propriétés et commandes. En principe, on devrait pouvoir remplacer le ViewModel sans changer quoi que ce soit à la vue...

    Citation Envoyé par The_badger_man Voir le message
    Et puis tu ne fais qu'écrire en C # se qui aurait dû l'être en XAML mais tout en restant dans la même classe (la vue). Est-ce vraiment casser la pattern que d'écrire du code pour suppléer le XAML si ce n'est pas possible de faire autrement?
    Ce qui me gène, ce n'est pas tant d'écrire du code behind, mais de mettre une dépendance en dur sur le ViewModel. Enfin, de toutes façons j'ai l'impression qu'il n'y a pas trop le choix . Donc en attendant mieux c'est ce que je vais faire

  8. #8
    Membre expérimenté
    Avatar de FRED.G
    Profil pro
    Inscrit en
    Novembre 2002
    Messages
    1 032
    Détails du profil
    Informations personnelles :
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Novembre 2002
    Messages : 1 032
    Points : 1 505
    Points
    1 505
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Oui, mais normalement la vue n'a pas besoin de connaitre explicitement le type du ViewModel. On l'affecte simplement au DataContext, et le binding récupère dynamiquement les propriétés et commandes. En principe, on devrait pouvoir remplacer le ViewModel sans changer quoi que ce soit à la vue...
    Je ne veux pas faire trop dévier le sujet, mais a priori j'aurais pensé comme Florient. Si tu ne référence pas du tout le ViewModel, tu t'interdis par exemple de spécifier des DataTemplate basé sur un type, ce qui parfois est bien pratique.

    Sinon pour le coeur de ton pb, moi aussi j'implémente une couche ViewModel, mais je n'en suis pas encore aux InputBinding sur les commandes. Je surveille ce topic !
    (\ _ /)
    (='.'=)
    (")-(")

  9. #9
    Rédacteur
    Avatar de The_badger_man
    Profil pro
    Développeur .NET
    Inscrit en
    Janvier 2005
    Messages
    2 745
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 745
    Points : 8 538
    Points
    8 538
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Oui, mais normalement la vue n'a pas besoin de connaitre explicitement le type du ViewModel. On l'affecte simplement au DataContext, et le binding récupère dynamiquement les propriétés et commandes. En principe, on devrait pouvoir remplacer le ViewModel sans changer quoi que ce soit à la vue...

    Ce qui me gène, ce n'est pas tant d'écrire du code behind, mais de mettre une dépendance en dur sur le ViewModel.
    Je suis d'accord, c'est pourquoi il faut référencer le ViewModel dans la vue via une interface. Tu peux ainsi changer à ta guise son implémentation.
    Les règles du forum
    Le trio magique : FAQ + Cours + fonction rechercher
    Mes articles
    Pas de questions par messages privés svp

    Software is never finished, only abandoned.

  10. #10
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par FRED.G Voir le message
    Je ne veux pas faire trop dévier le sujet, mais a priori j'aurais pensé comme Florient. Si tu ne référence pas du tout le ViewModel, tu t'interdis par exemple de spécifier des DataTemplate basé sur un type, ce qui parfois est bien pratique.
    Mmm... là tu marques un point
    Effectivement, j'oubliais que j'avais quand-même une référence au ViewModel dans la définition de mon DataTemplate...

  11. #11
    Membre expérimenté
    Avatar de FRED.G
    Profil pro
    Inscrit en
    Novembre 2002
    Messages
    1 032
    Détails du profil
    Informations personnelles :
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Novembre 2002
    Messages : 1 032
    Points : 1 505
    Points
    1 505
    Par défaut
    Dans le premier lien que tu as donné, le gars propose effectivement une usine à gaz... Par contre, vers la fin, quelqu'un propose une classe qui a l'air intéressante :

    Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    using System;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Markup;
    using System.Diagnostics;
    using System.ComponentModel;
    using System.Reflection;
    using System.Windows.Input;
     
    namespace WpfMentor.CommandParameterBindings
    {
    	public class DataResource : Freezable
    	{
    		/// <summary>
    		/// Identifies the <see cref="BindingTarget"/> dependency property.
    		/// </summary>
    		/// <value>
    		/// The identifier for the <see cref="BindingTarget"/> dependency property.
    		/// </value>
    		public static readonly DependencyProperty BindingTargetProperty = DependencyProperty.Register("BindingTarget", typeof(object), typeof(DataResource), new UIPropertyMetadata(null));
     
    		/// <summary>
    		/// Initializes a new instance of the <see cref="DataResource"/> class.
    		/// </summary>
    		public DataResource()
    		{
    		}
     
    		/// <summary>
    		/// Gets or sets the binding target.
    		/// </summary>
    		/// <value>The binding target.</value>
    		public object BindingTarget
    		{
    			get { return (object)GetValue(BindingTargetProperty); }
    			set { SetValue(BindingTargetProperty, value); }
    		}
     
    		/// <summary>
    		/// Creates an instance of the specified type using that type's default constructor. 
    		/// </summary>
    		/// <returns>
    		/// A reference to the newly created object.
    		/// </returns>
    		protected override Freezable CreateInstanceCore()
    		{
    			return (Freezable)Activator.CreateInstance(GetType());
    		}
     
    		/// <summary>
    		/// Makes the instance a clone (deep copy) of the specified <see cref="Freezable"/>
    		/// using base (non-animated) property values. 
    		/// </summary>
    		/// <param name="sourceFreezable">
    		/// The object to clone.
    		/// </param>
    		protected sealed override void CloneCore(Freezable sourceFreezable)
    		{
    			base.CloneCore(sourceFreezable);
    		}
    	}
     
    	public class DataResourceBindingExtension : MarkupExtension
    	{
    		private object mTargetObject;
    		private object mTargetProperty;
    		private DataResource mDataResouce;
     
    		/// <summary>
    		/// Gets or sets the data resource.
    		/// </summary>
    		/// <value>The data resource.</value>
    		public DataResource DataResource
    		{
    			get
    			{
    				return mDataResouce;
    			}
    			set
    			{
    				if (mDataResouce != value)
    				{
    					if (mDataResouce != null)
    					{
    						mDataResouce.Changed -= DataResource_Changed;
    					}
    					mDataResouce = value;
     
    					if (mDataResouce != null)
    					{
    						mDataResouce.Changed += DataResource_Changed;
    					}
    				}
    			}
    		}
     
    		/// <summary>
    		/// Initializes a new instance of the <see cref="DataResourceBindingExtension"/> class.
    		/// </summary>
    		public DataResourceBindingExtension()
    		{
    		}
     
    		/// <summary>
    		/// When implemented in a derived class, returns an object that is set as the value of the target property for this markup extension.
    		/// </summary>
    		/// <param name="serviceProvider">Object that can provide services for the markup extension.</param>
    		/// <returns>
    		/// The object value to set on the property where the extension is applied.
    		/// </returns>
    		public override object ProvideValue(IServiceProvider serviceProvider)
    		{
    			IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
     
    			mTargetObject = target.TargetObject;
    			mTargetProperty = target.TargetProperty;
     
    			// mTargetProperty can be null when this is called in the Designer.
    			Debug.Assert(mTargetProperty != null || DesignerProperties.GetIsInDesignMode(new DependencyObject()));
     
    			if (DataResource.BindingTarget == null && mTargetProperty != null)
    			{
    				PropertyInfo propInfo = mTargetProperty as PropertyInfo;
    				if (propInfo != null)
    				{
    					try
    					{
                            // extra support for commands here. Some classes don't allow a null
                            // command for example InputBinding. In this case we use a dummy
                            // command (NullCommand) instead of setting a null value.
     
                            if ( propInfo.PropertyType.IsAssignableFrom(typeof( ICommand ) ))
                            {
                                return new NullCommand();
                            }
     
                            return Activator.CreateInstance(propInfo.PropertyType);
    					}
    					catch (MissingMethodException)
    					{
    						// there isn't a default constructor
    					}
    				}
     
    				DependencyProperty depProp = mTargetProperty as DependencyProperty;
    				if (depProp != null)
    				{
    					DependencyObject depObj = (DependencyObject)mTargetObject;
    					return depObj.GetValue(depProp);
    				}
    			}
     
    			return DataResource.BindingTarget;
    		}
     
    		private void DataResource_Changed(object sender, EventArgs e)
    		{
    			// Ensure that the bound object is updated when DataResource changes.
    			DataResource dataResource = (DataResource)sender;
    			DependencyProperty depProp = mTargetProperty as DependencyProperty;
     
    			if (depProp != null)
    			{
    				DependencyObject depObj = (DependencyObject)mTargetObject;
    				object value = Convert(dataResource.BindingTarget, depProp.PropertyType);
    				depObj.SetValue(depProp, value);
    			}
    			else
    			{
    				PropertyInfo propInfo = mTargetProperty as PropertyInfo;
    				if (propInfo != null)
    				{
    					object value = Convert(dataResource.BindingTarget, propInfo.PropertyType);
     
                        // extra support for commands here. Some classes don't allow a null
                        // command for example InputBinding. In this case we use a dummy
                        // command (NullCommand) instead of setting a null value.
     
                        if (value == null && propInfo.PropertyType.IsAssignableFrom( typeof( ICommand ) ) )
                        {
                            value = new NullCommand();
                        }
     
                        propInfo.SetValue(mTargetObject, value, new object[0]);
    				}
    			}
    		}
     
    		private object Convert(object obj, Type toType)
    		{
    			try
    			{
    				return System.Convert.ChangeType(obj, toType);
    			}
    			catch (InvalidCastException)
    			{
    				return obj;
    			}
    		}
     
            private class NullCommand : ICommand
            {
                #region ICommand Members
     
                public bool CanExecute( object parameter )
                {
                    return false;
                }
     
                public event EventHandler CanExecuteChanged;
     
                public void Execute( object parameter )
                {
                }
     
                #endregion
            }
    	}
    }

    Source : http://www.wpfmentor.com/2008/11/add...erties-in.html

    Je n'ai pas encore testé.
    (\ _ /)
    (='.'=)
    (")-(")

  12. #12
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    oui, j'avais vu ça mais j'ai pas vraiment eu le temps de regarder comment l'utiliser... je regarderai ça plus en détails ce soir

  13. #13
    Rédacteur
    Avatar de Thomas Lebrun
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    9 161
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 9 161
    Points : 19 434
    Points
    19 434
    Par défaut
    Est-ce que tu as eu le temps de regarder ? Car ca interesse pas mal de monde je pense

  14. #14
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par Thomas Lebrun Voir le message
    Est-ce que tu as eu le temps de regarder ? Car ca interesse pas mal de monde je pense
    Bon, je suis rentré un peu tard, je n'ai pu regardé que maintenant... effectivement, ça fonctionne
    Par contre le premier lien donné pour la source ne fonctionne pas pour les commandes, car la markup extension peut renvoyer null. Il faut utiliser le 2e lien, qui dans le cas d'une commande, renvoie une commande bidon si le résultat est null.
    C'est pas très compliqué à utiliser, voilà comment faire :
    Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <UserControl x:Class="TestDataResource.View.PersonView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:zz="clr-namespace:WpfMentor.CommandParameterBindings"
                 Height="300" Width="300">
        <UserControl.Resources>
            <zz:DataResource x:Key="editCommand" BindingTarget="{Binding EditCommand}"/>
        </UserControl.Resources>
        <UserControl.InputBindings>
            <KeyBinding Modifiers="Control" Key="E" Command="{zz:DataResourceBinding DataResource={StaticResource editCommand}}"/>
        </UserControl.InputBindings>
    ...

    La syntaxe est un peu lourde, surtout qu'il faut définir une DataResource pour chaque commande, mais ça a le mérite de fonctionner et c'est déjà pas mal

    Je ne désespère pas de trouver une solution encore plus élégante, mais en attendant je considère le problème comme résolu...

  15. #15
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Finalement, j'étais pas complètement satisfait par ce truc là : trop compliqué à mon goût... Donc j'ai un peu creusé, et j'ai fini par trouver un moyen d'accéder au DataContext au niveau de la déclaration des InputBindings (ou ailleurs), à l'aide d'une markup extension. Le résultat est très pratique, je pense pas qu'on puisse faire beaucoup plus simple

    Je me suis un peu inspiré de la DataResource mentionnée plus haut pour certains trucs, mais pour le reste, autant le dire tout de suite : c'est de la grosse bidouille . Après avoir un peu fouillé avec Reflector pour trouver ce dont j'avais besoin, j'ai repéré quelques classes et champs privés que j'utilise pour retrouver le DataContext de l'élément racine du XAML. Sachant que lors de la création de l'objet, il n'est pas forcément défini, je m'abonne à l'évènement DataContextChanged pour mettre à jour la commande quand le DataContext change.

    Voilà le code :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    using System;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Markup;
     
    namespace MVVMLib.Input
    {
        [MarkupExtensionReturnType(typeof(ICommand))]
        public class CommandBindingExtension : MarkupExtension
        {
            public CommandBindingExtension()
            {
            }
     
            public CommandBindingExtension(string commandName)
            {
                this.CommandName = commandName;
            }
     
            [ConstructorArgument("commandName")]
            public string CommandName { get; set; }
     
            private object targetObject;
            private object targetProperty;
     
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
                if (provideValueTarget != null)
                {
                    targetObject = provideValueTarget.TargetObject;
                    targetProperty = provideValueTarget.TargetProperty;
                }
     
                if (!string.IsNullOrEmpty(CommandName))
                {
                    ParserContext parserContext = GetPrivateFieldValue<ParserContext>(serviceProvider, "_context");
                    if (parserContext != null)
                    {
                        FrameworkElement rootElement = GetPrivateFieldValue<FrameworkElement>(parserContext, "_rootElement");
                        if (rootElement != null)
                        {
                            object dataContext = rootElement.DataContext;
                            if (dataContext != null)
                            {
                                ICommand command = GetCommand(dataContext, CommandName);
                                if (command != null)
                                    return command;
                            }
                            else
                            {
                                if (!dataContextChangeHandlerSet)
                                {
                                    rootElement.DataContextChanged += new DependencyPropertyChangedEventHandler(rootElement_DataContextChanged);
                                    dataContextChangeHandlerSet = true;
                                }
                            }
                        }
                    }
                }
     
                return DummyCommand.Instance;
            }
     
            private ICommand GetCommand(object dataContext, string commandName)
            {
                PropertyInfo prop = dataContext.GetType().GetProperty(commandName);
                if (prop != null)
                {
                    ICommand command = prop.GetValue(dataContext, null) as ICommand;
                    if (command != null)
                        return command;
                }
                return null;
            }
     
            private void AssignCommand(ICommand command)
            {
                if (targetObject != null && targetProperty != null)
                {
                    if (targetProperty is DependencyProperty)
                    {
                        DependencyObject depObj = targetObject as DependencyObject;
                        DependencyProperty depProp = targetProperty as DependencyProperty;
                        depObj.SetValue(depProp, command);
                    }
                    else
                    {
                        PropertyInfo prop = targetProperty as PropertyInfo;
                        prop.SetValue(targetObject, command, null);
                    }
                }
            }
     
            private bool dataContextChangeHandlerSet = false;
            private void rootElement_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
            {
                FrameworkElement rootElement = sender as FrameworkElement;
                if (rootElement != null)
                {
                    object dataContext = rootElement.DataContext;
                    if (dataContext != null)
                    {
                        ICommand command = GetCommand(dataContext, CommandName);
                        if (command != null)
                        {
                            AssignCommand(command);
                        }
                    }
                }
            }
     
            private T GetPrivateFieldValue<T>(object target, string fieldName)
            {
                FieldInfo field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
                if (field != null)
                {
                    return (T)field.GetValue(target);
                }
                return default(T);
            }
     
            private class DummyCommand : ICommand
            {
     
                #region Singleton pattern
     
                private DummyCommand()
                {
                }
     
                private static DummyCommand _instance = null;
                public static DummyCommand Instance
                {
                    get
                    {
                        if (_instance == null)
                        {
                            _instance = new DummyCommand();
                        }
                        return _instance;
                    }
                }
     
                #endregion
     
                #region ICommand Members
     
                public bool CanExecute(object parameter)
                {
                    return false;
                }
     
                public event EventHandler CanExecuteChanged;
     
                public void Execute(object parameter)
                {
                }
     
                #endregion
            }
        }
    }

    Et ça s'utilise comme ça :
    Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <UserControl x:Class="TestMVVM.View.PersonView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:input="clr-namespace:MVVMLib.Input;assembly=MVVMLib"
                 Height="300" Width="300">
        <UserControl.InputBindings>
            <KeyBinding Modifiers="Control" Key="E" Command="{input:CommandBinding EditCommand}"/>
        </UserControl.InputBindings>
        ...

    Plus besoin de déclarer quoi que ce soit dans les ressources, et la syntaxe est réduite au minimum vital

    Par contre, une petite limitation : ça ne marche a priori que pour le DataContext défini sur l'élément racine... (j'ai pas vérifié, mais vu le fonctionnement, ça semblerait logique...)

    On pourrait apporter une petite amélioration en ajoutant un handler sur le PropertyChanged du ViewModel, histoire de savoir si la commande change, mais en pratique c'est assez improbable il me semble...

  16. #16
    Rédacteur
    Avatar de Thomas Lebrun
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    9 161
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 9 161
    Points : 19 434
    Points
    19 434
    Par défaut
    Très très pratique ca


    Merci !

  17. #17
    Membre averti

    Profil pro
    Inscrit en
    Septembre 2005
    Messages
    214
    Détails du profil
    Informations personnelles :
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 214
    Points : 341
    Points
    341
    Par défaut
    Sympathique ta solution !

    Bien trouvé le coup de la MarkupExtension, j'ai pas du tout l'habitude d'en créer de nouvelles, mais visiblement, ça peut bien rendre service... Merci bien
    www.japf.fr mon blog sur WPF et .Net

  18. #18
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Pour info, j'ai fait des (petites) modifs dans le code, la nouvelle version est dispo ici :
    http://tomlev.wordpress.com/2009/03/...-pattern-mvvm/

  19. #19
    DrQ
    DrQ est déconnecté
    Membre expérimenté
    Avatar de DrQ
    Profil pro
    Inscrit en
    Mars 2002
    Messages
    388
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 388
    Points : 1 515
    Points
    1 515
    Par défaut
    Très jolie technique Thomas et super pratique.

    J'ai réalisé la même chose pour la propriété CommandParameter avec un petit plus qui me permet de faire un binding sur un ElementName.
    Le souci que je rencontre est que je ne peux pas faire de binding sur un ElementName dans un DataTemplate car le _rootElement me donne le ResourceDictionary contenant le DataTemplate. Je n'ai pas trouvé de solution pour le moment pour retrouver le DataTemplate dans lequel le KeyBinding se trouve. Y a t'il un moyen qui permette cela ?
    1)http://www.developpez.com/cours/
    2)Recherche
    3)Posez votre question en suivant les règles
    _oOo-DrQ-oOo_

  20. #20
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Points : 39 749
    Points
    39 749
    Par défaut
    Citation Envoyé par DrQ Voir le message
    Très jolie technique Thomas et super pratique.
    Ouais, sur le moment j'étais très content de moi, mais, avec le recul je suis plus très satisfait par cette solution... c'est vraiment pas clean d'utiliser la réflexion privée, parce que ça peut être "cassé" par les changements d'implémentation dans les versions suivantes de WPF (d'ailleurs c'est cassé en 4.0, cf. les commentaires de Thomas Lebrun sur mon blog).

    Depuis j'ai découvert une autre approche, un peu moins concise mais beaucoup plus propre : la classe CommandReference, dispo dans le MVVM Toolkit. Voir ce lien pour plus de détails

    Citation Envoyé par DrQ Voir le message
    J'ai réalisé la même chose pour la propriété CommandParameter avec un petit plus qui me permet de faire un binding sur un ElementName.
    Le souci que je rencontre est que je ne peux pas faire de binding sur un ElementName dans un DataTemplate car le _rootElement me donne le ResourceDictionary contenant le DataTemplate. Je n'ai pas trouvé de solution pour le moment pour retrouver le DataTemplate dans lequel le KeyBinding se trouve. Y a t'il un moyen qui permette cela ?
    Je ne crois pas... malheureusement il n'y a pas de moyen simple pour "remonter" l'arbre logique jusqu'au template. Par contre, dans la version 4, y a le nouveau namespace System.Xaml qui devrait permettre de faciliter ce genre de choses (enfin il me semble...)

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [MVVM] Utiliser un valueconverter pour binder une énum à des radiobuttons
    Par zoaax dans le forum Windows Presentation Foundation
    Réponses: 4
    Dernier message: 25/11/2011, 17h01
  2. Exemple d'utilisation des ApplicationCommands avec MVVM
    Par Krustig dans le forum Windows Presentation Foundation
    Réponses: 4
    Dernier message: 31/03/2011, 14h04
  3. utilisation des sockets sous windows
    Par Tupac dans le forum Réseau
    Réponses: 2
    Dernier message: 21/12/2002, 18h24
  4. [Crystal Report] Utilisation des vues de sql serveur
    Par Olivierakadev dans le forum SAP Crystal Reports
    Réponses: 2
    Dernier message: 15/11/2002, 17h44
  5. [BCB5] Utilisation des Ressources (.res)
    Par Vince78 dans le forum C++Builder
    Réponses: 2
    Dernier message: 04/04/2002, 16h01

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo