Binding ListBox.SelectedItem to Property - c#

This might be a duplicate question, but I'm unable to find a good answer. All the answers like Binding WinForms ListBox to object properties don't work on my WinForm. I'll explain.
I have a list of Firms that I show in a ListBox. I would like when the SelectedItem changes, that it updates a property on my model. So that I can read the Firms properties.
// the classes
public class Firm
{
public string Name { get; set; }
public int Id { get; set; }
// more properties ...
}
public class MyModel : INotifyPropertyChanged
{
private Firm _firm = new Firm();
public Firm Firm
{
get { return _firm; }
set
{
if (Equals(value, _firm)) return;
_firm = value;
OnPropertyChanged();
}
}
// more properties and OnPropertyChanged() ...
}
// the form
private MyModel Model;
public void MyForm(List<Firm> firms)
{
lstFirm.DataBindings.Add("SelectedItem", Model, "Firm",
true, DataSourceUpdateMode.OnPropertyChanged);
lstFirm.DisplayMember = "Name";
lstFirm.ValueMember = "Id";
lstFirm.DataSource = firms;
}
public void lstFirm_SelectedIndexChanged(object sender, EventArgs e)
{
// Do something with Model.Firm
}
The problem is that Model.Firm null is. Does anybody have an idea what I need to do to make a databinding between the ListBox and the Model? I bind other stuff on my WinForm (such as TextBoxes to String properties) and those work nicely.

From what I can see, your code never sets Model.Firm... Where's the constructor for MyModel? If you don't provide one, Model.Firm will stay null unless you explicitly set it. Here's an example constructor:
public MyModel(Firm firm)
{
_firm = firm;
}
Also, Equals() doesn't do what you think it does. Instead of if (Equals(value, _firm)) return;, use this: if (value == _firm) return;

Ok, so after a weekend of testing, I figured it out.
I was debuging in the SelectedIndexChanged event and didn't see the change in my Model.Firm just yet. But as the SelectedItemChanged event is only internal, I couldn't use that and that's where the databinding on SelectedItem applies the values to databound items.
Now the reason why the change isn't visible yet, is because the SelectedItemChanged is only fired after the SelectedIndexChanged is executed. So internally in the ListBox control, it probably looks like
this.SelectedIndex = value;
this.SelectedItem = FindItem(value);
this.SelectedIndexChanged(/*values*/);
this.SelectedItemChanged(/*values*/); // Apply databinding changes
So it's quite normal that you don't see the changes, before the change has occured. And I didn't know this, so I was kinda stumped why the SelectedItem (who was displaying the changed value) wasn't copied over to the databound model property.
So I didn't have to change anything major to get it all working. :)

Related

How to update an ObservableCollection with a List and update only the changed values?

Edit:
Turns out I was merging a list with the latest data from a rest API with the data in my GUI. All I really had to do was clear and refill my observablecollection. This post is basically an xy problem. I did not have the vocabulary to explain the problem I was facing.
I'm building an app with a Data class where I store all my data. In this class I have a List filled with objects. I have a Page with a ObservableCollection and a ListView. Currently when I update the ObservableCollection, I clear it and refill it with all the data from Data-class's List. When I do this, my ListView flickers. My guess is that completely rebuilding the ObservableCollection causes this, in combination with a custom ViewCell that is not the lightests. How could I go about updating only what I want? The list/o.collection can have diffrent sizes. The list/o.collection both store the same object.
What I tried:
List<csharp.Truck> favs = data.getFavoriteTrucks();
trucks.Clear();
foreach (csharp.Truck truck in favs)
{
trucks.Add(truck);
}
}
Works but makes my ListView flicker.
Trying this now, its pretty bad code I think, it does update the list how I want it to but the listview does not get updated for some reason. Maybe I need to trigger a refresh?
List<csharp.Truck> all = data.getTrucks();
//if list sizes are not equal(excess/missing objects)
if (all.Count != trucks.Count)
{
//excess object
if (all.Count < trucks.Count)
{
trucks.Clear();
foreach(csharp.Truck t in all)
{
trucks.Add(t);
}
}
//object missing
if(all.Count > trucks.Count)
{
foreach(csharp.Truck t in all)
{
if (!trucks.Contains(t))
{
trucks.Add(t);
}
}
}
}//objects are now the same
//test if object is present but outdated(favorite property)
foreach(csharp.Truck t in trucks)
{
if (t.isFavorite() != all[all.IndexOf(t)].isFavorite())
{
t.setFavorite(all[all.IndexOf(t)].isFavorite());
}
}
Also let me know if this approach is not good practise in the first place.
If you want to update only some properties, you could implement in your model class INotifyPropertyChanged interface.
public class Model : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaisePropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
Volatile.Read(ref PropertyChanged)?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And then if this property is binded to view, view will know, when you change it:
<Label Text="{Binding MyProperty}" />

