Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Silverlight]BindingMode.OneWay

Pas si OneWay que ça en fait...

Sujet résolu
    13 octobre 2011 à 12:29:01

    Salut à vous...

    Depuis deux jours, je bataille avec un problème plus qu'agaçant.

    J'ai un ViewModel qui implémente INotifyPropertyChanged. L’implémentation est faite de telle sorte à ce que même en accès inter-thread, on puisse notifier d'un changement (ça passe par Deployment.Current.Dispatcher.BeginInvoke())

    J'ai une vue attachée au ViewModel...Qui contient un contrôle héritant de la classe Control et qui permet de faire un séquenceur (un seul contrôle en regroupe plusieurs autres pour faire un système par étape avec les boutons suivant/précédent...).

    [Là je détaille le côté "Toolbox" et la conception du contrôle] Sur le contrôle en question, j'ai fais des propriétés de dépendance "NextEnabled" et "PreviousEnabled" (avec la déclaration de propriété, son DependencyProperty...) et je bind en OneWay le IsEnabled des boutons suivant/précédent du template par défaut du contrôle.

    [Là, je retourne côté programme client qui exploite la lib de contrôles] Dans la vue attachée au ViewModel, je bind ma propriété "NextEnabled" sur une propriété du ViewModel portant le même nom.

    The question : Par défaut, un bind est effectué en OneWay (tiré du MSDN). Ce mode est censé faire en sorte que lorsque la source (ici, mon ViewModel) notifie du changement d'une propriété, le moteur répercute la valeur de la propriété changée dans la propriété de dépendance du contrôle cible. Comment se fait-il que, dans mon cas, le changement ne soit pas répercuté?

    Ce que j'ai vérifié :
    -Est-ce que la notification est effectuée correctement? Oui
    -Est-ce que les valeurs de la propriété du ViewModel sont bien celle attendue avant/après le chargement de la vue? Oui
    -Est-ce que, après le chargement de la vue, la propriété de dépendance possède bien la bonne valeur? Non
    -Est-ce que la propriété de dépendance est notifiée du changement? Oui, à l'application du template par défaut uniquement...Après ça, plus aucune notification n'est reçue...
    -Est-ce que le dispatcher visuel a bien appelé la notification sur la propriété du ViewModel? Oui.

    Ce que j'ai testé :
    -Pensant au départ que TemplateBinding et Binding (même si le MSDN dit que le premier est équivalent au second avec RelativeSource={RelativeSource Mode=TemplatedParent}) ne donnaient pas le même résultat, j'ai testé les deux => Ca marche toujours pas.
    -Vu que la propriété du ViewModel et la propriété de dépendance portent le même nom, j'ai pensé à un conflit de nom (la propriété de dépendance se serait bindée sur elle même). Même en forçant la source avec NextEnabled="{Binding DataContext.NextEnabled, ElementName=MainView}", ça ne marche pas mieux...
    -Qu'est ce que ça donne en BindingMode.TwoWay dans ma vue? Ba la ça marche...

    La seule explication que j'ai à apporter à ce "bug"...C'est que au chargement de la vue, avec un BindingMode.OneWay, les valeurs de la source et de la cible deviennent différentes...Du coup, le moteur de liaison ne fait plus la relation entre les deux. Ca tient pas la route cette explication...mais c'est la seule que je trouve "possible" pour le moment.

    Quelqu'un aurait-il déjà été confronté à ce problème?
    • Partager sur Facebook
    • Partager sur Twitter
      13 octobre 2011 à 15:56:50

      Comment as-tu défini exactement le binding entre la propriété IsEnabled de tes boutons et les propriétés NextEnabled et PreviousEnabled du contrôle parent ? :euh:

      Je vois 4 manières possibles de réaliser cette liaison pour les boutons de ton séquenceur (Wizard) :

      1) Définir des DependencyProperties (DP) comme tu l'as fait au niveau du contrôle et y binder les propriétés IsEnabled de tes boutons. Le binding sera de la forme:
      <Button Content="Next" IsEnabled="{Binding NextEnabled, ElementName=nomDuControle}" />
      

      Le problème c'est que ça impose de nommer la racine du contrôle pour pouvoir le désigner comme source du binding, et ça pose problème si tu souhaites le renommer différemment au moment de l'utiliser - car du coup les Bindings ne retrouvent plus leur source.

      2) Avec ces mêmes DP, définir l'apparence par défaut de ton contrôle dans un Template et utiliser cette fois des TemplateBindings, de la forme:
      <Button Content="Next" IsEnabled="{TemplateBinding NextEnabled}" />
      

      Ca suppose donc de passer par un Template.

      3) Toujours avec ces mêmes DP, leur définir un callback à exécuter lorsqu'elles sont modifiées, et dans ce callback modifier tes boutons par code-behind.
      Donc tu as un bouton (nommé) :
      <Button x:Name="nextBtn" Content="Next" />
      

      Et une DP avec son callback :
      public static readonly DependencyProperty NextEnabledProperty = 
          DependencyProperty.Register("NextEnabled", typeof(bool), typeof(WizardControl), new PropertyMetadata(true, OnNextEnabledPropertyChanged);
      
      public static void OnNextEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
          WizardControl ctrl = d as WizardControl;
          ctrl.nextBtn.IsEnabled = (bool)e.NewValue;
      }
      

      C'est pas joli-joli (c'est du code-behind :-° ), mais ça fait l'affaire.

      4) (Celle que je préconise personnellement) Définir un WizardViewModel pour ton WizardControl, et lui donner les propriétés (observables) NextEnabled et PreviousEnabled ainsi que les méthodes GoNext ou GoPrevious correspondantes (ou, si tu utilises un framework comme Prism, des ICommand NextCommand et PreviousCommand).
      Tu as donc ton ViewModel "maître" de départ qui doit contenir un WizardViewModel "esclave" pour gérer l'état du Wizard.

      Donc tu as ton ViewModel maître:
      public class MainWindowViewModel : INotifyPropertyChanged
      {
          public MainWindowViewModel()
          {
              this.WizardViewModel = new WizardViewModel();
          } 
      
          // ...
      
          public WizardViewModel WizardViewModel { get; private set; }
      }
      

      Et ton ViewModel esclave:

      public class WizardViewModel : INotifyPropertyChanged
      {
          public WizardViewModel()
          {
              this.NextCommand = new DelegateCommand(this.GoNext, this.CanGoNext);
          }
      
          private bool NextEnabled
          {
              get { // retourne l'état actif ou inactif du bouton (observable) }
          } 
         
          public void GoNext()
          {
              // passe à l'écran suivant
          }
      
          // ...
      }
      

      Tu utilises ton WizardControl comme suit:

      <my:WizardControl DataContext="{Binding WizardViewModel}" />
      

      Et tu en définis ses boutons comme suit:
      <Button Content="Next" IsEnabled="{Binding NextEnabled}"/>
      

      (reste encore à associer l'action Click du bouton, selon que tu utilises des Commandes ou des Triggers).

      J'aime bien cette dernière approche parce qu'on reste en territoire connu : l'état d'un contrôle est géré comme d'habitude par son ViewModel, et on ne s'embête pas avec des DepencyProperties. Par contre ça impose l'utilisation du pattern MVVM pour le faire fonctionner.

      Parmi ces solutions, il y en a bien une qui devrait marcher :)
      • Partager sur Facebook
      • Partager sur Twitter
        13 octobre 2011 à 16:42:57

        Citation : Orwell

        Je vois 4 manières possibles de réaliser cette liaison pour les boutons de ton séquenceur (Wizard) :


        Ro le truc de folie XD. Ce contrôle s'appelle justement "Wizard" :D. Le contrôle comme cette même pensée sont magique XD :magicien:

        Mais revenons aux choses sérieuses.

        Je suis dans le 2eme cas. Définir les contrôles en créant une classe + un style par défaut...Ca permet justement de rendre plus flexible le contrôle (étant lui même créé à partir d'un template, ledit contrôle devient facilement templatisable).

        Voici les codes :
        Template par défaut référencé dans generic.xaml de la bibliothèque

        <ResourceDictionary
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:ufmk="[NameSpaceDeControleUtilisateurs]"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
            <Style TargetType="ufmk:Wizard">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ufmk:Wizard">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.15*"/>
                                    <RowDefinition Height="0.85*"/>
                                    <RowDefinition Height="25"/>
                                </Grid.RowDefinitions>
                                <ContentPresenter x:Name="TitlePresenter" Grid.Row="0" Content="{Binding CurrentStep.HeadContent, RelativeSource={RelativeSource Mode=TemplatedParent}}" ContentTemplate="{TemplateBinding HeadTemplate}"/>
                                <ContentPresenter x:Name="MainPresenter" Grid.Row="1" Content="{TemplateBinding CurrentStep}"/>
                                <Button x:Name="PART_PreviousButton" Grid.Row="2" Width="100" IsEnabled="{TemplateBinding PreviousEnabled}" Margin="0,0,110,0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="{Binding CurrentStep.PreviousContent, RelativeSource={RelativeSource Mode=TemplatedParent}, TargetNullValue=Previous, FallbackValue=Previous}"/>
                                <Button x:Name="PART_NextButton" Grid.Row="2" Width="100" IsEnabled="{TemplateBinding NextEnabled}" HorizontalAlignment="Right" VerticalAlignment="Center" Content="{Binding CurrentStep.NextContent, RelativeSource={RelativeSource Mode=TemplatedParent}, TargetNullValue=Next, FallbackValue=Next}"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
        



        Code source du contrôle Wizard.cs

        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Markup;
        
        namespace [NameSpaceDeControleUtilisateurs]
        {
            [ContentProperty("Steps")]
            public class Wizard : Control
            {
                #region Attributs
                public static readonly DependencyProperty CurrentStepProperty = DependencyProperty.RegisterAttached("CurrentStep", typeof(WizardStep), typeof(Wizard), null);
                public static readonly DependencyProperty CurrentStepIndexProperty = DependencyProperty.RegisterAttached("CurrentStepIndex", typeof(int), typeof(Wizard), new PropertyMetadata(Wizard.CurrentStepIndexChanged));
                public static readonly DependencyProperty HeadTemplateProperty = DependencyProperty.RegisterAttached("HeadTemplate", typeof(DataTemplate), typeof(Wizard), null);
                public static readonly DependencyProperty NextEnabledProperty = DependencyProperty.RegisterAttached("NextEnabled", typeof(bool), typeof(Wizard), null);
                public static readonly DependencyProperty PreviousEnabledProperty = DependencyProperty.RegisterAttached("PreviousEnabled", typeof(bool), typeof(Wizard), null);
                #endregion
        
                #region Propriétés
                /// <summary>
                /// Obtient ou définit l'étape actuellement affichée
                /// </summary>
                public WizardStep CurrentStep
                {
                    get { return (WizardStep)base.GetValue(CurrentStepProperty); }
                    set { base.SetValue(CurrentStepProperty, value); }
                }
        
                /// <summary>
                /// Obtient ou définit l'index de l'étape en cours d'affichage
                /// </summary>
                public int CurrentStepIndex
                {
                    get { return (int)base.GetValue(CurrentStepIndexProperty); }
                    set { base.SetValue(CurrentStepIndexProperty, value); }
                }
        
                /// <summary>
                /// Retourne la liste des étapes disponibles
                /// </summary>
                public List<WizardStep> Steps
                {
                    get;
                    private set;
                }
        
                /// <summary>
                /// Obtient ou définit le template des entêtes d'étape
                /// </summary>
                public DataTemplate HeadTemplate
                {
                    get { return (DataTemplate)base.GetValue(HeadTemplateProperty); }
                    set { base.SetValue(HeadTemplateProperty, value); }
                }
        
                /// <summary>
                /// Active ou non le bouton "Suivant"
                /// </summary>
                public bool NextEnabled
                {
                    get { return (bool)base.GetValue(NextEnabledProperty); }
                    set { base.SetValue(NextEnabledProperty, value); }
                }
        
                /// <summary>
                /// Active ou non le bouton "Précédent"
                /// </summary>
                public bool PreviousEnabled
                {
                    get { return (bool)base.GetValue(PreviousEnabledProperty); }
                    set { base.SetValue(PreviousEnabledProperty, value); }
                }
                #endregion
        
                #region Evénements
                /// <summary>
                /// Appelé quand le bouton "Next" est cliqué
                /// </summary>
                public event EventHandler NextClicked;
        
                /// <summary>
                /// Appelé quand le bouton "Previous" est cliqué
                /// </summary>
                public event EventHandler PreviousClicked;
        
                /// <summary>
                /// Appelé quand le bouton "Next" est cliqué et qu'on se trouve déjà à la fin des étapes
                /// </summary>
                public event EventHandler FinishClicked;
        
                /// <summary>
                /// Appelé quand le bouton "Previous" est cliqué et qu'on se trouve déjà au début des étapes
                /// </summary>
                public event EventHandler StartClicked;
                #endregion
        
                #region Constructeur
                public Wizard()
                {
                    this.Steps = new List<WizardStep>();
                    base.DefaultStyleKey = typeof(Wizard);
                }
                #endregion
        
                #region Méthodes
        
                #region OnApplyTemplate()
                /// <summary>
                /// Appelée quand le template est appliqué
                /// </summary>
                public override void OnApplyTemplate()
                {
                    Button previousButton = (Button)base.GetTemplateChild("PART_PreviousButton");
                    if (previousButton != null)
                    {
                        previousButton.Click += this.PreviousButton_Click;
                    }
        
                    Button nextButton = (Button)base.GetTemplateChild("PART_NextButton");
                    if (nextButton != null)
                    {
                        nextButton.Click += this.NextButton_Click;
                    }
        
                    this.MoveStepIndexTo(0, 0);
                    base.OnApplyTemplate();
                }
                #endregion
        
                #region CurrentStepIndexChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
                /// <summary>
                /// Appelée quand l'index à changé
                /// </summary>
                private static void CurrentStepIndexChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
                {
                    Wizard wizard = (Wizard)target;
                    wizard.OnCurrentStepIndexChanged((int)e.OldValue, (int)e.NewValue);
                }
                #endregion
        
                #region OnCurrentStepIndexChanged(int oldValue, int newValue)
                /// <summary>
                /// Appelée quand l'index à changé
                /// </summary>
                private void OnCurrentStepIndexChanged(int oldValue, int newValue)
                {
                    this.MoveStepIndexTo(oldValue, newValue);
                }
                #endregion
        
                #region MoveStepIndexTo(int oldIndex, int newIndex)
                /// <summary>
                /// Déplace l'index d'affichage à l'étape indiquée
                /// </summary>
                /// <param name="newIndex">Nouvel index</param>
                /// <param name="oldIndex">Ancien index</param>
                private void MoveStepIndexTo(int oldIndex, int newIndex)
                {
                    if (this.Steps.Count > 0 && newIndex < this.Steps.Count)
                    {
                        WizardStep newStep = this.Steps[newIndex];
                        this.NextEnabled = newIndex + 1 < this.Steps.Count || newStep.LeaveNextEnabled;
                        this.PreviousEnabled = newIndex - 1 > -1 || newStep.LeavePreviousEnabled;
                        this.CurrentStep = newStep;
                    }
                    else
                    {
                        this.NextEnabled = false;
                        this.PreviousEnabled = false;
                        this.CurrentStep = null;
                    }
                }
                #endregion
        
                #region PreviousButton_Click(Object sender, RoutedEventArgs e)
                /// <summary>
                /// Appelée quand on clique sur le bouton Précédent
                /// </summary>
                private void PreviousButton_Click(Object sender, RoutedEventArgs e)
                {
                    if (this.PreviousClicked != null)
                    {
                        this.PreviousClicked(this, EventArgs.Empty);
                    }
        
                    if (this.CurrentStepIndex <= 0)
                    {
                        if (this.StartClicked != null)
                        {
                            this.StartClicked(this, EventArgs.Empty);
                        }
                    }
                    else
                    {
                        this.CurrentStepIndex--;
                    }
                }
                #endregion
        
                #region NextButton_Click(Object sender, RoutedEventArgs e)
                /// <summary>
                /// Appelée quand on clique sur le bouton suivant
                /// </summary>
                private void NextButton_Click(Object sender, RoutedEventArgs e)
                {
                    if (this.NextClicked != null)
                    {
                        this.NextClicked(this, EventArgs.Empty);
                    }
        
                    if (this.CurrentStepIndex + 1 >= this.Steps.Count)
                    {
                        if (this.FinishClicked != null)
                        {
                            this.FinishClicked(this, EventArgs.Empty);
                        }
                    }
                    else
                    {
                        this.CurrentStepIndex++;
                    }
                }
                #endregion
        
                #endregion
            }
        }
        



        Le code source de WizardStep.cs

        using System;
        using System.Windows;
        using System.Windows.Controls;
        
        namespace [NameSpaceDeControleUtilisateurs]
        {
            public class WizardStep : UserControl
            {
                #region Attributs
                public static readonly DependencyProperty HeadContentProperty = DependencyProperty.RegisterAttached("HeadContent", typeof(Object), typeof(WizardStep), null);
                public static readonly DependencyProperty NextContentProperty = DependencyProperty.RegisterAttached("NextContent", typeof(Object), typeof(WizardStep), null);
                public static readonly DependencyProperty PreviousContentProperty = DependencyProperty.RegisterAttached("PreviousContent", typeof(Object), typeof(WizardStep), null);
                public static readonly DependencyProperty LeaveNextEnabledProperty = DependencyProperty.RegisterAttached("LeaveNextEnabled", typeof(bool), typeof(WizardStep), null);
                public static readonly DependencyProperty LeavePreviousEnabledProperty = DependencyProperty.RegisterAttached("LeavePreviousEnabled", typeof(bool), typeof(WizardStep), null);
                #endregion
        
                #region Propriétés
                /// <summary>
                /// Retourne le contenu de l'entête pour cette étape
                /// </summary>
                public Object HeadContent
                {
                    get { return base.GetValue(HeadContentProperty); }
                    set { base.SetValue(HeadContentProperty, value); }
                }
        
                /// <summary>
                /// Retourne le contenu du bouton "Suivant"
                /// </summary>
                public Object NextContent
                {
                    get { return base.GetValue(NextContentProperty); }
                    set { base.SetValue(NextContentProperty, value); }
                }
        
                /// <summary>
                /// Retourne le contenu du bouton "Précédent"
                /// </summary>
                public Object PreviousContent
                {
                    get { return base.GetValue(PreviousContentProperty); }
                    set { base.SetValue(PreviousContentProperty, value); }
                }
        
                /// <summary>
                /// Indique si on laisse le bouton "Suivant" actif et que cette étape est la dernière
                /// </summary>
                public bool LeaveNextEnabled
                {
                    get { return (bool)base.GetValue(LeaveNextEnabledProperty); }
                    set { base.SetValue(LeaveNextEnabledProperty, value); }
                }
        
                /// <summary>
                /// Indique si on laisse le bouton "Précédent" actif et que cette étape est la dernière
                /// </summary>
                public bool LeavePreviousEnabled
                {
                    get { return (bool)base.GetValue(LeavePreviousEnabledProperty); }
                    set { base.SetValue(LeavePreviousEnabledProperty, value); }
                }
                #endregion
            }
        }
        



        Un exemple d'utilisation du contrôle Wizard ainsi créé

        <ctl:ChildWindow
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:ctl="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
            xmlns:ufmk="[NameSpaceDeControleUtilisateurs]"
            xmlns:wctl="[AutreNameSpaceDeControleUtilisateurs]"
            mc:Ignorable="d" x:Name="MainWindow" x:Class="[...].ModalViews.AppointmentWizardWindow"
            Width="571" Height="503" Title="{Binding [Title]}" Style="{StaticResource GeneralAdornedWindowStyle}"
            DataContext="{Binding ModalViewsLocator.AppointmentWizardWindowViewModel, Mode=OneWay, Source={StaticResource EngineSource}}">
            <ufmk:Wizard x:Name="MainWizard" Margin="7" NextEnabled="{Binding NextEnabled, Mode=TwoWay}">
                <ufmk:Wizard.HeadTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding .}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center" TextWrapping="Wrap"/>
                    </DataTemplate>
                </ufmk:Wizard.HeadTemplate>
                <wctl:CategoryStep x:Name="CategoryStep" />
                <wctl:AvailabilityStep x:Name="AvailabilityStep" />
                <wctl:ClientSelectionStep x:Name="ClientSelectionStep" />
            </ufmk:Wizard>
        </ctl:ChildWindow>
        



        Le ViewModel attaché à la vue précédente

        using [NameSpace];
        
        namespace SilverlightBusiness.ViewModels.ModalViews
        {
            public class AppointmentWizardWindowViewModel : BaseWindowViewModel
            {
                #region Propriétés
                /// <summary>
                /// Indique si le bouton "Suivant" est actif ou non
                /// </summary>
                public bool NextEnabled
                {
                    get { return base.GetValue<bool>("NextEnabled"); }
                    set { base.NotifiedSetValue<bool>("NextEnabled", value); }
                }
                #endregion
        
                #region Constructeur
                public AppointmentWizardWindowViewModel()
                {
                    base.SetResourceManagerName("SilverlightBusiness.Resources.ModalViews.AppointmentWizardWindow");
                }
                #endregion
            }
        }
        



        Un exemple de création d'une étape du Wizard

        <ufmk:WizardStep
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:ufmk="[NameSpaceDeControleUtilisateurs]"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:bfmk="[NameSpacePourLesBehaviorsPersonnalises]"
            xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 
            x:Name="MainView" x:Class="[AutreNameSpaceDeControleUtilisateurs].CategoryStep"
            mc:Ignorable="d"
            d:DesignHeight="300" d:DesignWidth="400" 
            DataContext="{Binding ControlsLocator.AppointmentWizardLocator.CategoryStepViewModel, Mode=OneWay, Source={StaticResource EngineSource}}" 
            HeadContent="{Binding [Title]}"
            PreviousContent="{Binding [PreviousText]}" NextContent="{Binding [NextText]}">
            <i:Interaction.Behaviors>
                <bfmk:LifecycleManagerBehavior />
            </i:Interaction.Behaviors>
            <Grid>
        <!-- Content de l'étape 1 -->
            </Grid>
        </ufmk:WizardStep>
        



        Quelques détails :
        -Lors de l'utilisation de cet ensemble, on doit créer des interfaces qui héritent de WizardStep à la place de UserControl.
        -Chaque interface créée doit être placée dans un le contrôle Wizard (un peu le même fonctionnement que TabControl et TabItem en fait...)
        -Pourquoi faire hériter WizardStep de UserControl plutôt que de Control ou ContentControl? Parce que Control ne propose pas de propriété Content et que ContentControl n'est pas efficace tel quel dans les designers (La vue designée d'un ContentControl est minimisée aux dimensions spécifiées sur le contrôle (par défaut, 0/0) alors que la vue designée d'un UserControl prend en charge un dimensionnement-exemple appliqué uniquement lors du design (avec les propriétés d:DesignWidth et d:DesignHeight)) et que j'avais pas envie de me lancer dans le pourquoi du comment de ce problème ^^ .
        -Au départ, je pensais intéressant de placer NextEnabled et PreviousEnabled sur les étapes du Wizard plus que sur le Wizard lui même. J'ai d'abord cru que le bug venait de là et du coup, j'ai remis les deux propriétés sur Wizard plutôt que sur les étapes (un choix entre la logique ou la facilité...Le premier était moins logique mais plus pratique, le second, plus logique mais moins pratique à utiliser...Depuis je suis resté sur le second)

        Edit : Avec tout ça, j'ai oublié de préciser que le problème se situe dans le XAML du contrôle utilisant Wizard. Ici, j'ai laissé la solution de contournement qui consiste à binder en TwoWay sur la propriété du ViewModel correspondant. Si je passe en OneWay, j'ai beau mettre ma propriété du ViewModel à true ou à false, le contrôle Wizard ne reçoit aucune notification....

        Re-Edit : Au final, cette technique se rapproche de celle énoncée dans ta 4eme proposition : Mon ViewModel "maître" est la classe AppointmentWizardWindowViewModel...Mais je ne dispose pas de ViewModel esclave. En revanche, j'ai des ViewModel qui s'en rapprochent : Ceux qui sont utilisés pour les étapes (chaque étape dispose de son propre ViewModel).
        Le "Framework" utilisé est un Framework fait maison qui ne se base pas sur les commandes pour activer/désactiver des boutons. C'est un point noir avec lequel je suis en désaccord dans MVVM pur : Faire des "propriété" qui sont en fait des "action"...J'ai toujours trouvé ça aberrant...Je préfère faire une propriété Enabled, couplée avec un Trigger dans la vue qui appelle une méthode...Ca casse un peu le MVVM, mais ça me semble plus logique : Ce qui expose les données (les propriétés) garde son rôle de fournisseur de données et les méthodes gardent leur rôle d'action à entreprendre à certains moment et il n'y aucun mix des deux d'effectué.

        Re-Re-Edit : La proposition 3 a été mise en place juste pour tester...Malheureusement, même celle-ci n'aurait pas fonctionné, le callback recevant bien le changement à l'application du template par défaut et ne recevant plus rien après le chargement du contrôle.



        Last-Edit : Dernier test effectué : J'ai rajouté une CheckBox dans la première étape, bindée en TwoWay sur la propriété du ViewModel AppointmentWizardWindowViewModel avec un oint d'arrêt lors de la notification. Je check/décheck la checkbox...J'ai bien le point d'arrêt qui est atteind...Mais la propriété de dépendance de Wizard n'est toujours pas notifiée...

        BonJarreteAvecLesEdit :D : 'ai p'tet trouvé l'explication...

        Citation : MSDN


        Pour les liaisons OneTime et OneWay, les appels à SetValue modifient automatiquement la valeur cible et suppriment la liaison.


        Peut-être y'a-t-il à un moment dans le Wizard un appel à SetValue qui annule la liaison OneWay. Par exemple : Le contrôle est chargé...Les propriétés liées sont résolues...Le template est appliqué, la propriété NextEnabled est définie à true parce que l'étape à l'index 0+1 existe; cette définition annule la liaison OneWay sur NextEnabled. Toute autre notification n'est donc plus répercutée.

        La question qui en découle, c'est "Comment on fait pour définir une propriété dans un contrôle sans casser la liaison??" o_O . Autant en WPF, il y a SetValue (même effet qu'en Silverlight) et SetCurrentValue (ça définit la valeur de la propriété sans interrompre les opérations de binding)...Autant en Silverlight, SetCurrentValue n'existe pas. Alors on fait comment si on doit définir une propriété à l'intérieur du contrôle?? o_O
        • Partager sur Facebook
        • Partager sur Twitter
          14 octobre 2011 à 13:34:18

          Le problème, c'est qu'un d'un côté tu veux un Binding OneWay, mais de l'autre, tu veux quand même modifier ta propriété depuis ton code, alors que c'est ton ViewModel qui est la source de ton Binding. Si tu veux pas casser ton Binding, il te faudrait donc modifier le ViewModel, et non la DP (sinon, et ça semble logique, ton Binding est cassé). Y'a une logique qui est incohérente en fait.

          Enfin, c'est en tout cas ce que je comprends.
          • Partager sur Facebook
          • Partager sur Twitter
            14 octobre 2011 à 14:50:44

            Citation : Nisnor

            Citation : Orwell

            Je vois 4 manières possibles de réaliser cette liaison pour les boutons de ton séquenceur (Wizard) :


            Ro le truc de folie XD. Ce contrôle s'appelle justement "Wizard" :D. Le contrôle comme cette même pensée sont magique XD :magicien:


            Oui enfin bon, "wizard" est le terme générique pour un séquenceur de ce genre, pas de mystère ici :p

            Citation : Nisnor

            BonJarreteAvecLesEdit :D : 'ai p'tet trouvé l'explication...

            Citation : MSDN


            Pour les liaisons OneTime et OneWay, les appels à SetValue modifient automatiquement la valeur cible et suppriment la liaison.


            Peut-être y'a-t-il à un moment dans le Wizard un appel à SetValue qui annule la liaison OneWay. Par exemple : Le contrôle est chargé...Les propriétés liées sont résolues...Le template est appliqué, la propriété NextEnabled est définie à true parce que l'étape à l'index 0+1 existe; cette définition annule la liaison OneWay sur NextEnabled. Toute autre notification n'est donc plus répercutée.

            La question qui en découle, c'est "Comment on fait pour définir une propriété dans un contrôle sans casser la liaison??" o_O . Autant en WPF, il y a SetValue (même effet qu'en Silverlight) et SetCurrentValue (ça définit la valeur de la propriété sans interrompre les opérations de binding)...Autant en Silverlight, SetCurrentValue n'existe pas. Alors on fait comment si on doit définir une propriété à l'intérieur du contrôle?? o_O


            Je pense que tu as effectivement mis le doigt sur le problème :) Modifier directement la propriété NextEnabled (ou PreviousEnabled) depuis ton contrôle a pour effet de casser le binding avec le ViewModel.

            Malheureusement je n'ai pas la moindre idée de comment faire pour pouvoir modifier la valeur de ces propriétés tout en préservant les bindings et en répercutant ces changements sur leur source (càd les propriétés du viewmodel)... :euh: Je vais regarder de mon côté pour voir si je ne trouve pas d'indications là-dessus. En attendant si tu trouves une solution, ça m'intéresse ! :)

            Par rapport à ceci:

            Citation

            C'est un point noir avec lequel je suis en désaccord dans MVVM pur : Faire des "propriété" qui sont en fait des "action"...J'ai toujours trouvé ça aberrant...Je préfère faire une propriété Enabled, couplée avec un Trigger dans la vue qui appelle une méthode...Ca casse un peu le MVVM, mais ça me semble plus logique : Ce qui expose les données (les propriétés) garde son rôle de fournisseur de données et les méthodes gardent leur rôle d'action à entreprendre à certains moment et il n'y aucun mix des deux d'effectué.


            Je comprends ton point de vue. ;)
            Personnellement ça ne me dérange pas d'exposer des Commands (ou des InteractionRequests d'ailleurs) via des propriétés du ViewModel, car pour moi les propriétés du VM ne sont pas à voir comme des données à représenter mais bien comme des objets (plus généraux donc) exposés par le VM à l'attention de la vue (qui en fait ce qu'elle veut). Pour moi les données, les commandes et les requests sont tout autant d'objets qui constituent l'interface du ViewModel, et le plus facile pour manipuler cette interface en XAML est de l'exposer sous forme de propriétés. Donc... ^^
            (D'ailleurs, lorsque les actions ont des paramètres, ça devient rapidement difficile à gérer avec des triggers.)
            • Partager sur Facebook
            • Partager sur Twitter
              14 octobre 2011 à 16:35:59

              Citation : Strimy

              Le problème, c'est qu'un d'un côté tu veux un Binding OneWay, mais de l'autre, tu veux quand même modifier ta propriété depuis ton code, alors que c'est ton ViewModel qui est la source de ton Binding. Si tu veux pas casser ton Binding, il te faudrait donc modifier le ViewModel, et non la DP (sinon, et ça semble logique, ton Binding est cassé). Y'a une logique qui est incohérente en fait.


              Je pense que tu mélanges l'aspect "conception du contrôle" et l'aspect "usage du contrôle". Le bind OneWay ainsi que le ViewModel interviennent dans l'aspect "usage du contrôle" (le programme à proprement parler) et la modification de la propriété de dépendance à partir du code intervient dans l'aspect "conception du contrôle". La problématique semble clairement être le fait que, à l'appel de SetValue, les binding en OneWay/OneTime sont brisés. Dès lors, quand on est dans l'aspect de conception des contrôles...Comment on peut faire pour contourner ça? Ou comment on peut savoir qu'une propriété est bindée en OneWay/OneTime pour éviter, à l'intérieur du contrôle, de modifier des propriétés? Par ailleurs, ce qui me semble complexe, c'est de pouvoir concevoir des contrôles sans pouvoir en initialiser ses propriétés (même si après l'application du template, il est possible qu'un ViewModel puisse surcharger les valeurs d'initialisation du contrôle lors de l'usage du contrôle)...

              Citation : Orwell

              Je pense que tu as effectivement mis le doigt sur le problème :) Modifier directement la propriété NextEnabled (ou PreviousEnabled) depuis ton contrôle a pour effet de casser le binding avec le ViewModel.

              Malheureusement je n'ai pas la moindre idée de comment faire pour pouvoir modifier la valeur de ces propriétés tout en préservant les bindings et en répercutant ces changements sur leur source (càd les propriétés du viewmodel)... :euh: Je vais regarder de mon côté pour voir si je ne trouve pas d'indications là-dessus. En attendant si tu trouves une solution, ça m'intéresse ! :)


              C'est pas gagné...Peut-être est-ce corrigé dans Silverlight 5? En tous cas, je ne comprend pas trop pourquoi Microsoft à rendu la création de contrôle "possible pour n'importe quel cas" en WPF alors qu'en Silverlight, ça semble relever de l'imaginaire O_o...J'continuerais à chercher, j'espère aussi trouver une solution. Si quelqu'un en trouve une au passage...J'suis preneur :D
              • Partager sur Facebook
              • Partager sur Twitter
                14 octobre 2011 à 16:52:30

                Citation : Nisnor

                Je pense que tu mélanges l'aspect "conception du contrôle" et l'aspect "usage du contrôle". Le bind OneWay ainsi que le ViewModel interviennent dans l'aspect "usage du contrôle" (le programme à proprement parler) et la modification de la propriété de dépendance à partir du code intervient dans l'aspect "conception du contrôle". La problématique semble clairement être le fait que, à l'appel de SetValue, les binding en OneWay/OneTime sont brisés. Dès lors, quand on est dans l'aspect de conception des contrôles...Comment on peut faire pour contourner ça? Ou comment on peut savoir qu'une propriété est bindée en OneWay/OneTime pour éviter, à l'intérieur du contrôle, de modifier des propriétés? Par ailleurs, ce qui me semble complexe, c'est de pouvoir concevoir des contrôles sans pouvoir en initialiser ses propriétés (même si après l'application du template, il est possible qu'un ViewModel puisse surcharger les valeurs d'initialisation du contrôle lors de l'usage du contrôle)...


                Et donc, quel doit être la valeur dans ton ViewModel une fois que tu as changé la valeur de la DP ? Si ton VM est modifiée, c'est donc un Binding TwoWay, mais dans l'autre cas, ton Binding n'a plus aucun sens puisque la valeur de la source et de la cible ne sont plus synchronisée.

                Ceci dit (j'ai pas vérifié si c'était possible en SL), mais tu pourrais peut être mettre ton Binding en TwoWay avec un UpdateSourceTrigger à Explicit. De cette façon, la valeur de la DP ne remontera pas jusqu'au VM (à moins de faire un UpdateSource sur la BindingExpression), mais tu ne perdras pas le Binding en settant ta DP.
                • Partager sur Facebook
                • Partager sur Twitter
                  14 octobre 2011 à 19:17:39

                  Je suis d'accord pour dire qu'un binding OneWay n'a pas beaucoup de sens ici puisque tu veux maintenir la synchronisation entre la propriété de ton VM et la DP de ton contrôle (et que cette dernière peut changer indépendamment du VM). Cependant, donner un UpdateSourceTrigger Explicit ne changera strictement rien au problème, puisque son SetValue efface les bindings établis quelle que soit leur configuration.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    14 octobre 2011 à 21:36:11

                    Si le Binding est en TwoWay, il n'y a aucune raison qu'un SetValue casse le binding. J'ai fait le test en WPF et sur WP7, ca passe sans problème.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      15 octobre 2011 à 12:04:40

                      Citation : Strimy

                      Et donc, quel doit être la valeur dans ton ViewModel une fois que tu as changé la valeur de la DP ? Si ton VM est modifiée, c'est donc un Binding TwoWay, mais dans l'autre cas, ton Binding n'a plus aucun sens puisque la valeur de la source et de la cible ne sont plus synchronisée.


                      Ce que je voulais faire...C'est pouvoir avoir une propriété en lecture seule sur le ViewModel, permettant de fixer la valeur du contrôle attaché, sans avoir la contrainte de me soucier de la valeur de la propriété de dépendance à différentes étapes du cycle de vie du contrôle. Le ViewModel reste maître de cette propriété mais je ne veux pas que la propriété du ViewModel soit altérée par la vue puisque ce genre de modification ne m'intéresse pas.
                      C'est un peu comme si je voulais faire du "multi-binding" à sens unique, la première "source" étant le contrôle lui-même lors de son initialisation (cas où, à l'échelle du contrôle, je ne connais pas encore ce qui va être demandé après), la seconde source étant le ViewModel lors du reste du temps d'activité du contrôle.

                      Citation : Strimy

                      Ceci dit (j'ai pas vérifié si c'était possible en SL), mais tu pourrais peut être mettre ton Binding en TwoWay avec un UpdateSourceTrigger à Explicit. De cette façon, la valeur de la DP ne remontera pas jusqu'au VM (à moins de faire un UpdateSource sur la BindingExpression), mais tu ne perdras pas le Binding en settant ta DP.


                      Effectivement, j'y avais pas pensé...Merci pour cette astuce ^^ .

                      J'marque comme résolu...Cependant, si quelqu'un connait THE solution pour faire des SetCurrentValue en Silverlight...J'suis toujours preneur :D
                      • Partager sur Facebook
                      • Partager sur Twitter

                      [Silverlight]BindingMode.OneWay

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                      • Editeur
                      • Markdown