'How to perform a correct binding using wpf MVVM

I'm working on a bank application in c# wpf using the MVVM pattern which allows a manager in charge of a branch to display the data of one of the customers of this branch in another tab by selecting the line corresponding to this customer. I use a custom framework. Here is my Xaml code corresponding to the main view that displays the agencies for which the Manager is responsible:

 <f:UserControlBase x:Class="BankingApp.View.ManagerAgencyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:BankingApp.ViewModel"
             xmlns:vw="clr-namespace:BankingApp.View"
             xmlns:f="clr-namespace:PRBD_Framework;assembly=PRBD_Framework"
             mc:Ignorable="d" 
             d:DataContext="{d:DesignInstance Type=vm:ManagerAgencyViewModel, 
               IsDesignTimeCreatable=False}"
             FontSize="14" d:DesignHeight="498" d:DesignWidth="918">
    <UserControl.DataContext>
        <vm:ManagerAgencyViewModel x:Name="vm"/>
    </UserControl.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <TextBlock  Margin="5" Text="Agency:"/>
        <ComboBox 
                Margin="5,5,5,5"
               Grid.Column="1"
                    ItemsSource="{Binding Path=Agencies,Mode=TwoWay}"
                      DisplayMemberPath="Name"
                    SelectedItem="{Binding SelectedAgency}"
                />
        <f:MyDataGrid
            Grid.Row="1"
            ItemsSource="{Binding Clients}"  
            SelectedItem="{Binding SelectedClient}"
            CanUserAddRows="False"
            AutoGenerateColumns="False"
            x:Name="gridAccesses" Grid.ColumnSpan="2" Margin="0,0,5,0"
            >
            <DataGrid.Columns>
                <DataGridTextColumn  Header="idClient" Width="auto" Binding="{Binding UserId}" 
                 IsReadOnly="True" />
                <DataGridTextColumn Header="FirstName" Width="*" Binding="{Binding FirstName}" 
                 IsReadOnly="True" />
                <DataGridTextColumn Header="LastName" Width="*" Binding="{Binding LastName}" 
                 IsReadOnly="True" />
            </DataGrid.Columns>
        </f:MyDataGrid>
        <Button Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Width="80" Margin="10" 
          Content="New Client"  Command=""/>

        <f:MyTabControl Grid.Row="3" Grid.ColumnSpan="2" >
            <TabItem Header="Client">
                <vw:ManagerClientDataView  x:Name="ClientData"  DataContext="{Binding 
                 ManagerClientData, ElementName=vm}"/>
            </TabItem>
            <TabItem Header="Account">
                <vw:ManagerClientAccount/>
            </TabItem>
        </f:MyTabControl>
    </Grid>

</f:UserControlBase>

When I select on one of the agencies, it shows me all the clients of this agency like this: enter image description here

here is the corresponding viewModel

 using BankingApp.Model;
using PRBD_Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BankingApp.ViewModel {
    public class ManagerAgencyViewModel : ViewModelCommon {
        public ManagerClientDataViewModel ManagerClientData { get; private set; } = new 
        ManagerClientDataViewModel();
        public ManagerAgencyViewModel() {
            OnRefreshData();

        }

        public ObservableCollectionFast<Agency> Agencies { get; set; } = new();
        public ObservableCollectionFast<Client> Clients { get; set; } = new();

        private Client _selectedClient;
        public Client SelectedClient {
            get => _selectedClient;
            set {
                SetProperty(ref _selectedClient, value, () => ManagerClientData.SelectedClient = 
                            value);

            }
        }
        private Agency _selectedAgency;
        public Agency SelectedAgency {
            get => _selectedAgency;
            set => SetProperty(ref _selectedAgency, value, () => ClientOfAgency(SelectedAgency));
        }

        private void ClientOfAgency(Agency agency) {
            if (agency != null)
                Clients.RefreshFromModel(Context.Clients.Where(c => c.Agency.AgencyId == 
                agency.AgencyId));
        }
        protected override void OnRefreshData() {
            Agencies.RefreshFromModel(Context.Agencies.OrderBy(a => a.Name));
            ClientOfAgency(SelectedAgency);
        }
        public void init() {

        }
    }
}