ViewModel properties with multiple calls to PropertyChanged

Recently I've been learning C# and WPF for work. I'm trying to use MVVM on a project I'm working on, just to keep the code organized and learn how it works.
In MVVM, controls on the View bind to properties on the ViewModel, which implements INotifyPropertyChanged. Pretty often, when a certain property is updated, I'll want a bunch of other properties to get updated as a result.
For example, I have a ListBox with a TextBox above it. You can type in the TextBox, and it filters the stuff in the ListBox. But I also need to be able to clear the TextBox from code in certain cases. The code ends up looking like this:
private Collection<string> _listOfStuff;
public Collection<string> FilteredList
{
get
{
if (String.IsNullOrWhiteSpace(SearchText))
{
return _listOfStuff;
}
else
{
return new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
set
{
if (value != _listOfStuff)
{
_listOfStuff = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText"); // Tells the view to change the value of the TextBox
OnPropertyChanged("FilteredList"); // Tells the view to update the filtered list
}
}
}
As this project gets bigger, this is starting to feel sloppy. I have one setter with 6 calls to OnPropertyChanged and it's getting hard to keep track of stuff. Is there a better way to do this?
I tried out Assisticant on a project about a year ago. It figures out which of your properties need to raise notifications and also which are related. There is a good course for it on Pluralsight and the examples on the website are pretty good. If nothing else you could check out the source code to see how he did it.
Also some good suggestions from Change Notification in MVVM Hierarchies.
They mentioned:
Use an attribute -> e.g. [DependsUpon(nameof(Size))]
and
Josh Smith's PropertyObserver
Could put the raise property change calls in a method if you just need to raise the same notifications every time.
First you shouldn't do potentially expensive operations in a command, then you'll be able to remove the OnPropertyChanged("FilteredList"); from your SearchText.
So you should move that code from the getter and into it's own command and bind it from XAML (either as Command on a button or using Blends Interactivity Trigger to call it when the text fields value changes).
public ICommand SearchCommand { get; protected set; }
// Constructor
public MyViewModel()
{
// DelegateCommand.FromAsyncHandler is from Prism Framework, but you can use
// whatever your MVVM framework offers for async commands
SearchCommand = DelegateCommand.FromAsyncHandler(DoSearch);
}
public async Task DoSearch()
{
var result = await _listOfStuff.Where(x => x.Contains(SearchText)).ToListAsync();
FilteredList = new Collection<string>(result);
}
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get
{
return _searchText;
}
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
}
}
}
On a side note: You can also use OnPropertyChanged(nameof(FilteredList)); to have a refactor friendly version, when you rename your property all of your OnPropertyChanged calls will be updated to. Requires C# 6.0 though, but it's compatible with older .NET Frameworks (back to 2.0), but requires Visual Studio 2015 or later
For anyone searching for a good solution to this type of problem: Check out ReactiveUI.
It is a framework based on Reactive Extensions (Rx), with the idea that you model this type of dependencies between properties explicitly, without a jungle of RaisePropertyChanged(..).
Specifically check out the ObservableAsPropertyHelper (sometimes called OAPH).
You should only raise OnPropertyChanged in the setter of the property itself.
A cleaner implementation of your ViewModel can be:
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
FilteredList = new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
}
if you just don't wanna type only other option is to fire OnPropertyChanged for all properties which can be done by passing a null or string.Empty, although it will be sloppier code!
OnPropertyChanged(Null);
or
OnPropertyChanged(String.Empty);

OnPropertyChanged not working as expected with ObjectListView

Here is my model class, the column that I am interested in this question:
public class Cell : INotifyPropertyChanged
{
public string TestImageAspect
{
get { return testImageAspect; }
set
{
testImageAspect = value;
Console.WriteLine("OnPropertyChanged => testImageAspect");
this.OnPropertyChanged("OperationResult");
}
}
private string testImageAspect;
}
ImageList is prepared with required images. In the ObjectListView I set appropriate column's ImageAspectName to the property name:
Then on button click I run the following code to change the
Cell c = ...;
c.TestImageAspect = "success"; // the name exist in ImageList
After above code I see that OnPropertyChanged has been called, however UI is not updating, unless I hover to the row where it has to change, then I see new icon. I am not looking for dirty workaround, since I know few, but rather want to understand whether ObjectListView has to update UI itself. If yes, what am I doing wrong?
The ObjectListView property UseNotifyPropertyChanged has to be set true.
From the official documentation
If you set UseNotifyPropertyChanged, then ObjectListView will listen for changes on your model classes, and automatically update the rows when properties on the model classes changed. Obviously, your model objects have to implement INotifyPropertyChanged.
Could you post the XAML for the binding - that might help debug this. Also, it's bit confusing that your property is called TestImageAspect but you're passing "OperationResult" to OnPropertyChanged. I'm not sure if OnPropertyChanged would work either. The more usual way would be to do:-
public class Cell : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestImageAspect
{
get { return testImageAspect; }
set
{
testImageAspect = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestImageAspect"));
}
}
}
private string testImageAspect;
}

