0

I'm doing a simple binding of a Combobox in Page:

XAML:

 <ComboBox x:Name="Cmb_test"  Grid.Column="2" Grid.Row="2" HorizontalAlignment="Left"  ItemsSource="{Binding}" />

CODE behind:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
     //Binding of label works fine everytime
     My_label.Content = dt.Rows[0]["Column1"];

     Cmb_test.DataContext = dt.DefaultView;
     Cmb_test.SelectedValuePath = dt.Columns[3].ToString();
     Cmb_test.DisplayMemberPath = dt.Columns[4].ToString();

     //Just a check to see whether DataTable really has changed
     Console.WriteLine(dt.Rows.Count.ToString());
 }

But whenever my DataTable "dt" get's changed, my Combobox doesn't display Items anymore. I know there were a lot of questions allready asked regarding this issue, but all I could found were problems associating with refreshing during run-time. In my case I close a Page and re-open It when DataTable is changed, but result is empty Combobox.

Code for closing my Page, for addition:

CODE in same Page as Combobox:

private void BtnClose_Click(object sender, RoutedEventArgs e)
{
      Cmb_test.ItemsSource = null;
      Cmb_test.DataContext = null;

      var main_window = Application.Current.MainWindow;
      var frame = (main_window as MainWindow).My_Frame;
      frame.Content = null;

}

Code in MainWindow:

private void My_Frame_Navigated(object sender, NavigationEventArgs e)
{
     if (My_Frame.Content == null)
     {
        My_Frame.RemoveBackEntry();
     }
}

EDIT - another attempt:

XAML:

  <Page.Resources>
        <CollectionViewSource x:Key="My_source"/>
    </Page.Resources>

  <ComboBox x:Name="Cmb_test" ItemsSource="{Binding Source={StaticResource My_source}}" DisplayMemberPath="Column1"/>

CODE behind:

 private void Page_Loaded(object sender, RoutedEventArgs e)
        {

            var combo_datasource = new CollectionViewSource();
            combo_datasource = (CollectionViewSource)this.FindResource("seznamVrstEvidenc");
            combo_datasource.Source = Tabele.dt_Sifrant.DefaultView;

        }

What is going on here, how can I fix combobox to show It's Items everytime ?

Lucy82
  • 654
  • 2
  • 12
  • 32
  • What exactly do you mean by whenever your datatable gets changed? When you do what... the combobox has no items in it. – Andy Feb 20 '20 at 07:58
  • BTW.. Unless this is a "wizard" you're building with steps the user will want to go through and back. Rather than using frames I recommend contentcontrol and usercontrol instead of frame frames and pages. I would also translate a datatable into a typed list or observablecollection so you have the name of a property rather than column index. And... Even if ui is totally dynamic the current advice is to build ui from xaml as strings or templates. – Andy Feb 20 '20 at 08:11
  • @Andy, my DataTable - that is source of Combobox can be changed in one of my Window. to do that I have to first close my Page. DataTable get's changes, and I can see those canges in every other binded controls (Textbox,Labels, Datagrid), except Combobox. Can you show me some simple observable collection example ? – Lucy82 Feb 20 '20 at 08:17
  • If you have ado then the simplest step change would be to use a micro orm like Dapper. Dapper gives you a bunch of extension methods that make data access simpler. You can define a viewmodel for your data row. You can then fill a list of those. Observablecollection has a constructor takes a list. You can do filtering and sorting using various collectionview. – Andy Feb 20 '20 at 08:29
  • Also. Implement inotifypropertychanged on viewmodels. If you bind to a public property which is a collectionview you also need to raise a property changed event for a binding to "know" you switched that collectionview to a new one. – Andy Feb 20 '20 at 08:37
  • If you remove or add rows from an observablecollection that raise collection changed and any itemscontrol lwhose itemssource is bound to that collection will respond. Datatable also does this. If you just switch out a datatable then that could well cause problems. It's rarefy a good plan to work with a datatable though. With a collection of viewmodel you have somewhere to put business logic, validation etc. Datatables are also heavier objects. Although that doesn't matter in a simple app that doesn't have much data. – Andy Feb 20 '20 at 08:59
  • @Andy, well I've done this for first time in WPF and It looks very odd to me, I never had any simmilar problems in Winforms. I'll try to follow a code in link that Federicco posted to see If I will be able to get It working. – Lucy82 Feb 20 '20 at 09:06