Here is the code of my tab to display the data of the selected client:

 <f:UserControlBase x:Class="BankingApp.View.ManagerClientDataView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:BankingApp.ViewModel"
             xmlns:vw="clr-namespace:BankingApp.View"
             xmlns:f="clr-namespace:PRBD_Framework;assembly=PRBD_Framework"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Margin="0,10,0,10">
            <TextBlock Text="FirstName:"/>
            <TextBox Text="{Binding Firstname}"/>
        </StackPanel>

        <StackPanel Grid.Row="1" Margin="0,0,0,10">
            <TextBlock Text="LastName:"/>
            <TextBox Text="{Binding Lastname}"/>
        </StackPanel>

        <StackPanel Grid.Row="2"  Margin="0,0,0,10">
            <TextBlock Text="Email:"/>
            <TextBox Text="{Binding Email}"/>
        </StackPanel>

        <StackPanel Grid.Row="3"  Margin="0,0,0,10">
            <TextBlock Text="Password:"/>
            <PasswordBox f:PasswordHelper.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>

        <StackPanel Grid.Row="4">
            <TextBlock Text="Confirm Password:"/>
            <PasswordBox f:PasswordHelper.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
    </Grid>
</f:UserControlBase>

And the corresponding viewmodel

    using BankingApp.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PRBD_Framework;

namespace BankingApp.ViewModel {
    public class ManagerClientDataViewModel : ViewModelCommon {

        private Client _selectedClient;
        public Client SelectedClient {
            get => _selectedClient;
            set {
                SetProperty(ref _selectedClient, value);
                Console.WriteLine(SelectedClient.FirstName);
            }
        }

       public string Firstname {
            get => SelectedClient?.FirstName;
            set => SetProperty(SelectedClient.FirstName, value, SelectedClient, (s, v) => {
                s.FirstName = v;
                Console.WriteLine(s.FirstName);
            });

        }
        public string Lastname {
            get => SelectedClient?.LastName;
            set => SetProperty(SelectedClient.LastName, value, SelectedClient, (s, v) => {
                s.LastName = v;
                Console.WriteLine(s.LastName);
            });

        }

        public string Email {
            get => SelectedClient?.Email;
            set => SetProperty(SelectedClient.Email, value, SelectedClient, (s, v) => {
                s.Email = v;
                Console.WriteLine(s.Email);
            });
        }

        public string Password {
            get => SelectedClient?.Password;
            set => SetProperty(SelectedClient.Password, value, SelectedClient, (s, v) => {
                s.Password = v;
            });

        }
      
    

    }
}

My problem is that when I select a customer, his data does not appear in the form, could someone help me? Thanks



Solution 1:[1]

this is a working example without the use of any frameworks. Maybe this helps to track down your issue or just use this code ;-)

