Observable collection item property changed - c#

I have a column class which uses view model base to implement INotifyPropertyChanged (lazy I know):
public class Column : ViewModelBase
{
public string ColumnName { get; set; }
public bool Anonymize { get; set; }
}
And then a list of columns:
public class Columns : ObservableCollection<Column>
{
}
In my view model I have a property columns and I am binding that to a combo box with a checkbox and textblock:
private Columns _tableColumns;
public Columns TableColumns
{
get
{
return _tableColumns;
}
set
{
_tableColumns = value;
OnPropertyChanged("TableColumns");
}
}
<ComboBox Name="cbColumns" ItemsSource="{Binding TableColumns}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Anonymize, Mode=TwoWay}" />
<TextBlock Text="{Binding ColumnName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I change the Anonymize property through the checkbox on an item, how do make the Columns property change in the view model to reflect this?

Your Column class needs to implement INotifyPropertyChanged (which you say it does). You also need to raise that event it when the value of Anonymize changes (which you don't).

If you want to change the Anonymize property only from the UI, you are done. If you'd like to see the changes(from the backend) on the UI, you have to implement the INotifyPropertyChanged interface in the Column class.
public class Column : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string columnName;
public bool anonymize;
public string ColumnName
{
get { return columnName; }
set
{
columnName = value; RaiseOnPropertyChanged("ColumnName");
}
}
public bool Anonymize
{
get { return anonymize; }
set { anonymize = value; RaiseOnPropertyChanged("Anonymize"); }
}
public void RaiseOnPropertyChanged(string propertyName)
{
var eh = PropertyChanged;
if (eh != null)
eh(this, new PropertyChangedEventArgs(propertyName));
}
}

When the Anonymize state changes it will need to notify the view model that it needs to modify the collection of columns. The way I've solved this before is to add a CheckChanged event to the Column class that is raised when Anonymize. The the view model subscribes to the event after it creates the Column object but it is added to the Columns collection. When CheckChanged is raised the view model adds/removes the item from the Columns collection.

Related

C# BindingList<> not updating a WPF Listbox on changed items

I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.

How to get particular property from item selected in ListBox

I have the following:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
<TextBlock Text="{Binding SelectedItem.s}"/>
This is definition of SelectedItem
public MemEntity SelectedItem {get; set;}
MemEntity is a class containing
public String s {get; get;}.
Basically, I want s of the selected item to be shown in the TextBlock (same property as shown in ListBox). This doesn't work, so what am I doing wrong?
Try this,
<TextBlock ... Text="{Binding ElementName=items, Path=SelectedItem.s}" />
then add a name to your ListBox as,
<ListBox x:Name="items" SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
There are multiple way to do this. One option has already been provided in another answer that focusing on achieving the desired functionality by binding to a view element. Here is another option.
The view is unaware that selected item has changed. look into using INotifyPropertyChanged
You can create a base ViewModel to encapsulate the repeated functionality
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Have the view models inherit from this base class in order for the view to be aware of changes when binding.
public class ItemsViewModel : ViewModelBase {
public ItemsViewModel() {
items = new ObservableCollection<MemEntity>();
}
private MemEntity selectedItem;
public MemEntity SelectedItem {
get { return selectedItem; }
set {
if (selectedItem != value) {
selectedItem = value;
OnPropertyChanged(); //this will raise the property changed event.
}
}
}
public ObservableCollection<MemEntity> items { get; set; }
}
The view will now be aware when ever the SelectedItem property changes and will update the view accordingly.

string bound to TextBlock in ComboBox doesn't show up

I have a TextBlock in a ComboBox in a C# WPF project bound to a list of 'Envelope' items, which have a string 'Name' and a double 'Weight' property, the former of which I would like to see displayed in the TextBlock.
When I run my program, the ComboBox appears without any text in it. It properly has three unlabeled items in it, and if I view the ItemsSource or SelectedItem of the ComboBox they show the appropriate values, and other code which interacts with the SelectedItem of the ComboBox behaves properly. The only thing that does not work is that the TextBlock contains no text. If I replace the "{Binding Name}" with "au ghdfjlnvgmumar" then the appropriate garbled characters appear in the ComboBox, so it is definitely a problem with the binding. What is the problem, and how can I get around it?
Relevant code:
xaml:
<ComboBox Name="EnvelopeList" HorizontalAlignment="Center" Width="200" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#:
//main window code
public MainWindow()
{
InitializeComponent();
envelopes = new List<Envelope>();
envelopes.Add(new Envelope("TEST", 0));
envelopes.Add(new Envelope("HI", 10));
EnvelopeList.ItemsSource = envelopes;
}
//Envelope class
class Envelope
{
public string Name;
public double Weight;
public Envelope()
{
Name = "[None]";
Weight = 0;
}
public Envelope(string n, double w)
{
Name = n;
Weight = w;
}
public override string ToString()
{
return Name;
}
}
When DataBinding, you can only bind to Properties. Also, you need to update your properties with a PropertyChangedEvent. Otherwise, if you change your property after the initial binding it won't update the UI.
You need to use on property changed and a property
public class Envelope: ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name= value; OnPropertyChanged("Name"); }
}
}
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Finally, I notice that you're setting ItemsSource directly. Instead, you want to set your View's DataContext property and then bind to your ItemsSource
Here is a MSDN article on DataBinding that will teach you how to do it properly.
Name is a field, you can only bind to properties.

