Search code examples
c#wpfmvvm

WPF C# MVVM - Not able to update bindings after database results


I'm trying to create an WPF application with the MVVM model.

At the moment i'm stuck at the following;

In the application there is the option to seach on a number (registration number), nothing fancy just a textbox.

When the user pressed "Search" I wan't to load the data from the DB in to the model and showing it on the user controls.

This is my MainWindowViewModel

public class MainWindowViewModal : ViewModelBase
{
    //Fields
    private string _zoekenUitvaartnummer;
    private string _zoekenAchternaam;
    private string _zoekenGeboortedatum;
    private bool _volgendePagina = false;

    private IOverledeneRepository overledeneRepository;

    //Properties
    public string ZoekenUitvaartnummer
    {
        get => _zoekenUitvaartnummer;
        set
        {
            _zoekenUitvaartnummer = value;
            OnPropertyChanged(nameof(ZoekenUitvaartnummer));
        }
    }

    //->Commands
    public ICommand SearchUitvaartnummerCommand { get; }

    public MainWindowViewModal()
    {
        overledeneRepository = new OverledeneRepository();
        SearchUitvaartnummerCommand = new ViewModelCommand(ExecuteSearchUitvaartnummerCommand, CanExecuteSearchUitvaartnummerCommand);
        SearchAchternaamCommand = new ViewModelCommand(ExecuteSearchAchternaamCommand, CanExecuteSearchAchternaamCommand);
        
        
    }

    private bool CanExecuteSearchUitvaartnummerCommand(object obj)
    {
        bool validNummerSearch;
        if(string.IsNullOrWhiteSpace(ZoekenUitvaartnummer))
        {
            validNummerSearch = false;
        }
        else
        {
            validNummerSearch = true;
        }
        return validNummerSearch;
    }


    private void ExecuteSearchUitvaartnummerCommand(object obj)
    {
        var SearchUitvaartleider = overledeneRepository.GetUitvaarleiderByUitvaartId(ZoekenUitvaartnummer);
        OverledeneViewModel.Instance.LoadCurrentUitvaartLeider(ZoekenUitvaartnummer);
    }

}

on overledeneRepository.GetUitvaarleiderByUitvaartId(ZoekenUitvaartnummer) the database is called

    public class OverledeneRepository : RepositoryBase, IOverledeneRepository
    {
        public OverledeneUitvaartleiderModel GetUitvaarleiderByUitvaartId(string uitvaartId)
        {
            OverledeneUitvaartleiderModel uitvaartLeiderGegevens = null;
            using (var connection = GetConnection())
            using (var command = new SqlCommand())
            {
                connection.Open();
                command.Connection = connection;

                command.CommandText = "SELECT UitvaartId, CONCAT(Initialen,' ',Achternaam) as PersoneelName, PersoneelId, Uitvaartnummer " +
                                        " FROM OverledeneUitvaartleider" +
                                        " INNER JOIN ConfigurationPersoneel ON PersoneelId = Id" +
                                        " WHERE Uitvaartnummer=@uitvaartNummer";

                command.Parameters.Add("@uitvaartNummer", System.Data.SqlDbType.VarChar).Value = uitvaartId;
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        uitvaartLeiderGegevens = new OverledeneUitvaartleiderModel()
                        {
                            UitvaartId = (Guid)reader[0],
                            PersoneelNaam = reader[1].ToString(),
                            PersoneelId = (Guid)reader[2],
                            Uitvaartnummer = reader[3].ToString(),
                        };
                    }
                }
            }
            return uitvaartLeiderGegevens;
        }
    }

the returned value is parsed to the viewmodel

public class OverledeneViewModel : ViewModelBase
{
    //Fields
    private string _uitvaartNummer;
    private string _uitvaartLeider;

    
    private IOverledeneRepository overledeneRepository;
    private OverledeneUitvaartleiderModel _uitvaartLeiderModel;

    //Properties
    public OverledeneUitvaartleiderModel UitvaartLeiderInfo
    {
        get
        {
            return _uitvaartLeiderModel;
        }

        set
        {
            _uitvaartLeiderModel = value;
            OnPropertyChanged(nameof(UitvaartLeiderInfo));
        }
    }
    
    private OverledeneViewModel()
    {
        overledeneRepository = new OverledeneRepository();
        UitvaartLeiderInfo = new OverledeneUitvaartleiderModel();
        SaveCommand = new ViewModelCommand(ExecuteSaveCommand, CanExecuteSaveCommand);
    }

     public static OverledeneViewModel Instance {get;} = new();