XAML:

 <Window x:Class="WpfApp1AgencyTest.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:WpfApp1AgencyTest"
        mc:Ignorable="d"
        xmlns:vm="clr-namespace:WpfApp1AgencyTest"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:ManagerAgencyViewModel/>
    </Window.DataContext>
    <Grid>
        <TabControl>
            <TabItem Header="Manager" >
                <StackPanel>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="auto"/>
                    </Grid.RowDefinitions>
                    <TextBlock Margin="5" Text="Agency:"></TextBlock>
                    <ComboBox Grid.Column="1" Margin="5,5,5,5"
                              ItemsSource="{Binding Path=Agencies}" 
                              DisplayMemberPath="Name"
                              SelectedItem="{Binding SelectedAgency}"
                              ></ComboBox>
                </Grid>
                <DataGrid
                    Grid.Row="1"
                    ItemsSource="{Binding SelectedAgency.Clients}"
                    SelectedItem="{Binding SelectedClient}"
                    AutoGenerateColumns="False"
                    
                    >
                    <DataGrid.Columns>
                            <DataGridTextColumn Header="id" Binding="{Binding ID}"></DataGridTextColumn>
                            <DataGridTextColumn Header="First name" Binding="{Binding Firstname}"></DataGridTextColumn>
                            <DataGridTextColumn Header="Last name" Binding="{Binding Lastname}"></DataGridTextColumn>
                        </DataGrid.Columns>
                    
                </DataGrid>
                    <TabControl>
                        <TabItem Header="Client">
                            <StackPanel DataContext="{Binding SelectedClient}">
                                <TextBlock>First name:</TextBlock>
                                <TextBox Text="{Binding Firstname}"></TextBox>
                                <TextBlock>Last name:</TextBlock>
                                <TextBox Text="{Binding Lastname}"></TextBox>
                                <TextBlock>Password:</TextBlock>
                                <TextBox Text="{Binding Password}"></TextBox>
                                <TextBlock>Confirm Password:</TextBlock>
                                <TextBox></TextBox>
                            </StackPanel>
                            
                        </TabItem>
                        <TabItem Header="Account">
                            
                        </TabItem>
                    </TabControl>
                </StackPanel>
            </TabItem>
        </TabControl>
    </Grid>

</Window>

class Client

    using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1AgencyTest
{
    public class Client : INotifyPropertyChanged
    {

        private string id;

        public string ID
        {
            get { return id; }
            set { id = value; RaisePropertyChange(); }
        }


        private string firstname;
        public string Firstname
        {
            get => firstname; set
            {
                firstname = value;
                RaisePropertyChange();
            }
        }

        private string lastname;
        public string Lastname
        {
            get => lastname; set
            {
                lastname = value;
                RaisePropertyChange();
            }
        }
        private string password;
        public string Password
        {
            get => password; set
            {
                password = value;
                RaisePropertyChange();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChange([CallerMemberName] string caller = "")
        {

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(caller));
            }
        }
    }
}

class Agency

    using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1AgencyTest
{
    public class Agency
    {
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; RaisePropertyChange(); }
        }

        public ObservableCollection<Client> Clients { get; set; } = new();
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChange([CallerMemberName] string caller = "")
        {

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(caller));
            }
        }
    } 

}

class ManagerAgencyViewModel

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1AgencyTest
{
    public class ManagerAgencyViewModel 
    {
        public ManagerAgencyViewModel()
        {
            Agencies =
                new ObservableCollection<Agency>() {
                   new Agency()
                   {
                       Name="Dexia",
                       Clients = new ObservableCollection<Client>()
                       {
                           new Client(){ID="1", Firstname = "b",Lastname="inconnu"},
                           new Client(){ ID="2",Firstname = "Mustafa",Lastname="Azoud" },
                           new Client{ID="3",Firstname="Samiha",Lastname="Draa"}
                       }
                   },
                   new Agency()
                   {
                        Name="2Advacend",
                       Clients = new ObservableCollection<Client>()
                       {
                           new Client(){ ID="4", Firstname = "Joe",Lastname="Doe"},
                           new Client(){ ID="5", Firstname = "Max",Lastname="Headroom" },
                          
                       }
                   }
                };
        }
        public ObservableCollection<Agency> Agencies { get; set; }


        private Agency selectedAgency;
        public Agency SelectedAgency
        {
            get => selectedAgency;
            set
            {
                selectedAgency = value;
                RaisePropertyChange();
            }
        }

        private Client selectedClient;
        public Client SelectedClient { get => selectedClient; set {
                selectedClient = value;
                RaisePropertyChange();
            } }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChange([CallerMemberName] string caller = "")
        {

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(caller));
            }
        }
    }

   
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 silverfighter