I can't binding DataGrid to ObservableCollection

I can't understand what's happening here. I have two public properties in my ViewModel:
public ObservableCollection<vw_ClientesFull> MyClients { get; set; }
private IEnumerable<vw_ClientesFull> _clients;
public IEnumerable<vw_ClientesFull> Clients
{
get
{
return _clients;
}
set
{
_clients= value;
OnPropertyChanged("Clients");
}
}
Then I have a method to refresh both of them:
private void RefreshClientes()
{
this.serviceClient.Clientes_ListarCompleted += (s, e) =>
{
Clients = e.Result;
MyClients = new ObservableCollection<vw_ClientesFull>(Clients);
};
this.serviceClient.Clientes_ListarAsync(_sRazonSocial, VendedorSel, TransporteSel, _nID, bInactivos);
}
Them i bind my dataGrid to show the information. If I do:
ItemsSource="{Binding Path=Clients}"
If works perfect, but if i do:
ItemsSource="{Binding Path=MyClients}"
Nothing is show! Why? Doesn't ObservableCollection fire onPropertyChange Automaticaly?
Thanks for the help!!!
UPDATE
So if i need to fire the OnPropertyChange manualy, why this work without it?
public ObservableCollection<Vendedores> Vendedores { get; set; }
private void CargarVendedores()
{
Vendedores = new ObservableCollection<Vendedores>(this.serviceClient.GetVendedores());
this.VendedorSel = this.Vendedores.FirstOrDefault();
}
If i bind a combobox like this:
ItemsSource="{Binding Path=Vendedores}"
Work without the OnPropertyChange! Why!
This problem is due to a misconception. ObservableCollection does not raise PropertyChanged, (which happens when the entire property is reassigned) when you replace it, but rather CollectionChanged (which is raised when items are added or removed). You still need to raise PropertyChanged if you plan to reassign the whole object.
Yes, ObservableCollection implements INotifyPropretyChanged. However, it isn't going to help you here :)
ObservableCollection is special because it implements INotifyCollectionChanged. In other words, it raises an event when items are added to or removed from the underlying collection. It also implements INotifyPropertyChanged, so anything bound to a property of the collection will get updated.
You are changing the variable itself though (setting to a new instance no less). This requires that the "instance" of the ObservableCollection property raise the event. In other words, you need:
private ObservableCollection<vw_ClientesFull> myClients;
public ObservableCollection<vw_ClientesFull> MyClients
{
get { return myClients; }
set
{
myClients = value;
OnPropertyChanged("MyClients");
}
In your update, the binding hasn't fired yet (you set in the constructor) so it gets the correct list. Subsequent changes to the list wouldn't work, however.
You must raise the PropertyChanged event when you set the value of MyClients, same as you've already done for Clients.

Binding data from the list depending on the property of another control

I need to implement next UI on WPF with next logic:
I have a ViewModel:
public class VersionVulnerListViewModel : INotifyPropertyChanged
{
private ObservableCollection<Vulner> _vulners;
public ObservableCollection<Vulner> Vulners
{
get
{
return _vulners;
}
private set
{
_vulners = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Vulners"));
}
}
}
Where Vulner have next implementation:
public class Vulner : PocoEntity
{
public Vulner()
{
}
// ... other properties
public IList<VulnerData> Data { get; set; }
}
and VulnerData have some properties.
I need next: dependency of selected item of some combobox i need to bind diferent Data items from my list into GridView. Its will be good if its possible to do this just with XAML edetings, without adding code-behiend logic. Or add code-behiend logic, but according MVVM pattern.
Example:
I know it's an old question, but it's not answered yet, so I'll give it a try (in hope it will help somebody)
You can bind the SelectedValue of the Combobox to a variable. In the set-Block of the variable, you can also change the content of your ObservableCollection. VoilĂ , the GridView changes depending on the ComboBox.

Categories