How to bind Dictionary to combobox using {Binding}?

Dictionary filled with elements, but the binding is not happening. What's the problem?

<combobox margin="0,0,10,0" itemssource="{Binding clients}" selectedvaluepath="Key" displaymemberpath="Value"></combobox>


To declare and initialize a Dictionary variable
Dictionary<string, string> clients;

public AddOrder()
{
 InitializeComponent(); 
 connection = new OleDbConnection(ConfigurationManager.ConnectionStrings["AccessBarbershop"].ConnectionString);
 clients = GetClients();
}</string>


Method that returns the Dictionary.
private Dictionary<string, string> GetClients()
{
 Dictionary<string, string> clients = new Dictionary<string, string>();
 using (connection)
{
 if (connection.State != ConnectionState.Open)
connection.Open();
 string request = "SELECT Codcliente, name FROM Customers";
 using (OleDbCommand command = new OleDbCommand(request, connection))
 using (OleDbDataReader reader = command.ExecuteReader())
 while (reader.Read())
 clients.Add(reader["Codcliente"].ToString(), reader["name"].ToString());
}
 return clients;
}</string></string></string>


Changed methods added class.
private ObservableCollection<clients> GetClients()
{
 var clients = new ObservableCollection<clients>();
 if (connection.State != ConnectionState.Open)
connection.Open();
 string request = "SELECT Codcliente, name FROM Customers";
 using (OleDbCommand command = new OleDbCommand(request, connection))
 using (OleDbDataReader reader = command.ExecuteReader())
 while (reader.Read())
 clients.Add(new Clients(reader["Codcliente"].ToString(), reader["name"].ToString()));
 return clients;
}
}

public class Client : INotifyPropertyChanged
{ 
public Client(string id, string name)
{
 ID = id; Name = name;
}
string id;
public string ID
{
 get { return id; }
 set { id = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID")); }
}
string name;
public string Name
{
 get { return name; }
 set { name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); }
}

public event PropertyChangedEventHandler PropertyChanged;
}</clients></clients>


<combobox margin="0,0,10,0" itemssource="{Binding clients}" selectedvaluepath="ID" displaymemberpath="Name"></combobox>


Solution
July 8th 19 at 16:37
3 answers
July 8th 19 at 16:39
Solution
You did it all wrong.

1) To the binding worked, the object to which the property is attached (i.e., one IN WHICH is clients) must implement INotifyPropertyChanged and pull PropertyChanged when you change its properties to bind were able to learn about it; specifically in your case, it may not be so critical, but as soon as other properties in addition to clients of a primitive type, you will realize that you can't become attached to them;

2) for the binding to work, you need to bind to properties. Yeah, maybe the box will work, but from the point of view of architecture and the idea of MVVM is movement against the wind;

3) Binding in your case partially working, because apparently it pulls the data from the dictionary when it already exists, and the items in it are loaded, but does not receive information about what the content of a dictionary as something is changed. She may get this information, because an ordinary dictionary does not implement INotifyCollectionChanged. Pay attention, this is a different interface for collections. Realizing his collections generate the CollectionChanged event when they someone change. This allows you to add all bindings of all changes in the collection, for example if you have added to the collection a new item. Of the standard collections that implement INotifyCollectionChanged, ObservableCollection is, functionality which is enough in 95% of cases. This is the most common collection (read - list. But not a dictionary!), with the difference that it implements INotifyCollectionChanged, and, consequently, to work correctly with bindings.