Set foreground text color of checkbox items in a wpf listbox

I have several lists containing data from a db. The listboxes serves as filters for a chart, and the appearance of the listboxes should change depending on what is selected in other listboxes.
Here is a simplified example of what I'm trying to do specifically:
Class Region
{
public int RegionID { get; set; }
public string RegionName { get; set; }
}
Class Country
{
public int CountryID { get; set; }
public string CountryName { get; set; }
public int RegionID { get; set; }
}
private void fillListBoxes()
{
List<Region> allRegions = getRegions();
lstRegionsFilter.ItemsSource = allRegions;
}
A country obviously belongs to a region, and I also have for example Ports, which is then located in a country etc etc.
All listbox items are checkboxes defined like this:
<ListBox Name="lstRegionsFilter">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=RegionName}"
Tag="{Binding Path=RegionID}"
Click="CheckBox_Click"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As items in any listbox is clicked they are added to a list of filters which will filter the data displaying the chart. So for example if "Europe" is selected under Regions then all Countries that belongs to Europe should be colored differently in the listbox for countries, for example blue.
So in code I want to loop through the check boxes in the country-listbox and set its foreground color to something depending on if the value displayed/tagged to that checkbox is a country that belongs to the selected Region so typically in a foreach loop. However the items in the listbox is of type Region so how can I access the underlying checkbox? This should be pretty basic stuff I know, but its driving me nuts!
Try this:
public class Region : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private void OnPropertyChaned(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public int RegionID { get; set; }
public string RegionName { get; set; }
public bool IsChecked
{
get { return isChecked; }
set
{
if (isChecked != value)
{
isChecked = value;
OnPropertyChaned("IsChecked");
}
}
}
}
public class Country : INotifyPropertyChanged
{
private readonly Region parentRegion;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public Country(Region parent)
{
parentRegion = parent;
parentRegion.PropertyChanged += ParentChanged;
}
private void ParentChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName.Equals("IsChecked"))
{
OnPropertyChanged("IsParentChecked");
}
}
public int CountryID { get; set; }
public string CountryName { get; set; }
public int RegionID { get { return parentRegion.RegionID; }}
public bool IsParentChecked
{
get { return parentRegion.IsChecked; }
}
}
and the xaml:
This is for the Regions:
<ListBox Name="lstRegionsFilter">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=RegionName}"
Tag="{Binding Path=RegionID}"
IsChecked="{Binding Path=IsChecked}"
Click="CheckBox_Click"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this is for the Countries
<ListBox Name="lstCountriesFilter">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox ...
Foreground={Binding IsParentChecked, Converter={StaticResource boolToBrushConverter}"/>
...
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note:
You need to implement the Converter class (see here)
Add the converter to the xaml as StaticResouce
The Country Checkboxes forecolor will change automatically if the regions IsChecked property changed (from the ui or from code-behind), so no loop needed.
I haven't tried it, probably you can find mistakes but I wanted to show the "phylosophy"
Hope it helps
If i get you right, your plan is to have a country highlighted in its region coulor if its region is selected ?
Rq 1 : You don't have to loop through checkBoxes to know which region is selected : this is the purpose of the binding that the values of the binded collection themselves will be affected.
Rq2 : If you change the values in code, you have to implement INotifyPropertyChanged in your region class for the U.I. to be updated.
How to do it ?
1. have a static dictionnary of RegionId --> Boolean.
2. Whenever a RegionId gets selected/unselected, update that dictionnary and raise a static event 'SelectionDictionnaryUpdated'.
3. Now add a notifying property 'IsOwnerRegionSelected' in your Country class. it will look in the dictionnary to say if corresponding region is selected or not.
4. add property 'CountryRegionColor' in your Country class. It will return the color based on RegionId and, say a RegionId -> Color static dictionnary.
5. add a Trigger, inside your DataTemplate, and a DataTrigger that will switch color beetween white and CountryRegionColor, depending on IsOwnerRegionSelected value.
6. In the constructor of a country, add a handler to SelectionDictionnaryUpdated that will NotifyPropertyChanged on the "IsOwnerRegionSelected".
that's it !
you can make it with non-shared dictionnary (each country having a RegionId --> Boolean dictionnary property that you inject in constructor)
you can make things simpler by having 'CurrentCoulor': notifying property that will return either white or CountryRegionColor depending if it is selected or not. just bind the Background of your list to that property, and notifyCurrentCoulor changed when you catch SelectionDictionnaryUpdated.

WPF - Auto refresh combobox content

I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.

Categories

Resources