2 Answers2

1

You're not binding your ComboBox to anything

<ComboBox x:Name="Cmb_test" [...] ItemsSource="{Binding}" />

You should have some sort of collection

<ComboBox x:Name="Cmb_test" [...] ItemsSource="{Binding MyList}" />

Looking at the rest of your code it seems like you're "manually binding" the ComboBox to a DataTable. You can then create a Binding programmatically that links the ComboBox ItemsSource to the DataTable DefaultView.

There is a problem though, if what you mean with "DataTable is changed" is something like

dt = new DataTable();

or

dt = Db.GetTable();

you will likely fall in the same problem again because the binding is done between two instances, so when a new dt is created you have to rebind it to the ComboBox.

Another way of solving the problem could be to set the ComboBox ItemsSource every time you have a new DataTable.

I hope I was helpful.

--------- UPDATE --------

Following my comment I would go with implementing INotifyPropertyChanged on the class that stores the DataTable dt.

public class ThatParticulareClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {  
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // [...] all the other stuff

    public void MethodThatUpdateDataTable()
    {
        // Update DataTable
        NotifyPropertyChanged(nameof(dt));
    }
}

That should work. If your changes on the DataTable object came only from the user (from the view) than you should register at the control which expose the DataTable an the ending edit event (something like DataGrid.RowEditEnding). At that point you call NotifyPropertyChanged(nameof(dt)); (Be sure that this call is from the same class that contains the DataTable dt)

Implementation

Federico Rossi
  • 415
  • 5
  • 11
  • Look at the markup again Frederico. See ItemsSource="{Binding}" ? and Cmb_test.DataContext = dt.DefaultView; – Andy Feb 20 '20 at 08:31
  • @Federicco Rossi, DaTable is allways same one (global static DataTable), no new instances created, that is why It looks even more weird to me. I've tried even setting new CollectionViewSource manually, but that doesn't help either. Will post in edited code in a minute, check It out. – Lucy82 Feb 20 '20 at 08:49
  • You're right, I didn't know you could do that. I've tried a small example and it worked perfectly. But in it I did reset the DataContext each time I wanted some updates. In this case, if the DataTable instance is always the same, the problem is probably that the DataTable is not notifying the ComboBox of the changes. So as previously said by you, @Andy , you have to do something about that. Use a ObservableCollection or implement INotifyPropertyChanged and call something like `RaisePropertyChanged(nameof(DataTable));` – Federico Rossi Feb 20 '20 at 08:49
  • @FedericoRossi, I'm a WPF beginner, so this is very strange to me. All other controls works fine except this one. Should I follow code in link you provided for implementing what you mentioned ? – Lucy82 Feb 20 '20 at 08:56
  • @FedericoRossi, this is too hard for me, I'm struggling with It. Any chance to show me an example ? – Lucy82 Feb 20 '20 at 09:36
  • 1
    Yes, i'm experimenting. If I were you I would go for the implementation [link](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.8) of INotifyPropertyChanged with the call of the NotifyPropertyChanged on the DataTable whenever you update it. I'll edit my answer whenever i've got something to show you – Federico Rossi Feb 20 '20 at 09:42
  • @FedericoRossi, you probably didn't notice part where I said that I'm a WPF beginner. What you posted is still very new to me, so I'm completely lost. From what I've learned today is that in this cases I have to know MVVM pattern, which is the bottom problem, so I need to learn this concept. But I've managed to do something on my own that now works for me, I will post that soon. However, I didn't even imagine that I would need to do this much of work for a thing that I ussually solved in a line of code in Winforms. Honestly, all my excitement for developing WPF apps has now reached bottom :)) – Lucy82 Feb 20 '20 at 13:14
  • 1
    Yeah, Wpf it's powerful but sometimes it can be confusing. – Federico Rossi Feb 20 '20 at 13:29
  • @FedericoRossi, what do you think about my posted answer, is that an option to be accepted ?..It works for me, but problems continue when I try to obtain Combobox SelectedItem text Value. Only thing that works is "Cmb_Test.SelectedValue.ToString()", but for that you have to assign SelectedValue in XAML or else It doesn't work. That would be a problem If I needed different column for SelectedValue, because there is no "SelectedDisplayValue" method for reading just a text from It - and SelectedItem returns only a type of View_Model class !!... Another bump I guess :) – Lucy82 Feb 20 '20 at 14:04
  • @FedericoRossi, I'm sorry but I don't know how to use your solution.Is this class supposed to be set as DataContext of my Page ? And more important question - what goes in "all other stuff" comment section ? And how does then XAML look like, like in my original post or changes needed too ? – Lucy82 Feb 21 '20 at 07:37
  • 1
    I'm gonna try to make an example repo, and share it to you, stay tuned. – Federico Rossi Feb 21 '20 at 09:51
  • I've added a Repository – Federico Rossi Feb 21 '20 at 11:41
  • 1
    @FedericoRossi, thanks, now I understand, nice solution. It Will come in good use for further projects too :) – Lucy82 Feb 24 '20 at 14:03