4) Attached to the elements of the dictionary is also not a good idea, especially if you do not fully understand how it works. Subject to paragraph 3, it is much wiser to make ViewModel Customer with properties Id and Name, and instead of the dictionary to use ObservableCollection<clientviewmodel></clientviewmodel>. In this case, ViewModel ClientViewModel must implement INotifyPropertyChanged, if its properties can change. Accordingly, the SelectedValuePath is the Id (if you need), and the DisplayMemberPath is the Name. Then it should work.
Changed methods added class, but the data is still somehow not displayed. - verna.Adams commented on July 8th 19 at 16:42
code/solyushen in the Studio - Breana_Leffl commented on July 8th 19 at 16:45
: added the modified code in the post. - verna.Adams commented on July 8th 19 at 16:48
I would have still made the clients property, not a field.
In General, it looks adequate. What is specifically expressed in dormant binding? What do you do with clients that is not reflected in the combobox? - Breana_Leffl commented on July 8th 19 at 16:51
: the combobox is empty. - verna.Adams commented on July 8th 19 at 16:54
an array of clients exactly zapolnyaetsya?
Binding in Debug mode and write it to the Output window binding errors, if any. Run the program and see if errors in the Output window - Breana_Leffl commented on July 8th 19 at 16:57
the array is populated in the output window there are no errors for the binding. - verna.Adams commented on July 8th 19 at 17:00
Easier to make a model Client and make ClientsViewModel(of course by implementing INPC), and in the VM to return a ObservableCollection, but when you create the window DataContext to bind(window, page), for instance ClientsViewModel - Josef commented on July 8th 19 at 17:03
then the complete code here (preferably in the form of archive solyushenami), because apparently doing something else. - Breana_Leffl commented on July 8th 19 at 17:06
: rgho.st/8XwJSjGQj - verna.Adams commented on July 8th 19 at 17:09
Fixes:
1) in AddOrder.xaml {Binding clients} change to {Binding Clients}
2) from AddOrder.cs can all be removed, except for the constructor. In the constructor, leaving only the call to InitializeComponents();
3) ClientsViewModel.cs type properties Clients are changing with an IEnumerable (I don't know why you decided to put as IEnumerable, exhibit collection needs so that the INotifyCollectionChanged interface was visible. And you "cut", exposing the collection as IEnumerable, binding is thought that there is not ObservableCollection, and primitive enumerable object) on the ObservableCollection. Make a public property - you don't put the access modifier public, in sharp by default, all private.
4) AddOrderButton_Click right after the line creating the window (AddOrder addOrder = new AddOrder();) write the following:
addOrder.DataContext = new ClientsViewModel();
Is setting the data context for the window, in fact it is the most important thing, because without it binding in principle, do not know to which object to bind to. All of the properties specified in the bindings (for example, Clients in your case) are searched in the current DataContext, which can be specified as a whole for the window (in most cases for simple Windows like your order is Added) and for a specific control (in particular, you can set the DataContext via the binding to the DataContext property of the parent).

Comments on the styling, which will simplify your understanding and work:
1) AddOrder and AddService suggest to rename AddOrderWindow and AddServiceWindow. First, it is still Windows, and secondly, don't suggest to name the classes of verb names (with the exception of classes of functors, but in Sharpe that possibility is still there). The class is an entity, so it is almost always a noun. Even if your class is created for a specific action, call it a verbal noun, such as the StreamReader or JsonSerializer;
2) rename the class Clients in the ClientViewModel, and now the confusion arises;
3) CallerMemberName is in principle a solution to the problem of transfer of title of the properties, but it's an old crutch with a not so obvious behavior and fit it not always. Now it is convenient and correct to use the nameof operator:

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ID)));

instead

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID"));

The nameof operator gives you the security of renaming properties because the compiler substitutes the string literal on the transmitted identifier, you can rename the properties, including the means of refactoring and you get so that the Name property is renamed to FullName, and the arguments of the event still passes the literal "Name". And Yes, to use CallerMemberName, you need to pass a parameter to a NotifyPropertyChanged to the compiler framed it myself. - Breana_Leffl commented on July 8th 19 at 17:12
If I rename Clients in the ClientViewModel, then I will have two ClientViewModel. - verna.Adams commented on July 8th 19 at 17:15
now you ClientsViewModel, not ClientViewModel. And they perform completely different tasks. ClientViewModel is ViewModel for one client. ClientsViewModel for all. In General, ClientsViewModel on the fact that you have a model to display the add order. I think it would be more logical to rename ClientsViewModel in AddOrderViewModel.

