Partage
  • Partager sur Facebook
  • Partager sur Twitter

Liaison Model et ViewModel

c#

Sujet résolu
    10 octobre 2018 à 12:40:13

    Bonjour à tous,

    Je suis de retour après avoir essayé d'améliorer ma formation:euh:. J'ai repris le code de mon application (en MVVM C#) qui me sert à appliquer mes pauvres connaissances. Je crois que l'écriture est pus "propre"...

    Il reste une chose que je ne comprends pas et que je n'arrive pas à utiliser. Il s'agit de l'utilisation de INotifyPropertyChanged ou de RaisePropertyChanged.

    Lorsque l'utilisateur entre une nouvelle donnée dans un datagrid, un calcul est effectué et le résultat doit apparaître dans un textblock. Je n'arrive pas à avertir mon ViewModel d'un changement du Model. Je donne ci-dessous les différents fichiers:

    ViewModel:

    using GalaSoft.MvvmLight;
    using Compte_Bancaire.Models;
    using System.Collections.ObjectModel;
    using Compte_Bancaire.Utils;
    
    namespace Compte_Bancaire.ViewModel
    {
        public class UcEntreeViewModel: ViewModelBase
        {
            #region
            public int CleEntree;
            #endregion
    
            #region Constructeur
            public UcEntreeViewModel()
            {
                ItemEntree = new ObservableCollection<CollectionEntree>(ServiceOperationMensuelle.GetOperationEntree());
                Calcul calcul = new Calcul();
                ResultatEntree = new ObservableCollection<Calcul>()
                {
                new Calcul{SumEntree=calcul.SumEntree }
                };
                ResultatEntree.CollectionChanged += ResultatEntree_CollectionChanged;
            }
    
            private void ResultatEntree_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                Calcul calcul = new Calcul();
                ResultatEntree = new ObservableCollection<Calcul>()
                {
                new Calcul{SumEntree=calcul.SumEntree }
                };
            }
            #endregion
    
            #region Propriétés
            private ObservableCollection<CollectionEntree> itemEntree;
            public ObservableCollection<CollectionEntree>ItemEntree
            {
                set
                {
                    itemEntree = value;
                    RaisePropertyChanged("ItemEntree");
                }
                get
                {
                    return itemEntree;
                }
            }
    
            private CollectionEntree itemSelectionne;
            public CollectionEntree ItemSelectionne
            {
                set
                {
                    itemSelectionne = value;
    
                    if(itemSelectionne!=null)
                    {
                        CleEntree = itemSelectionne.CleEntree;
                    }
                    RaisePropertyChanged("ItemSelectionne");
                }
                get
                {
                    return itemSelectionne;
                }
            }
    
            private ObservableCollection<Calcul> resultatEntree;
            public ObservableCollection<Calcul> ResultatEntree
            {
                set
                {
                    if (resultatEntree == value)
                    {
                        ResultatEntree.CollectionChanged -= ResultatEntree_CollectionChanged;
                    }
                    if (resultatEntree != value)
                    {
                        resultatEntree = value;
                        ResultatEntree.CollectionChanged += ResultatEntree_CollectionChanged;
                    }
                    RaisePropertyChanged("ResultatEntree");
                }
                get
                {
                    return resultatEntree;
                }
            }
            #endregion
        }
    }
    

    Model:

    using System.ComponentModel;
    using System.Linq;
    
    namespace Compte_Bancaire.Models
    {
        public class Calcul : INotifyPropertyChanged
        {
            //public event PropertyChangedEventHandler PropertyChanged;
    
            public Calcul()
            {
                GetTotalEntree();
            }
    
            #region Propriétés
            private decimal sumEntree = 0;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public decimal SumEntree
            {
                set
                {
                    if (sumEntree != value)
                    {
                        sumEntree = value;
                        if (PropertyChanged != null)
                        {
                            PropertyChanged(this, new PropertyChangedEventArgs("NomAnnee"));
                        }
                    }
            }
                get
                {
                    return sumEntree;
                }
            }
            #endregion
    
            #region Méthodes pour les différents calculs
            public void GetTotalEntree()
            {
                BddContext Bdd = new BddContext("BddOperationMensuelle.mdf");
                decimal totalEntree = Bdd.Entrees.Select(c => c.Credit).Sum();
                SumEntree = totalEntree;
            }
            #endregion
    }
    }
    

    Je pense ne pas savoir utiliser l’événement PropertyChanged. Pourriez-vous m'aider SVP. Merci

    • Partager sur Facebook
    • Partager sur Twitter
      11 octobre 2018 à 20:41:29

      Ta propriété s'appelle "SumEntree", et tu notifie que la propriété "NomAnnee" à changé. :)
      • Partager sur Facebook
      • Partager sur Twitter
        11 octobre 2018 à 23:22:02

        Bonsoir Nisnor, merci d'avoir décortiqué mon code.

        J'ai vu cette erreur et je l'ai corrigée mais malgré tout ça ne fonctionne pas. Je vais tenté autre chose et je te tiens au courant. Par contre situ as une idée ou si tu a du temps pour m'expliquer un peu InotifyPropertyChanged (avec des mots simples) je suis preneur.

        Bonne soirée

        • Partager sur Facebook
        • Partager sur Twitter
          12 octobre 2018 à 10:07:20

          Tout est dans le nom de l'interface en fait.

          Souvent, en orienté objet, il ne faut pas chercher bien loin pour comprendre ce que c'est censé faire.

          En l'occurence, INotifyPropertyChanged :

          • "I", c'est une sorte de convention C#, ça indique que c'est une interface. Rien de plus. Conceptuellement, tu sais que ce type d'élément ne pourra pas être instancié et que les membres exposés seront forcément implémentés par "autre chose" (souvent, une classe) que tu ne connais pas. Là dessus, je t'invite à lire ce que tu peux trouver au sujet des interfaces. Pour faire simple : Tu sais ce à quoi tu auras accès, mais tu ne sais absolument rien de "qui" va te fournir ce à quoi tu auras accès.
          • "Notify", qui laisse supposer que les éléments qui vont implémenter cette interface auront le rôle de "générateur d'événement".
          • "PropertyChanged", à mettre en rapport avec le notify juste avant. Les composants qui vont implémenter cette interface auront le rôle de "générateur d'événement", chacun des événements contenant surement des informations pour savoir quelles sont les propriétés qui changeront de valeur.

          En WPF, cette interface est utilisée par le moteur de binding du framework pour capter les changements des éléments sur lesquels le code de l'interface utilisateur (généralement un code XAML) sera lié (avec les fameux "{Binding ....}" en XAML.

          Tout est contextuel. WPF ne fait pas exception à cette règle. La propriété maitresse des éléments WPF est le DataContext. Par défaut, les bindings vont aller piocher leurs données directement de ce qui se trouvera dans DataContext (qui est elle même une propriété pouvant être liée). Il y a une relation 1-1 entre le contexte UI et un contexte objet. Exemple :

          //Ca, c'est mon contexte de données.
          //Dans ce contexte, j'ai une seule propriété : MaProperty
          public class MaClass : INotifyPropertyChanged
          {
             public String MaProperty { /*Le code get/set ici */ }
             /* Le code pour faire les propertyChanged ici */
          }
          <Window /*Les imports de namespace ici*/
                  xmlns:myApp="/*LeNamespace de l'application ici*/">
             <Window.DataContext>
                <!--
          Voila. Ici, j'établi une relation directe entre mon contexte-métier et la UI. Avec cette syntaxe, le moteur WPF va simplement faire un "instanceWindow.DataContext = new MaClass();"
                -->
                <myApp:MaClass/>
             </Window.DataContext>
             <!--
          Ci-dessous, un binding.
          Le sens (Mode=) est configuré pour être unidirectionnel. Ca ne sera que dans le sens {InstanceMaClass]->[UI], parce qu'un TextBlock se contente d'afficher une donnée, pas de saisir cette donnée.
          La propriété utilisée sera "MaProperty", qui devra impérativement se trouver publiquement disponible dans ce qui se trouve dans DataContext.
             -->
             <TextBlock Text="{Binding MaProperty, Mode=OneWay}"/>
          </Window>

          A partir de maintenant, à chaque fois que je déclencherais l'événement PropertyChanged avec le nom "MaProperty", WPF va capter ce changement, récupérer la valeur retournée par le get de MaProperty, vérifier que cette valeur est bien différente de celle qui est actuellement affichée et si c'est le cas, ça va effacer la zone occupée par le TextBlock et régénérer son contenu en ré-effectuant une passe de layout des contrôles & une passe de rendu.

          Comme tu le vois aussi, c'est tout séquentiel. Ce qui signifie que si, à un moment ou à un autre, tu inverses les actions ou en oublie une, ça ne peut pas fonctionner.

          En fait, je pense que tu as déjà compris ce mécanisme, mais que tu "t'inventes" une sorte de blocage pour tenter de justifier une simple erreur de nommage. Du code que tu as posté là, à part l'erreur de nom de propriété, c'est juste. Si ça ne fonctionne pas, c'est que tu as encore une erreur...Mais, vu que tu n'as pas posté le code XAML (ou C# où tu réalise le binding, mais je doute que tu l'ai fait ainsi ^^), on ne pourra te pointer les erreurs que sur la moitié du système.

          • Partager sur Facebook
          • Partager sur Twitter
            12 octobre 2018 à 16:07:06

            Merci NISNOR d'avoir pris le temps de me fournir ces super explications. Elles me confortent dans ce que je pensais avoir compris (super je ne suis pas complètement bouché...).

            Pour assurer voici comment je comprends le déroulement des opérations:

            - Dans la classe Calcul INotifyPropertyChanged lève l'événement PropertyChanged sur SumEntree lors d'un changement de valeur

            - Ensuite (j'ai lu) l'événement PropertyChanged est automatiquement levé dans le viewModel sur ResultatEntree.

            - Si le binding est correct la valeur du textblock est changé.

            J'espère que j'ai tout bon?

            Mais le problème est que lorsque je place des points d'arrêt dans mon code, lors du changement de la valeur de SumEntree il n'y a pas de retour dans le ViewModel et par conséquent pas de changement de valeur dans le TextBlock (j'ai corrigé l'erreur de nommage). Là je ne comprends pas pourquoi.

            Et en plus j'ai essayé en déclarant ResultatEntree comme un nouvel objet de type Calcul mais ça ne fonctionne pas plus. Cette technique peut elle fonctionner?

            Je te fournis les codes manquants à savoir le xaml et la classe CollectionEntree qui lance les calculs.

            Encore merci à toi, ton aide est précieuse.

            Xaml

            <Window 
                    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:local="clr-namespace:Compte_Bancaire"
                    xmlns:ViewModel="clr-namespace:Compte_Bancaire.ViewModel" x:Name="Window" x:Class="Compte_Bancaire.MainWindow"
                    mc:Ignorable="d"
                   
                    Title="COMPTE BANCAIRE" Height="450" Width="800" WindowState="Maximized" FontFamily="Comic Sans MS"  WindowStartupLocation="CenterScreen" 
                    Icon="/Compte Bancaire;component/Images/compte-bancaire.jpg" 
                    DataContext="{Binding Source={StaticResource ServiceLocator} ,Path=MainViewModel}">
            
                <Window.Resources>
            
                    <SolidColorBrush x:Key="TextResultat" Color="#FF0D44A6"/>
                    <!--<DataTemplate x:Key="MoisAnneeTemplate">
                        <StackPanel>
                            <TextBlock Text="{Binding XPath=@NomMois}"/>
                        </StackPanel>
                    </DataTemplate>-->
            
                </Window.Resources>
            
            
            
                <Grid>
                    <TabControl x:Name="TabControlGeneral" Margin="0" Foreground="#FF406097" BorderBrush="{DynamicResource TextColor}" FontStyle="Italic">
                        <TabItem x:Name="TabItemFeuilleCompte" Header="FEUILLE DE COMPTE" Foreground="{DynamicResource TextColor}">
            
                            <Grid>
                                <Grid.Background>
                                    <ImageBrush  Stretch="UniformToFill" Opacity="0.2" ImageSource="/Compte Bancaire;component/Images/compte-bancaire.jpg"/>
                                </Grid.Background>
            
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="200"/>
                                    <ColumnDefinition Width="400"/>
                                    <ColumnDefinition Width="300*"/>
                                </Grid.ColumnDefinitions>
            
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="40"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="10"/>
            
                                </Grid.RowDefinitions>
            
            
                                <StackPanel x:Name="BrdMenu" Grid.RowSpan="3" Grid.Row="1" Background="{DynamicResource FondBarre}">
            
                                    <Expander x:Name="ExpandAnnee" Header="ANNEE" Style="{Binding Mode=OneWay, Source={StaticResource ExpanderMenu}}"  Foreground="{DynamicResource TextColor}" Margin="5" FontStyle="Italic" FontSize="11" IsExpanded="{Binding ExpandAnnee}" Cursor="Hand">
                                        <StackPanel>
                                            <ListBox x:Name="ListAnnee" FontWeight="Normal" Foreground="{DynamicResource TextColor}" Background="{x:Null}" BorderBrush="{x:Null}" Margin="0,0,5,0" Padding="0" ItemsSource="{Binding Annee}" DisplayMemberPath="NomAnnee" SelectedItem="{Binding AnneeSelectionne}" Cursor="Hand" />
                                        </StackPanel>
                                    </Expander>
            
                                    <Expander x:Name="ExpandMois" Header="MOIS" Style="{Binding Mode=OneWay, Source={StaticResource ExpanderMenu}}"  Foreground="{DynamicResource TextColor}" Margin="5,0,5,5" FontSize="11" FontStyle="Italic" Cursor="Hand" IsExpanded="{Binding ExpandMois}" >
                                        <StackPanel >
                                            <ListBox x:Name="ListMois" FontWeight="Normal" Foreground="{DynamicResource TextColor}" Background="{x:Null}" BorderBrush="{x:Null}" Margin="0,0,5,0" Padding="0"  Cursor="Hand" ItemsSource="{Binding Mois}" DisplayMemberPath="Nom" SelectedItem="{Binding MoisSelectionne}" />
                                        </StackPanel>
                                    </Expander>
            
                                    <Button x:Name="BtnTransfert" Background="{x:Null}" BorderBrush="{DynamicResource TextColor}" Foreground="{DynamicResource TextColor}" Margin="0,10"  Height="30" Width="120" Style="{DynamicResource ButtonStyle1}" Cursor="Hand" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center">
                                        <StackPanel Orientation="Horizontal">
                                            <Image  Width="25" Height="25" HorizontalAlignment="Center" Margin="0,0,5,0" Stretch="UniformToFill" VerticalAlignment="Center" Source="/Compte Bancaire;component/Images/Tranfert.ico" ></Image>
                                            <Label Foreground="{DynamicResource TextColor}" Content="Transfert" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" ></Label>
                                        </StackPanel>
                                    </Button>
            
                                    <GroupBox x:Name="GroupResultat" Margin="5" Header="OPERATIONS" BorderThickness="1" SnapsToDevicePixels="True" ClipToBounds="True" BorderBrush="{DynamicResource TextColor}">
            
                                        <StackPanel x:Name="StackResultats">
            
                                            <GroupBox x:Name="GroupReliquat" Header="{Binding TitreReliquat}" Margin="5,5,5,0" Foreground="{DynamicResource TextResultat}" BorderBrush="{DynamicResource TextColor}" BorderThickness="2">
                                                <GroupBox.Background>
                                                    <SolidColorBrush Color="#FF20B608" Opacity="0.15"/>
                                                </GroupBox.Background>
                                                <TextBlock x:Name="TextReliquat" FontSize="16" Margin="5,10" Text="1000" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextValeurPositive}" FontStyle="Italic" FontWeight="Bold"/>
                                            </GroupBox>
            
                                            <GroupBox x:Name="GroupEntree" FontSize="11" Header="Total des entrées" Margin="5,10,5,0" Foreground="{DynamicResource TextColor}" BorderBrush="{DynamicResource TextColor}" >
                                                <TextBlock x:Name="TextEntree" FontSize="14" Margin="5,10" Text="{Binding ResultatEntree, StringFormat=0.00;#}"  HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextColor}" FontStyle="Italic" Background="{x:Null}" >
                                                    <TextBlock.DataContext>
                                                        <ViewModel:UcEntreeViewModel/>
                                                    </TextBlock.DataContext>
                                                </TextBlock>
                                            </GroupBox>
            
                                            <GroupBox x:Name="GroupMensualisation" FontSize="11" Header="Total des mensualisations" Margin="5,5,5,0" BorderBrush="{DynamicResource TextColor}" >
                                                <TextBlock x:Name="TextMensualisation" FontSize="14" Margin="5,10" Text="1000" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextColor}" FontStyle="Italic"/>
                                            </GroupBox>
            
                                            <GroupBox x:Name="GroupDepense" FontSize="11" Header="Total des dépenses" Margin="5,5,5,0" BorderBrush="{DynamicResource TextColor}" >
                                                <TextBlock x:Name="TextDepense" FontSize="14" Margin="5,10" Text="1000" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextColor}" FontStyle="Italic"/>
                                            </GroupBox>
            
                                            <GroupBox x:Name="GroupReste" Header="{Binding TitreSolde}" Margin="5,10,5,0" Foreground="{DynamicResource TextResultat}" BorderBrush="{DynamicResource TextColor}" BorderThickness="3" FontWeight="Bold" >
                                                <GroupBox.Background>
                                                    <SolidColorBrush Color="#FFF7210B" Opacity="0.15"/>
                                                </GroupBox.Background>
                                                <TextBlock x:Name="TextSolde" FontSize="20" Margin="5,10" Text="-25" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource TextValeurNegative}" FontStyle="Italic" FontWeight="Bold"/>
                                            </GroupBox>
            
                                        </StackPanel>
                                    </GroupBox>
            
                                </StackPanel>
            
                                <Border x:Name="BrdIconeTitre" >
                                    <Border.Background>
                                        <SolidColorBrush Color="#FFA6BCCF" Opacity="0.8"/>
                                    </Border.Background>
            
                                    <Image Width="40" Height="40" Margin="5,0,20,0" Source="/Compte Bancaire;component/Images/FeuilleCompte.png"/>
            
                                </Border>
            
                                <Border x:Name="BrdTitre" Grid.Column="1" Grid.ColumnSpan="3" >
                                    <Border.Background>
                                        <SolidColorBrush Color="#FFA6BCCF" Opacity="0.8"/>
                                    </Border.Background>
            
                                    <Label x:Name="LabTitre" Foreground="{DynamicResource TextColor}" Margin="5,0" FontSize="20" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" IsEnabled="False" Content="{Binding TitrePage}"/>
            
                                </Border>
            
                                <Border x:Name="BrdEntree" Grid.Row="1" Grid.Column="1" >
                                    <Border.Background>
                                        <SolidColorBrush Color="#FFA6BCCF" Opacity="0.2"/>
                                    </Border.Background>
                                    <ContentControl x:Name="ContentEntree" Content="{Binding FeuilleEntree}"/>
                                </Border>
            
                                <Border x:Name="BrdMensualisation" Grid.Row="2" Grid.Column="1">
                                    <Border.Background>
                                        <SolidColorBrush Color="#FFA6BCCF" Opacity="0.2"/>
                                    </Border.Background>
                                    <ContentControl x:Name="ContentMensualisation" Content="{Binding FeuilleMensualisation}" />
                                </Border>
            
                                <Border x:Name="BrdDepense" Grid.Row="1" Grid.Column="2" Grid.RowSpan="2">
                                    <Border.Background>
                                        <SolidColorBrush Color="#FFA6BCCF" Opacity="0.2"/>
                                    </Border.Background>
                                    <ContentControl x:Name="ContentDepense" Content="{Binding FeuilleDepense}" />
                                </Border>
            
                            </Grid>
            
                        </TabItem>
                        <TabItem x:Name="TabItemCheque" Header="CHEQUES" Foreground="{DynamicResource TextColor}">
                            <Grid>
                                <Grid.Background>
                                    <ImageBrush  Opacity="0.3" Stretch="UniformToFill" ImageSource="/Compte Bancaire;component/Images/cheque3.jpg"/>
                                </Grid.Background>
            
                            </Grid>
                        </TabItem>
                    </TabControl>
                </Grid>
            </Window>

            Classe CollectionEntree

            using GestionComptes.DAL;
            using System.ComponentModel;
            using System.Linq;
            
            namespace Compte_Bancaire.Models
            {
                public class CollectionEntree : INotifyPropertyChanged
                {
                    public event PropertyChangedEventHandler PropertyChanged;
            
                    #region Variables
                    private string NomBase= "BddOperationMensuelle.mdf";
                    #endregion
            
                    #region Constructeur
                    public CollectionEntree()
                    {
                        GetTotalEntree();
                    }
                    #endregion
            
                    #region Propriétés
            
                    private bool changementEntree = false;
                    public bool ChangementEntree
                    {
                        set
                        {
                            changementEntree = value;
                            if (PropertyChanged != null)
                            {
                                OnPropertyChanged("ChangementEntree");
                            }
                        }
                        get
                        {
                            return changementEntree;
                        }
                    }
            
                    private bool validEntree ;
                    public bool ValidEntree
                    {
                        set
                        {
                            validEntree = value;
                            if (PropertyChanged != null)
                            {
                                OnPropertyChanged("ValidEntree");
                            }
                        }
                        get
                        {
                            return validEntree;
                        }
                    }
            
                    private string operationEntree;
                    public string OperationEntree
                    {
                        set
                        {
                            operationEntree = value;
                            if (PropertyChanged != null)
                            {
                                OnPropertyChanged("OperationEntree");
                            }
                        }
                        get
                        {
                            return operationEntree;
                        }
                    }
            
                    private decimal creditEntree;
                    public decimal CreditEntree
                    {
                        set
                        {
                            creditEntree = value;
                            if (PropertyChanged != null)
                            {
                                OnPropertyChanged("CreditEntree");
                            }
                        }
                        get
                        {
                            return creditEntree;
                        }
                    }
                    private int cleEntree;
                    public int CleEntree
                    {
                        set
                        {
                            cleEntree = value;
                            if (PropertyChanged != null)
                            {
                                OnPropertyChanged("CleEntree");
                            }
                        }
                        get
                        {
                            return cleEntree;
                        }
                    }
            
                    private decimal sumEntree = 0;
                    public decimal SumEntree
                    {
                        set
                        {
                            if (sumEntree != value)
                            {
                                sumEntree = value;
                                if (PropertyChanged != null)
                                {
                                    PropertyChanged(this, new PropertyChangedEventArgs("SumEntree"));
                                }
                            }
                        }
                        get
                        {
                            return sumEntree;
                        }
                    }
                    #endregion
            
                    #region Méthodes
                    private void OnPropertyChanged(string propertyName)
                    {
                        //if (PropertyChanged != null)
                        //    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            
                        BddContext BddOperationMensuelle = new BddContext(NomBase);
            
                        // Enregistrement d'une nouvelle opération
                        if (CleEntree==0 && OperationEntree!=null && CreditEntree!=0)
                        {
                            var NewEntree = new Entree();
                            {
                                NewEntree.Validation = ValidEntree;
                                NewEntree.Operation = OperationEntree;
                                NewEntree.Credit = CreditEntree;
                            }
                            BddOperationMensuelle.Entrees.Add(NewEntree);
                            BddOperationMensuelle.SaveChanges();
            
            
                        }
                        GetTotalEntree();
                        
                        
                    }
                    #endregion
            
                    #region Méthodes pour les différents calculs
                    public void GetTotalEntree()
                    {
                        BddContext Bdd = new BddContext("BddOperationMensuelle.mdf");
                        decimal totalEntree = Bdd.Entrees.Select(c => c.Credit).Sum();
                        SumEntree = totalEntree;
                    }
                    #endregion
                }
            }




            • Partager sur Facebook
            • Partager sur Twitter
              14 octobre 2018 à 20:54:07

              Non, ce n'est pas automatique. C'est à toi de lever l'événement. L'interface n'est qu'un contrat, elle n'est là que pour indiquer que "un truc" (mais on ne sait pas quoi) va - au moins - exposer un événement, appelé PropertyChanged.

              • Partager sur Facebook
              • Partager sur Twitter
                14 octobre 2018 à 23:43:33

                D'accord, ce n'est pas automatique mais je pense que j'ai bien indiqué dans le code ce qu'il fallait faire en cas de changement de la propriété SumEntree. Si ce n'est pas le cas je n'ai pas compris comment faire.

                J'ai apporté quelques petites corrections mais ça n'a pas fait avancer grand chose.

                - A la ligne 75 de la classe UcEntreeViewModel j'ai remplacé "if (resultatEntre == value)" par "if (value == resultatEntree) ce qui me semble plus correct.

                - A la ligne 84 du Xaml le binding est devenu "Text="{Binding ResultatEntree/SumEntree, StringFormat=0.00;#,ConverterCulture=fr}""

                Peux-tu comprendre et m'expliquer, s'il te plait, pourquoi le programme ne repasse pas par le ViewModel après un changement de la propriété SumEntree.

                Merci de me consacrer un peu de ton temps.

                • Partager sur Facebook
                • Partager sur Twitter
                  15 octobre 2018 à 12:43:30

                  Je n'ai pas posté plus la dernière fois parce que j'ai été pris sur autre chose.

                  J'allais ajouter : SumEntree n'est jamais utilisée dans le code XAML.

                  N'hésite pas à utiliser les outils Microsoft qui sont assez puissant pour débugguer les problèmes :

                  • Placer des points d'arrêts dans le code du ViewModel ou du Model à des endroits clés; Notamment dans les get & set de tes properties, ça peut être bien pour déterminer si tu as bien câblé les choses. Par exemple : Placer un point d'arrêt dans le set de ton SumEntree, lancer l'application en debug, réaliser des actions puis voir si ton point d'arrêt est atteint (vérification des câblages correct) et si oui, à quel moment (vérification du séquençage des actions. La stacktrace pourra t'aider à voir à quel moment ton séquençage n'est plus bon).
                  • Regarder ce que la console de debug te sort. WPF est, de base, verbeux pour te sortir des messages assez explicite en cas d'erreur de binding, par exemple, quand tu fais un binding sur une propriété qui n'existe pas dans un DataContext non null. La verbosité des messages WPF peut également être configurée pour t'en cracher d'avantage.

                  Dans ton code XAML, j'ai vu se balader des redéfinitions de DataContext en plein milieu du code (ligne 86 par ex). C'est le meilleur moyen pour perdre les pédales. La propriété DataContext fait partie des propriétés contaminante, c'est à dire qu'un enfant va hériter de la valeur de son parent, tant qu'elle n'est pas explicitement définie à quelque chose (soit une valeur directe, soit un binding).

                  Il ne faut pas essayer de faire "trop complexe" pour quelque chose de simple. Si ton code est compliqué à dérouler, c'est qu'il est très probablement soit faux, soit trop complexe pour rien. Là, sur cette redéfinition, tu te retrouve avec "une instance" de UcViewModel qui se balade "au milieu de nulle part" et qui risquerait d'etre référencée nulle part ailleurs que dans le DataContext de ton TextBlock...Autrement dit : Il s'agirait d'une sorte d'instance zombie totalement inutile.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    15 octobre 2018 à 13:02:51

                    Merci pour ta réponse. Je vais essayer les outils dont tu me parles et que je n'ai jamais utilisés.

                    Pour le changement de datacontext j'ai réécrit cette partie et supprimé le changement mais ça n'a rien donné.

                    Je te tiens au courant.

                    Petite question supplémentaire: Pourquoi le forum me demande d'attendre 24 heures avant de renvoyer un message?

                    • Partager sur Facebook
                    • Partager sur Twitter
                      15 octobre 2018 à 23:27:08

                      Bonsoir Nisnor, 

                      en suivant tes conseils (toujours efficaces) j'ai fait trois choses:

                      - J'ai transféré toute la partie concernant ResultatEntree dans le MainViewModel ce qui me permet de ne plus changer de DataContext.

                      - J'ai changer le binding du textblock en Text="{Binding ResultatEntree/SumEntree, StringFormat=0.00;#,ConverterCulture=fr}"

                      - J'ai utilisé le debugger et je me suis aperçu que la ligne 27 du model Calcul répondait toujours null à PropertyChanged.

                      Et là je ne sais pas faire car je pense ne pas avoir compris le fonctionnement. Je m'explique en essayant d'être clair.

                      Pour moi lorsque j'écris: if (PropertyChanged != null) c'est que je veux savoir si queque chose est abonné à l’événement. Donc de le cas de mon programme personne puisque toujours null.

                      Je pensais que la ligne du ViewModel: ResultatEntree.CollectionChanged += ResultatEntree_CollectionChanged; permettait de s'abonner à l'événement mais ce n'est pas ça. Donc j'ai rien compris...

                      D'ailleurs cette ligne lorsqu'elle est effectuer n'envoie pas le programme sur la méthode du même nom, autre incompréhension pour moi.

                      Peux tu m'expliquer et m'aiguiller sur ce qu'il faut faire.

                      Merci d'avance

                      • Partager sur Facebook
                      • Partager sur Twitter
                        16 octobre 2018 à 9:47:37

                        M'est d'avis que tu devrais laisser ce projet de côté pour le moment et refaire des projets plus simple, pour bien comprendre les interactions entre les différents composants.

                        Par exemple : Dans un premier temps, faire une application qui sert à rien, qui affiche juste 2 ou 3 TextBox et qui répercute les changements dans des TextBlock par exemple. A la limite, si tu fais ça, poste ton code ici et dis nous comment t'explique que ça marche (ou pas).

                        Ensuite, faire une appli qui affiche une liste d'éléments, avec 2 boutons : un Add et un Remove. Pour le moment, rien lié à l'édition des éléments, juste afficher la liste. De la même façon, poste le code ici et dis nous comment t'explique que ça fonctionne (ou pas).

                        Enfin, en restant sur l'appli précédente, rajoute, dans une partie de ta vue, des champs permettant d'éditer l'entrée sélectionnée dans la liste. Et comme toujours, poster le code ici et dis nous comment t'explique que ça fonctionne (ou pas).

                        A travers de ces 3 cas, tu devrais monter dans la complexité fonctionnelle...mais - si tu as bien compris comment ça marche - pas nécessairement accroitre la complexité technique. Ces 3 projets devraient pouvoir tenir dans 2 fichiers de code source : Le code XAML de la MainWindow et le code C# du métier. Si je dis ça, c'est parce que je vois aussi des références à un "Locator". Or, cette notion de locator, ce n'est pas quelque chose que tu aurais forcément fait vu ton niveau. C'est un pattern qu'on retrouve dans certains Framework MVVM populaire tel que MVVMLight. Pour démarrer, tu devrais te passer de ce genre de Framework. Ces outils sont très bien pour accélérer le travail...Mais si tu ne comprends pas la base, ça va juste te faire cache-misère et tu seras bloqué de la même façon que tu l'es aujourd'hui.

                        Edit : Les explications devront être faites avec les bons termes, bien sûr ^^ . Parce que j'ai l'impression que, même sans parler de WPF, il te manque quelques notions de base de C#/.NET classique.

                        -
                        Edité par Nisnor 16 octobre 2018 à 9:51:50

                        • Partager sur Facebook
                        • Partager sur Twitter
                          16 octobre 2018 à 10:41:04

                          Merci Nisnor pour ta réponse. 

                          Les applications dont tu me parles je les ai déjà faites en lisant des formations disponibles sur le net. Je pensais avoir compris en gros le fonctionnement.

                          J'avoue que je me forme seul, que je ne passe pas tout mon temps avec C# et que je fais ça pour le plaisir. Donc je ne suis pas à la pointe de tout sur le vocabulaire (il y en a beaucoup) et sur toute les ficelles de la programmation (le chantier est assez énorme).

                          Pour le vocabulaire, je sais que c'est lourd de reprendre régulièrement les façons de s'exprimer. Mais j'étais prof et j'ai toujours pensé que la pédagogie consistait en autre à des redites régulières. Mais soit certain que je te présente toutes mes excuses pour ces lacunes et que j'accepte toutes les rectifications.

                          J'utilise MVVM light après avoir fait à l'aide d'un tuto une application en MVVM sans pattern. Je trouve son utilisation assez sympa. Pour le Locator, je l'utilise juste pour fournir les DataContext correspondants à chaque Vue. Je ne trouve pas cela trop compliqué. Mais s'il y a d'autres utilisations là je ne connais pas.

                          Pour ma part, même si le projet paraît un peu lourd, je l'effectue morceau par morceau (un gros problème n'est qu'une suite de petits problèmes). Il me semble qu'il est plus sympa de travailler sur une application personnelle.

                          Pour l'instant je suis bloqué par l'avertissement de la vue d'un changement de propriété dans le model dans un cas peut être un peu plus compliqué qu'à l'habitude. Mais il faut bien essayer d'avancer...

                          Donc si tu penses que c'est inintéressant et que tu n'a pas le temps de me donner des explications je le comprends très bien. Dans le cas contraire je trouverai ça vraiment sympa.

                          Merci de me tenir au courant et en espérant avoir encore tes conseils qui franchement me permettent d'avancer (même si ça ne se voit pas)

                          J'espère à plus 

                          • Partager sur Facebook
                          • Partager sur Twitter
                            16 octobre 2018 à 12:31:59

                            Je pense que @Nisnor essaye d'initier en vous une interrogation socratique et pas à vous noter.

                            En vous demandant d'expliquer ce que vous avez compris du Databinding dans un contexte "simple" il pourra voir où ça pêche. ;)

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                              16 octobre 2018 à 12:42:10

                              Merci bacelar pour cette réponse. Je n'ai jamais pensé que Nisnor voulait me noter. Je ne pense pas que le forum soit là pour ça. 

                              Je remercie Nisnor de passer du temps à répondre à mes différentes questions. Je trouve ça extrêmement sympa de sa part.

                              Je travaille en parallèle et je cherche les raisons de mes problèmes.

                              En autre dans cette application je ne pense pas que Databinding soit en cause. Je viens de m'apercevoir qu'il pourrait s'agir d'une gestion d'un Datagrid en MVVM. En effet il faut, je pense, que je sache qu'elle est la colonne sélectionnée. Dans ce que j'ai écrit il n'y a une surveillance que sur la première sélection (quelque soit la colonne du Datagrid). Moi j'ai besoin d'avoir une surveillance de toutes les colonnes car les calculs que je veux effectuer s'effectuent sur la seconde colonne.

                              Je vais chercher...

                              Encore merci et que Nisnor ne soit pas inquiet bien au contraire. 

                              • Partager sur Facebook
                              • Partager sur Twitter
                                17 octobre 2018 à 13:27:53

                                Bonjour Nisnor;

                                C'est vrai que je ne suis pas trop avancé en C# et donc je ne me pose surement pas les bonnes questions. C'est du manque d'expérience et d'automatisme. Donc bien évidemment, malgré tous mes essais, je tourne complètement en rond.

                                Après étude du comportement de ma solution en mode débogage, le programme ne revient pas dans le ViewModel après l'enregistrement de mes données et l'exécution d'un nouveau calcul de la valeur de SumEntree.

                                Pourrais-tu me donner une piste? Où dois je intervenir?

                                J'espère que tu me donneras une réponse.

                                D'avance merci.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  17 octobre 2018 à 23:53:44

                                  Je t'invite à étudier cette application-exemple, à lire les commentaires, à la copier-coller dans un projet-exemple et debugger en pas-à-pas pour voir "par où ça passe" :

                                  MainWindow.xaml :

                                  <!-- Ca, c'est le code descriptif de mon IHM -->
                                  <Window x:Class="WpfApp1.MainWindow"
                                          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:local="clr-namespace:WpfApp1"
                                          mc:Ignorable="d" x:Name="root"
                                          Title="MainWindow" Height="350" Width="525">
                                      <!--
                                      Les 3 lignes ci-dessous seraient strictement équivalente à un code C# 
                                      dans lequel monInstanceMainWindow serait égal à new MainWindow(), donc à une instance de type MainWindow :
                                      monInstanceMainWindow.DataContext = new MainWindowViewModel();
                                      -->
                                      <Window.DataContext>
                                          <local:MainWindowViewModel/>
                                      </Window.DataContext>
                                      <!-- A partir d'ici, le contexte de ma MainWindow, c'est une instance de MainWindowViewModel -->
                                      
                                      <!--
                                      Les composants UI qui héritent de Window font partie de ces contrôles à contenu unique.
                                      Il exposent généralement une propriété Content ou Child, qui reflète le contenu visible
                                      Comme une UI peut être composée de plusieurs choses, il y a aussi les contrôle à contenu multiple.
                                      Ces contrôles apportent généralement une logique de positionnement. On a la Grid (layout en forme de tableau à la Excel),
                                      la StackPanel (layout en pile horizontale ou verticale), la UniformGrid (layout en tableau où toutes les cellules font la même taille),
                                      le DockPanel (layout où certains éléments sont collés sur des bords)
                                      -->
                                      <Grid>
                                          <Grid.ColumnDefinitions>
                                              <ColumnDefinition Width="0.40*"/>
                                              <ColumnDefinition Width="0.60*"/>
                                          </Grid.ColumnDefinitions>
                                  
                                          <!--
                                          Première partie de mon IHM : Afficher une liste des dépenses, avec la possibilité d'en ajouter ou d'en supprimer
                                          -->
                                          <Grid Grid.Column="0">
                                              <!-- Oui, un conteneur multiple peut en contenir un autre, pour faciliter le design des interfaces -->
                                              <Grid.RowDefinitions>
                                                  <RowDefinition Height="Auto"/>
                                                  <!-- Auto = la dimension est calculée au minimum pour que les contrôles enfants qui s'y trouvent aient de quoi s'afficher -->
                                                  <RowDefinition Height="*"/>
                                                  <!-- * = L'inverse d'Auto. Tout l'espace disponible est occupé, même si les contrôles enfant ne l'utilisent pas -->
                                                  <RowDefinition Height="Auto"/>
                                              </Grid.RowDefinitions>
                                              <TextBlock Grid.Row="0" Text="Liste des dépenses :"/>
                                              <!-- 
                                              Premier binding en OneTime, parce que je sais que l'instance de ma collection ne changera pas, pas plus que le DataContext d'ailleurs.
                                              Si l'un des deux devait changer, bien sûr, il faudrait transformer ça en OneWay
                                              
                                              Second binding en TwoWay, parce que l'utilisateur peut changer de sélection n'importe quand. Il faut que le métier soit informé de ce changement
                                              pour pouvoir réaliser des actions si nécessaire. Naturellement, TwoWay implique aussi que si le changement provient du métier, il faut que l'UI
                                              soit capable d'afficher à l'utilisateur quelle est la nouvelle sélection.
                                              -->
                                              <ListBox Grid.Row="1" 
                                                       ItemsSource="{Binding Expenses, Mode=OneTime}" 
                                                       SelectedItem="{Binding SelectedExpense, Mode=TwoWay}"
                                                       DisplayMemberPath="Nom"/>
                                              <StackPanel Grid.Row="2" Orientation="Horizontal">
                                                  <Button Content="Ajouter" Command="{Binding AjouterCommand, Mode=OneTime}"/>
                                                  <Button Content="Supprimer" Command="{Binding SupprimerCommand, Mode=OneTime}"/>
                                                  <TextBlock Text="Somme = "/>
                                                  <TextBlock Text="{Binding Total, Mode=OneWay, StringFormat='{}{0:C}'}" Language="fr-fr"/>
                                              </StackPanel>
                                          </Grid>
                                          
                                          <!-- La suite de l'IHM : le panel de saisie des dépense -->
                                          <StackPanel Grid.Column="1" Orientation="Vertical" IsEnabled="{Binding HasSelectedExpense, Mode=OneWay}">
                                              <StackPanel Orientation="Horizontal" DataContext="{Binding SelectedExpense, Mode=OneWay}"><!-- Ici, ça peut etre sympa de redéfinir le contexte, parce que à ce niveau de la UI, je sais que mon TextBox n'aura besoin de rien d'autre que des données présentes dans une instance de Expense -->
                                                  <TextBlock Text="Nom :"/>
                                                  <TextBox Text="{Binding Nom, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="120"/><!-- Juste pour le côté fancy, je veux que mes changements soient répercutés quand je tape une lettre, pas quand je sors la souris de la boite de texte (lost focus)-->
                                              </StackPanel>
                                              <StackPanel Orientation="Horizontal" DataContext="{Binding SelectedExpense, Mode=OneWay}">
                                                  <TextBlock Text="Prix :"/>
                                                  <TextBox Text="{Binding Prix, Mode=TwoWay}" MinWidth="120"/>
                                              </StackPanel>
                                              <StackPanel Orientation="Horizontal" DataContext="{Binding SelectedExpense, Mode=OneWay}">
                                                  <TextBlock Text="Quantite :"/>
                                                  <TextBox Text="{Binding Quantite, Mode=TwoWay}" MinWidth="120"/>
                                              </StackPanel>
                                              <StackPanel Orientation="Horizontal" DataContext="{Binding SelectedExpense, Mode=OneWay}">
                                                  <TextBlock Text="Total = "/>
                                                  <TextBlock Text="{Binding PrixTotal, Mode=OneWay, StringFormat='{}{0:C}'}" Language="fr-fr"/>
                                              </StackPanel>
                                          </StackPanel>
                                      </Grid>
                                  </Window>
                                  

                                  MainWindowViewModel.cs :

                                  using System;
                                  using System.Collections.ObjectModel;
                                  using System.Collections.Specialized;
                                  using System.ComponentModel;
                                  using System.Linq;
                                  using System.Windows;
                                  using System.Windows.Input;
                                  
                                  namespace WpfApp1
                                  {
                                      /*
                                       * Le ViewModel associé à la vue MainWindow.
                                       * Basiquement, un ViewModel représente le code métier qui se cache derrière une IHM WPF.
                                       * On appelle code métier, la logique spécifique au fonctionnel que tu cherches à addresser avec ton application.
                                       * Par exemple, si ton IHM possède 2 champs Prix HT et TVA, le code qui calculera le prix TTC, c'est du code métier.
                                       * Le "métier" de ton application sera d'accélérer le travail d'un humain (un comptable, un caissier, ...) en
                                       * lui sortant directement le prix TTC à partir d'un prix HT et d'une TVA.
                                       */
                                      public sealed class MainWindowViewModel : Notifiable
                                      {
                                          /*
                                           * Cette propriété est de type "Collection notifiable de [...]".
                                           * En termes d'instances d'objet, je n'ai aucun intérêt à rendre l'instance de cette liste notifiable
                                           * d'autant plus si cette propriété est en lecture seule. Elle n'est affectée qu'une seule et unique fois
                                           * au cours du cycle de vie de mon application : Quand MainWindowViewModel est construit.
                                           * Ceci induit le fait que, dans un contexte WPF, si une instance de MainWindowViewModel existe, une instance
                                           * de cette collection existe forcément.
                                           */
                                          public ObservableCollection<Expense> Expenses
                                          {
                                              get;
                                          }
                                  
                                          private Expense _selectedExpense;
                                          public Expense SelectedExpense
                                          {
                                              get { return _selectedExpense; }
                                              set
                                              {
                                                  if (_selectedExpense != value)
                                                  {
                                                      _selectedExpense = value;
                                                      this.RaisePropertyChanged(nameof(MainWindowViewModel.SelectedExpense));
                                                      this.RaisePropertyChanged(nameof(MainWindowViewModel.HasSelectedExpense));
                                                  }
                                              }
                                          }
                                  
                                          /*
                                           * Un autre exemple de propriété métier, dont le rôle est d'afficher la somme des prix totaux de toutes les dépenses.
                                           */
                                          public decimal Total
                                          {
                                              get { return this.Expenses.Sum(e => e.PrixTotal); }//Avec une petite ligne LINQ-To-Object, cette logique métier est très vite pliée 
                                          }
                                  
                                          public bool HasSelectedExpense
                                          {
                                              get { return this.SelectedExpense != null; }
                                          }
                                  
                                          /*
                                           * Je ne rentre pas dans le détail de ICommand, ce n'est pas le but de ce tuto.
                                           * Cette interface est là pour permettre des réaliser des bindings sur des actions, plutôt que des bindings
                                           * sur des données. Comprendre : En MVVM, même les actions utilisateur sont des "données".
                                           */
                                          public ICommand AjouterCommand
                                          {
                                              get;
                                          }
                                  
                                          public ICommand SupprimerCommand
                                          {
                                              get;
                                          }
                                  
                                          /*
                                           * Mon constructeur.
                                           */
                                          public MainWindowViewModel()
                                          {
                                              this.Expenses = new ObservableCollection<Expense>();
                                              //Vu que cette collection n'est construite qu'une seule et unique fois, si je veux faire des actions métier en réponse
                                              //à la modification de la collection, c'est maintenant ou jamais!
                                              this.Expenses.CollectionChanged += this.Expenses_CollectionChanged;
                                  
                                              //De même, je n'explique pas ce contient BasicAction.
                                              //Si tu utilise des framework comme MVVMLight, ils te proposeront aussi des implémentations de cette interface.
                                              //Si tu veux vraiment voir de quoi il retourne, je t'invite à implémenter cette interface toi même, dans un projet test
                                              this.AjouterCommand = new BasicAction(this.DoAdd);
                                              this.SupprimerCommand = new BasicAction(this.DoRemove);
                                          }
                                  
                                          /*
                                           * Ici, je vais capter les événements de modifications du contenu de la collection.
                                           * Pas les changements d'instance de collection, ni ceux de changement d'une valeur d'une des instances
                                           * incluse dans la collection. Seulement les changements de contenu de collection.
                                           * Par exemple : Si j'ajoute ou supprime un élément, je modifie le contenu de la collection
                                           */
                                          private void Expenses_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
                                          {
                                              if (e.NewItems != null)//Si j'ai de nouveaux éléments, je dois surveiller leur changement de valeur pour indiquer, ici même, le changement de la propriété "Total"
                                              {
                                                  foreach (Expense exp in e.NewItems)
                                                  {
                                                      exp.PropertyChanged += this.Expense_PropertyChanged;
                                                  }
                                              }
                                              if (e.OldItems != null)//Si j'ai des éléments supprimés, je dois me désabonner de leur changement de valeur, pour éviter les comportements bizarres et les fuites mémoire.
                                              {
                                                  foreach (Expense exp in e.OldItems)
                                                  {
                                                      exp.PropertyChanged -= this.Expense_PropertyChanged;
                                                  }
                                              }
                                  
                                              //Ca, c'est pour couvrir les cas où un objet Expense serait ajouté à ma collection et aurait déjà des valeurs renseignées dans Prix ou Quantité
                                              //ou dans le cas des suppressions.
                                              this.RaisePropertyChanged(nameof(MainWindowViewModel.Total));
                                          }
                                  
                                          private void Expense_PropertyChanged(object sender, PropertyChangedEventArgs e)
                                          {
                                              //Je pourrais réagir à d'autres changements, mais je sais que PrixTotal sera changeant si Prix ou Quantite change.
                                              //Pas besoin d'en surveiller 50 donc.
                                              if(e.PropertyName == nameof(Expense.PrixTotal))
                                              {
                                                  this.RaisePropertyChanged(nameof(MainWindowViewModel.Total));
                                              }
                                          }
                                  
                                          /*
                                           * La logique métier associée à l'action utilisateur d'ajouter une nouvelle dépense.
                                           */
                                          private void DoAdd()
                                          {
                                              Expense exp = new Expense();
                                              this.Expenses.Add(exp);
                                              this.SelectedExpense = exp;//Voila pourquoi la vue doit pouvoir réagir au changement de sélection depuis le métier.
                                          }
                                  
                                          /*
                                           * Et celle associée à l'action de supprimer une dépense.
                                           */
                                          private void DoRemove()
                                          {
                                              if (this.SelectedExpense == null)//Voila pourquoi le métier à besoin de savoir quelle dépense est actuellement sélectionnée
                                              {
                                                  MessageBox.Show(Application.Current.MainWindow, "Merci de sélectionner la dépense à supprimer d'abord");
                                              }
                                              else
                                              {
                                                  this.Expenses.Remove(this.SelectedExpense);
                                              }
                                          }
                                      }
                                  
                                      /*
                                       * Pas plus de détail ici, ce n'est pas le but de cet exemple.
                                       */
                                      public class BasicAction : ICommand
                                      {
                                          private Action _action;
                                          public BasicAction(Action act)
                                          {
                                              _action = act;
                                          }
                                          public event EventHandler CanExecuteChanged;
                                  
                                          public bool CanExecute(object parameter)
                                          {
                                              return true;
                                          }
                                  
                                          public void Execute(object parameter)
                                          {
                                              _action();
                                          }
                                      }
                                  }
                                  

                                  Notifiable.cs :

                                  using System;
                                  using System.ComponentModel;
                                  
                                  namespace WpfApp1
                                  {
                                      /*
                                       * Une classe de base, juste pour fournir un minimum de logique sur la levée de PropertyChanged.
                                       * Ca implémente INotifyPropertyChanged dont le seul membre est l'événement PropertyChanged.
                                       * L'interface sera utilisée par le moteur de binding WPF pour déceler les instances d'objets
                                       * qui peuvent indiquer de changements de valeurs, exposées au travers des propriétés (aussi appelés
                                       * accesseurs dans d'autres langages).
                                       * Basiquement, dans WPF, il y aura des "if(dataContext is INotifyPropertyChanged) { ((INotifyPropertyChanged)dataContext).PropertyChanged += ... }"
                                       * qui seront fait et qui engendreront des actions sur le moteur de rendu graphique afin d'afficher les nouveaux changements
                                       */
                                      public abstract class Notifiable : INotifyPropertyChanged
                                      {
                                          //Le fameux membre de l'interface
                                          public event PropertyChangedEventHandler PropertyChanged;
                                  
                                          //Une méthode à moi, qui me permettra d'éviter de copier/coller la logique de levée d'événement partout où ce sera utilisé.
                                          protected void RaisePropertyChanged(String propertyName)
                                          {
                                              if(this.PropertyChanged != null)//Si le composant n'est pas référencé dans une instance d'un contrôle WPF, ça peut être null.
                                              {
                                                  /*
                                                   * Si c'est pas null, je lève l'événement, en passant sender = l'instance d'objet en cours et en paramètre, une
                                                   * nouvelle instance PropertyChangedEventArgs qui contiendra le nom de la propriété changeante.
                                                   */
                                                  this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                                              }
                                          }
                                      }
                                  }
                                  

                                  Expense.cs :

                                  using System;
                                  
                                  namespace WpfApp1
                                  {
                                      /*
                                       * Une classe de modèle de donnée. Ce genre de classe pourrait être issue directement d'une base de données par exemple.
                                       * Pour cet exemple, elle ne contient que 3 propriétés directement modifiable par l'utilisateur : Le nom, le prix et la quantité.
                                       * Ces 3 propriétés sont donc configurées en lecture & écriture.
                                       * Bien sûr, à chaque fois, la séquence d'action, c'est conserver la nouvelle valeur saisie par l'utilisateur.
                                       * Le nom, c'est un exemple de propriété scalaire sans logique métier associée.
                                       * Les 2 autres sont un exemple de propriété scalaire dont leur saisie va déclencher une logique métier de calcul de prix total.
                                       * Bien sûr, cette logique métier ne serait pas à conserver dans la base, puisque c'est une valeur recalculée automatiquement à la demande.
                                       */
                                      public sealed class Expense : Notifiable
                                      {
                                          private String _nom;
                                          public String Nom
                                          {
                                              get { return _nom; }
                                              set
                                              {
                                                  if(_nom != value)
                                                  {
                                                      _nom = value;
                                                      this.RaisePropertyChanged(nameof(Expense.Nom));
                                                  }
                                              }
                                          }
                                  
                                          private decimal _prix;
                                          public decimal Prix
                                          {
                                              get { return _prix; }
                                              set
                                              {
                                                  if (_prix != value)
                                                  {
                                                      _prix = value;
                                                      this.RaisePropertyChanged(nameof(Expense.Prix));
                                                      this.RaisePropertyChanged(nameof(Expense.PrixTotal));//Quand le prix change, le prix total aussi
                                                  }
                                              }
                                          }
                                  
                                          private decimal _quantite;
                                          public decimal Quantite
                                          {
                                              get { return _quantite; }
                                              set
                                              {
                                                  if(_quantite != value)
                                                  {
                                                      _quantite = value;
                                                      this.RaisePropertyChanged(nameof(Expense.Quantite));
                                                      this.RaisePropertyChanged(nameof(Expense.PrixTotal));//Quand la quantité change, le prix total aussi
                                                  }
                                              }
                                          }
                                  
                                          public decimal PrixTotal
                                          {
                                              get { return this.Prix * this.Quantite; }//Rien de transcendant dans cette logique là 
                                          }
                                      }
                                  }
                                  

                                  Le MainWindow.xaml.cs ne contient rien d'autre que le code par défaut (Pas de code behind).

                                  Cet exemple est surtout là pour montrer qu'au final, WPF, ça s'utilise de façon très "naturelle". Je veux indiquer qu'une valeur est changeante? Bon, je lève mon événement et ça roulera (pour peu que ce soit bien câblé). Il en va exactement de même quand "un truc de gestion" doit indiquer qu'un des éléments géré est changeant et que ça impacte directement l'ensemble. Avant de pouvoir indiquer que l'ensemble à changé, il faut déjà remonter l'information au groupe en question (ce serait pareil dans la société en fait : Pour qu'un chef puisse etre au courant de quelque chose, il faut déjà commencer par lui remonter l'information).

                                  Après, bien sûr, il faut avoir la tete sur les épaules et toujours garder en tête ce qui se déroule dans l'application en cours de développement. Mais ça, là aussi, ce serait pareil dans la vrai vie : si t'envoie un courrier de résiliation à ton assurance (instance A), ta banque (instance B) sera totalement incapable de savoir que tu voulais clôturer ton compte courant (appel d'une méthode d'instance), même si ces 2 organismes sont gérés par la même boite (le processus de ton application .NET)

                                  Note également que si je ne répond pas directement à ta demande de t'indiquer précisemment où ça cloche sur ton code, c'est parce qu'il est "trop gros" et qu'en gros, cette tache reviendrait pour moi à faire du debug intégral de ton application. D'une part, j'ai clairement autre chose à faire que de débugger les applications des autres gratuitement...Et d'autre part, je doute que ça te fasse progresser d'une quelconque manière ^^ . Vu le fonctionnel de ton app, je n'ai pas l'impression qu'il soit compliqué. Par conséquent, si ça ne fonctionne pas, le problème sera surement "simple", mais ce serait mieux que ce soit toi qui le trouve, plutôt que moi. Pour ça, on pourra te donner des outils pour t'aider (le meilleur étant le debuggueur visual studio) ou quelque pistes, mais personne ne le fera à ta place. Si tu n'y arrives pas, c'est qu'il te manque surement des automatismes dans cette techno et pour ça, le meilleur conseil qu'on pourrait te donner, c'est de "t'enlever des sources de problèmes" pour refonder tes connaissances de base de cette techno de façon plus robuste (et oui, une interface graphique, c'est une source de problème supplémentaire).

                                  -
                                  Edité par Nisnor 18 octobre 2018 à 0:10:25

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    18 octobre 2018 à 10:19:46

                                    Bonjour Nisnor,

                                    Merci pour ta réponse et ton exemple. Je vais l'étudier très attentivement. Une première remarque est qu'enfin c'est du code avec des commentaires qui sont utiles. En effet la plupart des tutos donne du code sans explication et c'est pratiquement impossible à comprendre.

                                    Saches, je te l'ai déjà dit, il n'ai pas question et que tu passes trop de temps avec mes soucis, et que tu me donnes une solution toute faite à mon problème. Tu as raison ça ne me servirait à rien et ne me ferai pas progresser. Je veux mettre le doigt moi même sur le problème. Je ne te demandais que des pistes comme tu le précises.

                                    Je comprends parfaitement les explications que tu donnes sur le déroulement du programme et je crois que c'est là que je pêche. Peut être que le procédurale ne veux pas sortir de mon corps :D.

                                    Je te tiens au courant quand j'aurai remis mon application en état de fonctionnement. Je vais prendre le temps qu'il faut mais j'y arriverai.

                                    Encore merci ce message est vraiment super sympa de ta part :).

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      22 octobre 2018 à 9:38:40

                                      Bonjour Nisnor,

                                      J'ai travaillé sur ton code et enfin beaucoup de choses sont devenues plus claires. Un très grand merci et un très grand bravo pour avoir pris le temps de construire cet exemple super bien documenté. C'est très efficace. Pour la suite de mon discourt excuses moi s'il y a des problèmes de vocabulaire. N'hésites pas à me reprendre :D

                                      - J'ai compris les utilisations des abonnements. Dans les tutos ça ne me paraissait pas aussi clair.

                                      - La classe "Notifiable" est une super idée. Il est évident et tu t'en doutes que je n'y aurais jamais pensé.

                                      - L'utilisation de "nameof" est aussi une bonne chose. On évite les fautes d'orthographe ou les mauvais noms.

                                      - L'utilisation de "NewItems" et "OldItems" que je ne connaissait pas.

                                      - Je ne connaissais pas non plus "BasicAction" que je vais creuser en espérant qu'il existe quelque chose concernant la modification d'une valeur en plus de l'ajout et de la suppression.

                                      Tu vois pas mal de choses et encore ce ne sont que les principales.

                                      Il me reste, quand même, quelques questions:

                                      - Quel est l’intérêt de la propriété "HasSelectedExpense"?  Je n'ai pas compris.

                                      - Si je reviens sur une application utilisant une base de données et sur l'enregistrement des données. Quel est la meilleur technique:

                                                    - mettre à jour la base dès l'ajout, la suppression ou ma modification d'une donnée 

                                                    - réaliser cette opération en fin de saisie en utilisant un bouton "sauvegarder" par exemple.

                                      Si le second cas est la meilleur écriture comment procéder? J'ai penser utiliser une seconde "ObservableCollection" pour ne pas réenregistrer les données déjà existantes. Mais que deviennent les modifications ou les suppressions des données déjà existantes?

                                      J'arrête là car je ne voudrais pas te submerger de questions.

                                      Merci encore pour le temps passé pour cette petite appli. C'est génial :D

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        22 octobre 2018 à 15:20:00

                                        "La classe "Notifiable" est une super idée. Il est évident et tu t'en doutes que je n'y aurais jamais pensé."

                                        En fait....Cette classe, "tu en as eu l'idée", mais vu que tu utilises un framework dédié et qu'il s'agit d'une des pierres fondamentale du MVVM dans WPF, un tel framework ne pouvait pas exister sans ce type de classe.

                                        Cette classe Notifiable, c'est une version ultra-simplifiée "ViewModelBase" que tu as déjà utilisée dans ton premier post.

                                        "L'utilisation de "nameof" est aussi une bonne chose. On évite les fautes d'orthographe ou les mauvais noms."

                                        Tout à fait. Attention cela dit, l'opérateur nameof est apparu assez récemment dans les versions du langage C# (je crois que ça date de la version 5 ou 6...On est à la 7 et la 8 pointe le bout de son nez).

                                        "L'utilisation de "NewItems" et "OldItems" que je ne connaissait pas."

                                        Quand tu t'abonnes à des événements proposés par le framework .NET, n'hésite pas à être curieux "pour rien". Dans ta méthode de réponse à l'événement, tu tapes juste "e." et là, l'auto-compléteur visual Studio va te sortir une liste de trucs disponibles, associés au contexte actuel. Avec "e.", il va te proposer tous les membres d'instance du type de "e" ainsi que toutes les méthodes d'extensions qui seraient rendues accessible avec un importe d'espace de nom. Exemple : les méthodes Linq-To... . Sans le "using System.Linq;" tout en haut du code source, si tu fais "uneInstanceDeListGenerique.", tu vas te voir proposer juste les membres d'instances. Si tu rajoutes le "using System.Linq", et tu refais "uneInstanceDeListGenerique.", tu vas voir apparaitre, en plus des membres d'instance, toutes les méthodes d'extension LINQ.

                                        De là, tu regardes les noms...Et tu vois si il y a des trucs sympa. Si oui, n'hésite pas à les placer dans ton code source, replace le curseur d'édition "dans" le nom du membre nouvellement ajouté et appuyer sur "F1". Ca va t'ouvrir une page internet qui mènera directement sur la documentation Microsoft du membre en question.

                                        "Je ne connaissais pas non plus "BasicAction" que je vais creuser en espérant qu'il existe quelque chose concernant la modification d'une valeur en plus de l'ajout et de la suppression."

                                        Regarde plus en profondeur les "Commandes WPF" (en Anglais, t'auras sans doute de meilleurs résultats : "WPF Commands").

                                        Il y a effectivement moyen de passer 1 argument aux méthodes pointées par les commandes. MVVMLight propose surement quelque chose dans ce sens. Cet argument peut être extrait directement d'un contexte tiré directement d'un niveau spécifique de la UI.

                                        Si tout ça est souvent peu utilisé pour une approche UI "Selection First -> Read & Update" ensuite, ça peut être beaucoup plus utile si tu veux faire des menu contextuels.

                                        "Quel est l’intérêt de la propriété "HasSelectedExpense"?  Je n'ai pas compris."

                                        Comme son nom l'indique, le seul rôle de cette propriété est d'exposer publiquement quelque chose qui indique si oui ou non il y a une instance Expense exploitable dans la propriété "SelectedExpense".

                                        Ca permet de simplifier le code XAML qui va activer/désactiver certaines parties de la UI en faisant simplement un "IsEnabled="{Binding HasSelectedExpense, Mode=OneWay}"".

                                        Il y aurait bien d'autre moyen d'arriver au même résultat mais globalement plus complexe ou plus long à taper.

                                        A noter que dans le "DoRemove", au lieu de faire "if(this.SelectedExpense == null)", j'aurais pu (j'aurais dû même) remplacer ça par "if(!this.HasSelectedExpense)"; Le but étant aussi de factoriser certaines logique métier (ici : factoriser la logique qui me permet de savoir si j'ai une dépense valide sélectionnée ou non).

                                        Ici, la logique métier associée à cette propriété n'est pas très complexe. Mais dans d'autres cas, la logique de validation peut être plus compliquée, raison de plus pour la factoriser quelque part (pour éviter de dupliquer/copier-coller un code complexe à plusieurs endroit différents du logiciel).

                                        "Si je reviens sur une application utilisant une base de données et sur l'enregistrement des données. Quel est la meilleur technique"

                                        Il n'y a pas de meilleure technique dans ce cas. Ca dépendra entièrement de l'expérience utilisateur que tu veux donner. En général, ces points sont discutés avec les ergonomes et UX designer.

                                        Personnellement, étant un peu de la vieille école, j'aurais fait ça avec un bouton sauvegarder, puis, à l'usage, j'aurais surement cherché à automatiser ça dans le flow d'utilisation de mon appli.

                                        La sauvegarde automatique peut présenter des problèmes, si par exemple ta procédure de sauvegarde se trouve réalisée sur un média réseau. Par exemple : si tu sauvegarde à chaque fois que le nom de ton élément change, même si ta sauvegarde dure 200ms à l'unité, tu remarqueras très vite que c'est "frustrant" d'être bloqué 200ms à chaque appuie sur une touche du clavier. Il faut trouver un juste milieux entre "sauvegarder à chaque changement" et "ne jamais sauvegarder tant que l'utilisateur ne l'a pas explicitement demandé" et tester, pour voir ce qui plait le plus. Tout est une question de curseur donc ^^ .

                                        "Si le second cas est la meilleur écriture comment procéder?"

                                        De tes codes passés, tu sembles utiliser un ORM, type Entity Framework.

                                        Et comme cette techno est bien ficelée, tu n'as pas à te soucier qui a changé ou comment, c'est déjà géré pour toi dans le framework. Quand tu utilises ce genre de techno, là aussi, tu as juste à utiliser "le plus naturellement possible" tes données : Tu lis, tu modifies ce que tu veux et tu sauvegarde.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          22 octobre 2018 à 15:35:55

                                          Encore et toujours merci pour tes explications très complètes.

                                          Je repars sur mon application et j'essaie, avec toutes ces nouvelles connaissances, de la faire fonctionner. Je te tiens au courant et ce serait surprenant que je n'ai pas d'autres questions.

                                          Encore un grand merci

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            25 octobre 2018 à 12:24:20

                                            Bonjour Nisnor, je suis déjà de retour.

                                            J'ai réécrit mon application sans utiliser MVVMlight et en suivant tes conseils et tes explications. Tout fonctionne bien et je te remercie d'avoir éclairé mon esprit.

                                            Il me reste un problème (ça aurait été trop beau...). Je sais qu'un viewmodel ne peut pas "discuter" avec un autre viewmodel. Dans mon application j'ai un viewmodel (UcEntreeViewModel) qui gère le usercontrol pour les entrées. J'ai un viewmodel (MainWindowViewModel) qui gère la page principale. Dans cette page je veux afficher le total des entrées dans un textbox.

                                            La somme de toutes les entrées est effectuée dans UcEntreeViewModel (comme tu me l'as monté). Pour "remonter" la valeur de la somme vers MainWinowViewModel, j'ai créé une classe Calcul:

                                            using Compte_Bancaire.Utils;
                                            using System.Collections.ObjectModel;
                                            using System.ComponentModel;
                                            using System.Linq;
                                            
                                            namespace Compte_Bancaire.Models
                                            {
                                                public class Calcul : Notifiable
                                                {
                                            
                                                    #region Constructeur
                                                    public Calcul()
                                                    {
                                                        
                                                    }
                                                    #endregion
                                            
                                                    #region Propriétés
                                                    private decimal calculEntree;
                                                    public decimal CalculEntree
                                                    {
                                                        set
                                                        {
                                                            calculEntree = value;
                                                            RaisePropertyChanged(nameof(Calcul.CalculEntree));
                                                        }
                                                        get
                                                        {
                                                            return calculEntree;
                                                        }
                                                    }
                                                    #endregion
                                                }
                                            }

                                            Dans UcEntreeViewModel j'ai ajouté les lignes suivantes:

                                            private void Entree_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                                                    {
                                                        // Sauvegarde des données dans l'observablecollection ItemEntree
                                                        var entree = new CollectionEntree()
                                                        {
                                                            OperationEntree = ItemSelectionne.OperationEntree,
                                                            CreditEntree = ItemSelectionne.CreditEntree
                                                        };
                                                        RaisePropertyChanged(nameof(UcEntreeViewModel.TotalEntree));
                                                        Calcul calcul = new Calcul();
                                                        calcul.CalculEntree = TotalEntree;
                                                    }

                                            Puis dans le MainWindowViewModel les lignes suivantes dans le constructeur:

                                            Calcul ResultatCalcul = new Calcul();
                                                        ResultatCalcul.PropertyChanged += ResultatCalcul_PropertyChanged;

                                            Et:

                                            private void ResultatCalcul_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                                                    {
                                                        if (e.PropertyName == nameof(Calcul.CalculEntree))
                                                        {
                                                            RaisePropertyChanged(nameof(MainViewModel.ValeurTotalEntree));
                                                        }
                                                    }

                                            Je pense être sur une bonne piste mais ça ne fonctionne pas. Aurais tu une piste ou une idée?

                                            Merci par avance.




                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              26 octobre 2018 à 15:27:42

                                              "Je sais qu'un viewmodel ne peut pas "discuter" avec un autre viewmodel"

                                              Pourquoi ne pourrait-il pas?

                                              Au contraire d'ailleurs, la discussion entre ViewModel, c'est un sujet "critique", dans le sens où il est inévitable, mais si on veut garder nos blocs métiers bien séparés, il s'agit de ne pas simplement pas créer de couplage trop fort entre chacun.

                                              Après, ça dépend beaucoup des cas...

                                              Par exemple : Dans une application similaire à un navigateur web, où chaque tab serait modélisée par un ViewModel...Il y a bien un ViewModel "conteneur" qui "connait tout le monde" (tout comme, dans la UI, il y aurait un TabControl qui contiendrait tous les TabPage actifs). Il est tout à fait possible de faire communiquer les tab de différentes façon, en utilisant des patterns prévus pour.

                                              Par exemple, dans Windows, le pattern "event loop" est utilisé pour gérer la communication entre les processus. Toute application Windows contient nécessairement une "pompe de message". Cette pompe, c'est basiquement une boucle "infinie" qui va "écouter" les messages que le coeur du système Windows lui remonte. Mais ces messages....Ils peuvent provenir de Windows lui même (si il s'agit d'une interaction avec le matériel par exemple...un mouvement de souris, une activation d'une touche du clavier, etc.) mais également d'un autre processus "qui parle" avec un autre qu'il n'a pas créé (Inter-Process Communication, IPC).

                                              Des patterns, il y en a plein...Je ne les connais pas tous, loin de la.

                                              Celui que tu propose s'approche énormément du pattern "mediator" : Certaines logiques de communication inter-bloc sont encapsulées dans un objet tiers.

                                              En revanche, dans ton implémentation, il y a un hic qui diverge du pattern mediator. Ce pattern suppose que tous les membres d'une discussion parlent à travers une même instance du médiateur. Or là, ce n'est pas ce que tu fais. Chacun de tes membres (à savoir : ton instance de UcEntreeViewModel et ton instance de MainWindowViewModel) parle avec une instance spécifique de ton médiateur (UcEntreeViewModel dispose de son propre médiateur et MainWindowViewModel dispose également de son propre médiateur). Ca ne peut pas fonctionner. Arranges pour que l'instance de ton médiateur utilisée soit la même aussi bien pour MainWindowViewModel que pour UcEntreeViewModel.

                                              Une façon facile de partager ton instance de médiateur, c'est de faire un membre statique de ton mediateur, instancié une seule fois, quand le processus de ton application démarre. C'est aussi probablement la plus crade, parce que ton médiateur semble spécifique à une communication entre UEVM et MWVM.

                                              Une autre façon, peut-être plus ardue, mais à mon sens plus propre...Serait rapprocher d'avantage le code de ton métier de celui de la UI. Ta UI va surement indiquer "J'ai une fenêtre, qui contient un formulaire d'entrée utilisateur". Tu peux aussi raisonner de la même manière pour ta couche métier : "Ma logique métier principale contient une logique de saisie de donnée utilisateur". Ca revient à exposer une instance de UEVM directement depuis ton instance MWVM. Dans ta UI, il y aurait surement très peu de modifications à apporter pour supporter ça, puisque MainWindow est déjà dans un contexte de donnée valorisé à une instance de MWVM, il disposera donc de l'instance de UEVM que le UserControl pourra utiliser en redéfinissant son DataContext. Cela dit, si tu optes pour cette option...Tu noteras du coup que l'usage du pattern médiateur est particulièrement inutile et rajoute de la complexité pour rien. Ca, c'est vrai parce que tu n'as que 2 personnes qui discutent (et ce serait vrai dans la vrai vie aussi d'ailleurs : Pour parler à 2 personnes - en admettant que personne ne s'engueule bien sûr ^^ - pas besoin d'ajouter une 3eme personne).Si tu en as plus, il sera probablement plus intéressant de repenser la façon dont tu gère tes intervenants et là, le pattern médiateur reprendra du sens.

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                26 octobre 2018 à 16:13:49

                                                Merci Nisnor, je vais regarder le pattern médiator car j'aurai plus de deux personnes qui vont discuter. Ensuite j'essaie et je te tiens au courant.
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  29 octobre 2018 à 15:22:51

                                                  Bonjour Nisnor, j'ai un peu de mal à bien comprendre toutes tes explications malgré plusieurs lectures. Le passage sur le fait de chacun de mais viewmodel possède une instance du mediator.

                                                  Je ne connais pas du tout le mediator et je n'en ai jamais entendu parlé donc là je ne comprends rien. En effet s'il s'agit d'un pattern, je n'ai rien installé dans mon appli (j'ai même supprimé l'utilisation de MVVM light) et je ne fais aucun appel à quoique soit. :'(

                                                  Comment définit-on une instance de mediator?

                                                  Peux tu encore et encore m'aider?

                                                  Merci d'avance

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    29 octobre 2018 à 17:03:42

                                                    Oui, c'est un pattern.

                                                    Un pattern, c'est juste "une façon de développer". Ce n'est pas lié à un langage ou une techno. Certains framework peuvent implémenter un pattern en te fournissant un set d'outil qui vont dans le sens du pattern, mais c'est le seul lien qui existe entre un pattern et une techno.

                                                    Rien ne t'empêche d'adhérer à un pattern, sans utiliser de framework spécifique, ça veut juste dire que tu as développé un truc qui se conforme à ce qui est décris dans le pattern en question.

                                                    Peut-être dans un premier temps, utilise juste la technique crade : Définis quelque part dans ton programme un champs statique instancié une seule fois, un peu comme ça :

                                                    public class UneClasseBidon
                                                    {
                                                       public static readonly Calcul MonMediateur = new Calcul();//Voila...C'est statique, ça veut dire que tant que ton programme vivra, tous les composants utilisant UneClasseBidon.MonMediateur vont piocher dans la même instance de la classe Calcul. C'est pas très beau, mais ça fonctionne.
                                                    }

                                                    Après, les patterns, c'est pas quelque chose que les débutants étudient en premier...Tout simplement parce que ce sont des sortes de "code de conduite" du code...Mais si tu ne maitrise pas parfaitement "le code", tu risques de perdre plus de temps et de faire des catastrophes logicielles, plus que réellement faire quelque chose d'intéressant à mon sens. C'est un peu comme si t'essayais de conduire une voiture de course sur piste, sans avoir touché un volant de ta vie. Si j'en parlais, c'était juste pour te donner un sujet d'étude "pour plus tard", mais t'y attarde pas trop non plus.

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      29 octobre 2018 à 18:55:08

                                                      Merci pour ces détails.

                                                      A plus certainement :D

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        31 octobre 2018 à 10:36:45

                                                        Bonjour Nisnor, ça fonctionne c'est super. Encore merci et bravo de prendre de ton temps pour me donner tes conseils et explications.

                                                        A plus tard je pense…

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          2 novembre 2018 à 0:05:41

                                                          Bonsoir Nisnor, je sais c'est déjà le retour...

                                                          J'ai été un peu rapide en pensant que tout fonctionnait. En effet l'ajout et la suppression de données fonctionnent parfaitement. Par contre le changement d'une donnée existante ne marche pas.

                                                          Je te rappelle que je travaille avec un datagrid dans lequel l'utilisateur entre les données (il y en a deux) et que les actions s'effectuent après avoir appuyé sur Enter.

                                                          Je pensais qu'il me fallait contrôler NewItems et OldItems car j'ai lu qu'ils avaient tous les deux une valeur non nulle lors d'un changement de valeur. Mais je m'aperçois que le programme ne détecte même pas un changement dans l'observablecollection. Un peu perdu à vrai dire...

                                                          Je te donne le morceau de code que j'ai écrit:

                                                          private void ItemEntree_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
                                                                  {
                                                                      // Détection de l'ajout d'un item dans la collection
                                                                      if (e.NewItems != null)
                                                                      {
                                                                          foreach (CollectionEntree entree in e.NewItems)
                                                                          {
                                                                              entree.PropertyChanged += Entree_PropertyChanged;
                                                                          }
                                                                      }
                                                                      // Détection de la suppression d'un item dans la collection
                                                                      if (e.NewItems==null)
                                                                      {
                                                                          foreach (CollectionEntree entree in e.OldItems)
                                                                          {
                                                                              entree.PropertyChanged -= Entree_PropertyChanged;
                                                                              this.ItemEntree.Remove(this.ItemSelectionne);
                                                                              RaisePropertyChanged(nameof(UcEntreeViewModel.TotalEntree));
                                                                              MediatorCalcul.Mediator.CalculEntree = TotalEntree;
                                                                          }
                                                                      }
                                                                      RaisePropertyChanged(nameof(UcEntreeViewModel.TotalEntree));
                                                                      MediatorCalcul.Mediator.CalculEntree = TotalEntree;
                                                                  }
                                                                  private void Entree_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                                                                  {
                                                                      // Sauvegarde des données dans l'observablecollection
                                                                      {
                                                                          var entree = new CollectionEntree()
                                                                          {
                                                                              OperationEntree = ItemSelectionne.OperationEntree,
                                                                              CreditEntree = ItemSelectionne.CreditEntree
                                                                          };
                                                                      }
                                                                      RaisePropertyChanged(nameof(UcEntreeViewModel.TotalEntree));
                                                                      MediatorCalcul.Mediator.CalculEntree = TotalEntree;
                                                                      
                                                                  }
                                                              }

                                                          Puis je encore solliciter ton aide?

                                                          Par avance merci

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            2 novembre 2018 à 17:04:37

                                                            L'événement CollectionChanged & ses arguments reflètent un changement du contenu de la collection (2)...Aucunement un changement d'instance de la collection (1), pas plus qu'un changement à l'intérieur d'un des item de la collection (3).

                                                            Si problème il y a entre des lignes de 2 DataGrid différentes, c'est que DataGrid1 se binde sur des instances 1 de tes objets et DataGrid2 sur des instances 2 de tes objets, ou que l'un des binding est en OneWay alors qu'il devrait etre en TwoWay. Il faut que les 2 DG exploitent les même instances d'objet, sinon, ça ne fonctionnera pas.

                                                            Cette notion d'instance est déjà très importante en C# & .NET de base...Elle l'est encore plus en WPF car ce sont ces instances qui vont déterminer les réactions des interfaces.

                                                            Quelques notes :

                                                            • Le code indiqué ici, seul, ne permet pas de savoir où est l'erreur.
                                                            • Faire des if(e.New/OldItems dans le CollectionChanged, c'est bien...Mais NewItems == null ne veut pas nécessairement dire que OldItems ne le sera pas.
                                                            • this.ItemEntree.Remove est inutile puisque si tu reçois un événement de suppression, les objets indiqués dans OldItems n'existent déjà plus dans la collection source. Si tu fais ça pour synchroniser 2 collections différentes, ca appuie d'autant plus l'hypothèse d'instance d'objets différentes. Par ailleurs, 2 sources de données pour stocker les même données est une mauvaise façon de procéder.

                                                            -
                                                            Edité par Nisnor 2 novembre 2018 à 17:09:59

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter

                                                            Liaison Model et ViewModel

                                                            × 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