    public void LoadCurrentUitvaartLeider(string uitvaartNummer)
    {
        var UitvaarLeiderResult = overledeneRepository.GetUitvaarleiderByUitvaartId(uitvaartNummer);
        if (UitvaarLeiderResult != null)
        {
            UitvaartLeiderInfo.Uitvaartnummer = UitvaarLeiderResult.Uitvaartnummer;
            UitvaartLeiderInfo.PersoneelNaam = UitvaarLeiderResult.PersoneelNaam;

        }
    }
}

With this model behind

    public class OverledeneUitvaartleiderModel
    {
        public Guid UitvaartId { get; set; }
        public Guid PersoneelId { get; set; }
        public string PersoneelNaam { get; set; }
        public string Uitvaartnummer { get; set; }
    }

and finally this is the xaml where I would expect the data to be visable

<UserControl x:Class="Test.Views.OverledeneView"
   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:local="clr-namespace:Test.ViewModels"
   mc:Ignorable = "d" 
   d:DesignHeight="696.96" d:DesignWidth="1190.257"
   DataContext="{x:Static local:OverledeneViewModel.Instance}">
    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
        <Grid x:Name="GridOverledeneView" Background="White" ScrollViewer.CanContentScroll="True">
            <TextBox x:Name="input_UitvaartNrOverledene" Text="{Binding Path=uitvaartLeiderModel.Uitvaartnummer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TabIndex="1" VerticalContentAlignment="Center" HorizontalAlignment="Left" Margin="204,14,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="301" Height="31" FontSize="16" SelectionBrush="#FFD70000" FontFamily="Arial" BindingGroup="{Binding Text, ElementName=UitvaartLeider}"/>
        </Grid>
    </ScrollViewer>
</UserControl>

this is the ViewModelBase

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

What am I doing wrong?

Can anybody help me and explain to me what i'm doing wrong?

Any help would be really appreciated.

Patrick


Solution

  • Most likely you have confusion in instances of the OverledeneViewModel class. In the MainWindowViewModal.ExecuteSearchUitvaartnummerCommand method you create the first instance of OverledeneViewModel. And run the verification method on this instance.

        private void ExecuteSearchUitvaartnummerCommand(object obj)
        {
            var SearchUitvaartleider = overledeneRepository.GetUitvaarleiderByUitvaartId(ZoekenUitvaartnummer);
            var DL = new OverledeneViewModel();
            DL.LoadCurrentUitvaartLeider(ZoekenUitvaartnummer);
        }
    

    In the XAML of the OverledeneView View class, you create another instance of the OverledeneViewModel that is in an initial immutable state.

        <UserControl.DataContext>
            <local:OverledeneViewModel/>
        </UserControl.DataContext>
    

    If you don't need multiple instances of OverledeneViewModel at the same time, then try using Singleton. Example:

    public class OverledeneViewModel : ViewModelBase
    {
        // Some Code
        
        // Hiding the constructor
        private OverledeneViewModel()
        {
            overledeneRepository = new OverledeneRepository();
            UitvaartLeiderInfo = new OverledeneUitvaartleiderModel();
            SaveCommand = new ViewModelCommand(ExecuteSaveCommand, CanExecuteSaveCommand);
        }
    
         // The only instance available
         public static OverledeneViewModel Instance {get;} = new();
    
    }
    
        private void ExecuteSearchUitvaartnummerCommand(object obj)
        {
            var SearchUitvaartleider = overledeneRepository.GetUitvaarleiderByUitvaartId(ZoekenUitvaartnummer);
            // var DL = new OverledeneViewModel();
            OverledeneViewModel.Instance.LoadCurrentUitvaartLeider(ZoekenUitvaartnummer);
        }
    
    <UserControl x:Class="Test.Views.OverledeneView"
       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:local="clr-namespace:Test.ViewModels"
       mc:Ignorable = "d" 
       d:DesignHeight="696.96" d:DesignWidth="1190.257"
       DataContext={x:Static local:OverledeneViewModel.Instance}>
        <!--<UserControl.DataContext>
            <local:OverledeneViewModel/>
        </UserControl.DataContext>-->
    

    When I put the result in the messagebox it's showing 1234, maybe the binding is off somewhere? really lost here

    I didn't notice the second mistake right away.
    In "OverledeneView" you have set the binding:

        <TextBox x:Name="input_UitvaartNrOverledene"
                 Text="{Binding Path=uitvaartLeiderModel.Uitvaartnummer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                 -----/>
    

    However, in the class "OverledeneViewModel" there is no property "uitvaartLeiderModel".
    I think you need a binding like this:

        <TextBox x:Name="input_UitvaartNrOverledene"
                 Text="{Binding UitvaartLeiderInfo.Uitvaartnummer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                 -----/>
    

    P.S. There are also notes on the implementation of the "OverledeneUitvaartleiderModel" class. It does not have an implementation of INotifyPropertyChanged, which means that when its properties change, the view will not be automatically updated. I think you'd be better off replacing this class with a read-only structure.