0

I am doing a little pet project in WPF to which I am quite new.

My (partial) goal as it follows:

  1. Open main window, which contains two Comboboxes labeled System and Documents
  2. Upon loading this window I connect to an .mdb file that contains a table with System and Document columns.
  3. It creates an ObservableCollection of all the distinct System names stored in the database file and stores them as an arrayList to which my "System" combobox is bound. It successfully fills up the combobox and chooses the first member of the list as a selected item.
  4. The selectedItem of the "System" combobow is OneWayToSource bound to a string called SystemFilter. Anytime I changed the selection this string is succesfully updated.
    1. Using this SystemFilter I manage to filter the documents and get the correlating list of titles that is stored in the TitleList observable collection and fills up correctly the "Document" combobox.

It works properly, however everytime I filter the database I create a connection with the file, filter the content extract it and close the connection. Here, in order to avoid opening this connection everytime, I decided to load in all the items in one ObservableCollection called DocumentList and I intend to run LINQ queries on it. and the results of the query i store in Lists that I bind to the combobox. PROBLEM: However in this case the Combobox itemlist does not want to UPDATE Automatically when I choose another system. What can be the reason for this?

/Note: I applied INotifyPropertyChanged in my BaseViewModel and I use FodyWeaver to weave my assembly)

Original working version:

  • Two Combobox:

            <!--System-->
            <TextBlock Margin="5 0 0 0" Text="System" FontWeight="Bold" HorizontalAlignment="Left"/>
            <ComboBox SelectionChanged="Combobox_Doc_Sys_SelectionChanged" x:Name="Combobox_Doc_Sys" 
                      ItemsSource="{Binding SystemList}" SelectedItem="{Binding SystemFilter,Mode=OneWayToSource}"
                      SelectedIndex="{Binding SystemIndex, Mode=TwoWay}"  Padding="2" Margin="5 0 5 0" >
            </ComboBox>
    
            <!--Document-->
            <TextBlock Margin="5 10 0 0" Text="Document" FontWeight="Bold" HorizontalAlignment="Left"/>
            <ComboBox x:Name="Combobox_Doc_Doc" SelectionChanged="Combobox_Doc_Doc_SelectionChanged" ItemsSource="{Binding TitleList}"  SelectedItem="{Binding TitleFilter, Mode=OneWayToSource}" 
                      Padding="2" Margin="5 0 5 0" SelectedIndex="0">
            </ComboBox>
    
  • Code-behind of MainWindow

        public MainWindow()
    {
        InitializeComponent();
        //Create ViewModel correalating to mainwindow
        this.DataContext = new MainWindowViewModel();
    
     }
    /// <summary>
    /// Event correlating the change of the selected item in Document section's System combobox
    ///// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Combobox_Doc_Sys_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        //Call ViewModel's function to filter documentlist based on the Selected system
        MainWindowViewModel.GetListOfTitle();
        //MainWindowViewModel.GetListOfTitle2();
    
        // Restores the selection to the first item in Document section's Document combobox
        Combobox_Doc_Doc.SelectedIndex = 0;
    }
    
  • GetListOfTitle() function with connection to the .mdb file:

       public static void GetListOfTitle()
    {
        //List that will store the list of the title
    
        //filtering  SQL
        string sysFilterSQL = $"SELECT * FROM DocList WHERE System='{SystemFilter}'";
    
        //Clear List for safety sake
        TitleList.Clear();
    
    
        using (OleDbConnection MyConnection = new OleDbConnection(ConnectStringDocList))
        {
            OleDbCommand command = new OleDbCommand(sysFilterSQL, MyConnection);
            command.Connection = MyConnection;
            MyConnection.Open();
    
            OleDbDataReader reader = command.ExecuteReader();
    
            //We fill up TitleList with the list of the titles filtered by "system"
            while (reader.Read())
            {
                var item = reader.GetString(2);
                TitleList.Add(item);
            }
    
            //Close reader
            reader.Close();
            //Close connection
            MyConnection.Close();
        }
    
    }
    
  • Parameters

     public static ObservableCollection<string> TitleList { get; set; } = new ObservableCollection<string>();
    

    public static string SystemFilter { get; set; } = String.Empty;

When I change to the new version where first I successfully fill up DocumentList with the documents. The GetListofTitle2 method:

        internal static void GetListOfTitle2()
    {

        TitleList = (from s in DocumentList where s.System == $"{SystemFilter}" select s.Title).ToList();

    }

With:

        public static ObservableCollection<DocumentModel> DocumentList { get; set; } = new ObservableCollection<DocumentModel>();
    //--------------------------------------------------------------------------------------------//
    public static string SystemFilter { get; set; } = String.Empty;
    //--------------------------------------------------------------------------------------------//

    public static List<string> TitleList { get; set; } = new List<string>();

In this new version the TitleList is again updated correctly but it doesn't update on the interface :/ What is the issue? I would like to solve it because this solution seems cleaner than everytime open connection and load in the .mdb file

Thanks for the answers in advance

UPDATE: If I modify the second version to still use observableCollections and modify GetListofTitle2 as it follows:

        public static void GetListOfTitle2()
    {
        TitleList =new ObservableCollection<string>((from s in DocumentList where s.System == $"{SystemFilter}" select s.Title).ToList());
    }

It still does not update the combobox even though TitleList contains the correct items.

Anstetten
  • 11
  • 3

2 Answers2

2
  1. If you change the instance of TitleList in the model, the model should implement INotifyPropertyChanged interface and you must fire a PropertyChanged event as explained here (also, in aftermath, take a look here).
  2. If the reference of the TitleList stay the same but mutate (add or remove elements), it must implement INotifyCollectionChanged and that exactly what ObservableCollection do.

This code:

TitleList = new ObservableCollection<string>(...);

Is of kind 1: you change the TitleList instance.

But you use an auto-property that don't fire a PropertyChanged event so the binding is not refreshed:

// this is an auto-property
public List<string> TitleList { get; set; }

// it's equivalent to:
private List<string> _titleList;
public List<string> TitleList
{
  get => _titleList;
  set => _titleList = value;
}    

You must fire a PropertyChanged event in the set block:

private List<string> _titleList;
public List<string> TitleList
{
  get => _titleList;
  set
  {
    // if the new value is the same as the old, just do nothing
    if (Equal(_titleList, value))
      return;

    _titleList = value;
    // This call will raise the event
    NotifyPropertyChanged();
  }
} 
Orace
  • 7,822
  • 30
  • 45
  • INotifyPropertyChanged is implemented in my BaseViewModel. As I understood what you said in this version, a PropertyChanged even has to be fired. However I thought Fody Properties is exactly what this is for. This means It doesn't work here? or I misunderstood what it is for and I do not implement it correctly (Sry rly new to this topic) – Anstetten Feb 03 '20 at 15:38
  • The `PropertyChanged` event should be fired on a `set` property call, you use automatic property: `TitleList { get; set; }`. You do not implement it correctly. Look at `PhoneNumber` implementation here at the bottom of the page: https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface – Orace Feb 03 '20 at 15:54
0

As Orace said (sorry i can't add comments) you need to signal your property changed, ObservableCollection signals when one of its element changes but it come from the object collection itself... of course if this object has changed, initialized with a new observablecollection it can't signal there is a modification. I hope you understand...

You must implement INotifyPropertyChanged, and fire the property changed when you initialize again. The other Solution is to clear this collection, then fill it with your request.

I'm not english so i hope i'm clear with my explanations.

Vonkel.
  • 306
  • 3
  • 12