The rest happened? - Breana_Leffl commented on July 8th 19 at 17:18
: have done all the changes but still nothing displays - verna.Adams commented on July 8th 19 at 17:21
maybe I missed something in the list above, perezaleyte archive project - Breana_Leffl commented on July 8th 19 at 17:24
: rgho.st/6lrk7GjJy - verna.Adams commented on July 8th 19 at 17:27
as I mentioned, the Output window is useful for tracking bind failures. Now when opening the window the add order, we see the following:

System.Windows.Data Error: 40 : BindingExpression path error: '–°lients' property not found on 'object' "AddOrderViewModel' (HashCode=31470215)'. BindingExpression:Path=–°lients; DataItem='AddOrderViewModel' (HashCode=31470215); target element is 'ComboBox' (Name="); target property is 'ItemsSource' (type 'IEnumerable')

Very strange that the object AddOrderViewModel failed to find property Clients, because it there still is. Therefore, check into the classic mistake of a Russian programmer, and see that you accidentally wrote the Russian letter With the name of the properties Clients in the AddOrder.xaml - Breana_Leffl commented on July 8th 19 at 17:30
: Oh God, thank you so much. How did I miss this - verna.Adams commented on July 8th 19 at 17:33
July 8th 19 at 16:41
in one place
using (connection)
 in another
connection = new OleDbConnection


why?

on timeu
binding
source
datacontext
elementname
source
public property get (not field)
connection is used in other methods. - verna.Adams commented on July 8th 19 at 16:44
Then using(connection) then looks very strange. Or do you create each time the object or it is not removed. I've never tried after the connection's dispose to do open, but vygljadit very strange and seems to give an exception disposed - Breana_Leffl commented on July 8th 19 at 16:47
: do. I never thought about it. - verna.Adams commented on July 8th 19 at 16:50
: for the rest also wrote, if you want even more, wait for more responses or contacts in the mail. Good luck. - Breana_Leffl commented on July 8th 19 at 16:53
July 8th 19 at 16:43
A DataContext assigned?
No, only ItemsSource. - verna.Adams commented on July 8th 19 at 16:46
: That's why you and is not binding. DataContext points to an object, the properties of which binds. Take any MVVM framework (I'm hooked now on Caliburn Micro), everything is ready. Instead of ADO, go to EF for example, you will model tied to the DB, not have to be tormented with queries. - Breana_Leffl commented on July 8th 19 at 16:49
I will try to do. Thank you - verna.Adams commented on July 8th 19 at 16:52
You are now in the class Clients to do so

public class ClientsViewModel : INotifyPropertyChanged
IEnumerable Clients
{
get { return GetClients; }
}
.....
implementation of INCP
}

Take away everything else.
Make the Client class with the necessary fields(and implementing INPC).

in the method GetClients returns ObservaibleCollection if you need to add in the process a collection of data, and that they are updated in the DataGrid or ListBox or any IEnumerable, if that is not necessary.

Before showing a window to newWindow do.DataContext=new ClientsViewModel();

and all a better look at CaliburnMicro, all created for you. - Breana_Leffl commented on July 8th 19 at 16:55
: And as I ClientsViewModel implement INotifyPropertyChanged? - verna.Adams commented on July 8th 19 at 16:58
:

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

namespace ExampleINotifyPropertyChanged
{
class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void NotifyPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

change namespace to your all models and VM inherit from PropertyChangedBase

an example implementation

private string _amount;

public string Amount
{
get { return _amount; }
set
{
_amount = value;
NotifyPropertyChanged();
}
}

https://wcoder.github.io/notes/implementation-inot... - Breana_Leffl commented on July 8th 19 at 17:01
: in xaml to write consistently? or remove ItemsSource? - verna.Adams commented on July 8th 19 at 17:04
Change the bindings on your field in the ViewModel, SelectedValuePath and DisplayMemberPath, according to the model Client - Josef commented on July 8th 19 at 17:07

Find more questions by tags WPFC#XAML