0

I found a solution. I must say a very frustrating thing to learn before you come fammiliar with WPF and binding controls...

I followed numerious amount of articles in order to understand that in WPF you should use MVVM pattern in order to correctly bind your controls. However, there are so many different approaches doing this, so I ended up in something that is at least little understandable to me at this point:

1.) Create Model class. Here you define your DataTable columns and expose them as properties. This class needs to be inherited from INotifyPropertyChanged:

 class Test_Model : INotifyPropertyChanged
 {
        private string col1;
        private string col2;

        public string Col1
        {
            get
            {
                return col1;
            }
            set
            {
                col1 = value;
                OnPropertyChanged("Col1");
            }
        }
        public string Col2
        {
            get
            {
                return col2;
            }
            set
            {
                col2 = value;
                OnPropertyChanged("Col2");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
   }

2.) Create Viev_Model class.Here you create an IList (of Test_Model type) and fill It with DataTable, I used LINQ for that - that was also a challenge. Next, you again expose a property of IList type, in order to use that as your Datasource:

 class Test_ViewModel
 {
        private IList<Test_Model> _comboData;

        public Test_ViewModel()
        {
            _comboData = dt.AsEnumerable().
                Select(row => new Test_Model
                {
                    Col1 = row.Field<string>(0),
                    Col2 = row.Field<string>(1)
                }).ToList();
        }

        public IList<Test_Model> ComboData
        {
            get { return _comboData; }
            set { _comboData = value; }
        }
  }

3.) In your Window or Page assign DataContext to View_Model class. That is done in constructor, like this:

 public partial class My_Page: Page
 {
        public My_Page()
        {
            InitializeComponent();
            this.DataContext = new Test_ViewModel(); //assign ViewModel class
        }

        //...
  }

4.) Bind combobox to View_Model class:

  <ComboBox x:Name="Cmb_Test" ItemsSource="{Binding Path= ComboData}" 
  DisplayMemberPath="Col1" SelectedValuePath="Col1" SelectedIndex="0"/>

And then It worked, finally. Although I'm not happy with solution, so I have to figure out more simple way of doing proper binding. I ussually have many controls to bind & If I would do this for every DataTable I need, I would end up with tons of code in classes.

Lucy82
  • 654
  • 2
  • 12
  • 32
  • Your solution is correct (even though I still have some doubts that it will update in case you add or remove an instance of `Test_Model` from `Test_ViewModel._comboData`). We tried to suggest a more quick way of operating. It sill pass through the INotifyPropertyChanged but at lest you can call it only once. You make your code-behind implements INotifyPropertyChanged and so, evetytime you need, you can call OnPropertyChanged(nameof(dt)) to make the "binding" and so the control (a BomboBox in this case) update. A good time to call that would be an event fired after the completition of an edit. – Federico Rossi Feb 20 '20 at 14:18
  • @FedericoRossi thanks for all your help. I Will try your example too, because It's shorter than mine, so I guess that is not MVVM pattern. For update concerns - luckily I don't have to deal with that yet, in this čase Combobox is used only for displaying items. Though it contains SelecfionChanged event, from which I refresh Datagrid (with different static DataTable ). But Datagrid works fine with just setting dt.DefaultView, so I'm still confused why Combobox behaviour is different. I Will let you know when testing is finished and accept your answer If It works for me. – Lucy82 Feb 20 '20 at 15:44
  • You're welcome! No my example was not mvvm, it was meant to be a fast workaround – Federico Rossi Feb 20 '20 at 16:13