WPF ComboBox Binding : MVVM - wpf

I am trying to bind WPF combobox ItemsSource with a collection using MVVM design pattern. following is my code
XAML:
<ComboBox Height="30" Width="200" ItemsSource="{Binding PeopleList,Mode=TwoWay}"></ComboBox>
<TextBlock Height="Auto" Width="Auto" Text="{Binding SelectedPerson.ContactNo}"></TextBlock>
Code Behind :
public MainWindow()
{
InitializeComponent();
ViewModel vm = new ViewModel();
DataContext = vm;
}
Model Class :
class People : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
private string contactNo;
public string ContactNo
{
get { return contactNo; }
set
{
contactNo = value;
NotifyPropertyChanged("ContactNo");
}
}
private ObservableCollection<People> peopleList;
public ObservableCollection<People> PeopleList
{
get { return peopleList; }
set
{
peopleList = value;
NotifyPropertyChanged("PeopleList");
}
}
private People selectedPerson;
public People SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
NotifyPropertyChanged("SelectedPerson");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
ViewModel Class :
class ViewModel
{
ObservableCollection<People> PeopleList = null;
public ViewModel()
{
PeopleList = new ObservableCollection<People>();
People p1 = new People { Name = "Naresh", ContactNo = "9574733355" };
People p2 = new People { Name = "Ritesh", ContactNo = "9099028779" };
People p3 = new People { Name = "Saumya", ContactNo = "9904848779" };
PeopleList.Add(p1);
PeopleList.Add(p2);
PeopleList.Add(p3);
People People = new People();
People.PeopleList = PeopleList;
}
So, this is what I have done so far. Here, the issue I am facing is when I click on combo box nothing is happening.
Thanks for your help in advance.

The PeopleList needs to be real property (not a local field):
public ObservableCollection<People> PeopleList { get; set; }

You have to replace the field entry
ObservableCollection<People> PeopleList = null;
by a property entry
public ObservableCollection<People> PeopleList { get; set; }
Only properties can be binded not fields.
Furthermore you should set the DisplayMemberPath to show more than the type name "People". The three items cannot be distinguished. You could show the names of the people
<ComboBox Height="30" Width="200" ItemsSource="{Binding PeopleList,Mode=TwoWay}" DisplayMemberPath="Name"></ComboBox>

Related

How should I update a textbox value in a WPF app?

I'm troubling to resolve how a textbox should automatically change its value when a object is updated. I have a Temperature class with Name and Value.
public class Temperature
{
public string Name { get; set; }
public double Value { get; set; }
}
In MainWindow.xaml.cs after InitializeComponent(), I create an
ObservableCollection _lstTempObs = new ObservableCollection(); and then I add an initial temperature object.
In the main window I have a textbox
<TextBox x:Name="T1" Text="{Binding Path=Value}" HorizontalAlignment="Left" Height="23" Margin="215,55,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
And in MainWindow.xaml.cs I set the datacontext as
T1.DataContext = _lstTempObs.LastOrDefault();
The temperature update comes from an API. When I get the mew Temperature object, I add it in the ObservableCollection, but the value isn't change in the GUI.
public partial class MainWindow : Window
{
ObservableCollection<Temperature> _lstTempObs = new ObservableCollection<Temperature>();
public MainWindow()
{
InitializeComponent();
_lstTempObs.Add(new Temperature { Name = "T1", Value = "0.321" });
T1.DataContext = _lstTempObs.LastOrDefault();
}
}
you need to implement a notification-mechanism that informs the UI of changes to the data.
In WPF this is usually done using INotifyPropertyChanged-interface.
Check this answer for a basic reference on how to do that.
In a actual scenario I would implement the INPC-interface in a base class
public class INPCBase : INotifyPropertyChanged
{
public bool SetField<U>(ref U field, U value, [CallerMemberName] string propertyName = null)
{
if (field == value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Usage:
public class Temperature : INPCBase
{
private string name;
public string Name { get; set{base.SetField(ref name, value); } }
private double val;
public double Value { get; set{base.SetField(ref val, value); } }
}

WPF DataGrid : how to bind an object to reflect the item whose row is checked

I have a datagrid populated with elements and a checkbox for each element.
I'm looking for a way to have an object in my ViewModel be whichever element currently has its checkbox checked.
Here is my XAML so far :
<Window x:Class="fun_with_DataGridCheckBoxColumns.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:fun_with_DataGridCheckBoxColumns"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Label Content="Chosen One : " />
<Label Content="{Binding ChosenOne.Name, Mode=OneWay}" />
</StackPanel>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=OneWay}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="Is Chosen"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
and my CS :
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace fun_with_DataGridCheckBoxColumns
{
public partial class MainWindow : Window
{
public Person ChosenOne { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = new Viewmodel();
}
}
public class Viewmodel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; private set; }
private Person _chosenOne = null;
public Person ChosenOne
{
get
{
if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; }
else return _chosenOne;
}
set
{
_chosenOne = value;
NotifyPropertyChanged("ChosenOne");
}
}
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Person
{
private static int person_quantity = 0;
private int _id = ++person_quantity;
public int ID { get { return _id; } }
public string Name { get; set; }
}
}
Here is the behavior I am looking for :
ChosenOne in ViewModel becomes whichever Person has its checkbox checked
When a checkbox is checked, all others are unchecked
If no checkboxes are checked, sets ChosenOne to null
Basically it is the same behavior as if I had put this in the DataGrid (XAML) :
SelectedItem="{Binding ChosenOne, Mode=TwoWay}"
But in my case ChosenOne can not be the SelectedItem of the datagrid since I need SelectedItem for something else, and I have to use checkboxes for Company reasons.
I have not found how to simulate this "SelectedItem" logic with checkboxes.
I know I could put a "bool IsChosen" property in my Person class and bind the checkbox to it, but I would really rather avoid this. It will be my solution if all else fails.
Thank you.
An alternative would be to wrap your object with something that supports the checking.
Source
using System.ComponentModel;
namespace Jarloo
{
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{}
public CheckedListItem(T item, bool isChecked=false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
}
Add an IsChecked property to the Person class and implement the INotifyPropertyChanged interface:
public class Person : INotifyPropertyChanged
{
private static int person_quantity = 0;
private int _id = ++person_quantity;
public int ID { get { return _id; } }
public string Name { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; NotifyPropertyChanged("IsChecked"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Bind the DataGridCheckBoxColumn to this property:
<DataGridCheckBoxColumn Header="Is Chosen" Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
You can then handle the logic in your view model class. This should make sure that only a single Person is selected at a time:
public class Viewmodel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; private set; }
private Person _chosenOne = null;
public Person ChosenOne
{
get
{
if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; }
else return _chosenOne;
}
set
{
_chosenOne = value;
NotifyPropertyChanged("ChosenOne");
}
}
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
foreach(Person p in People)
p.PropertyChanged += P_PropertyChanged;
}
private bool handle = true;
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(handle && e.PropertyName == "IsChecked")
{
handle = false;
//uncheck all other persons
foreach (Person p in People)
if(p != sender)
p.IsChecked = false;
ChosenOne = sender as Person;
handle = true;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If you plan to add new Person objects to the People collection dynamically at runtime you should also make sure that you handle the PropertyChanged event for these as well:
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
foreach (Person p in People)
p.PropertyChanged += P_PropertyChanged;
People.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
{
foreach (object person in e.NewItems)
{
(person as INotifyPropertyChanged).PropertyChanged
+= new PropertyChangedEventHandler(P_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (object person in e.OldItems)
{
(person as INotifyPropertyChanged).PropertyChanged
-= new PropertyChangedEventHandler(P_PropertyChanged);
}
}
};
People.Add(new Person() { Name = "New..." });
}

WPF - How to get selected value binded to a table model

I have a combo box category that is binded to an ObservableCollection Categories based on tbl_Category with two properties CategoryName and CategoryDescription. Now I want to add the SelectedValue of the ComboBox to product table property Prod_Category
In my constructor :
cb.DataContext = Categories;
this.DataContext = new tbl_Product();
Combo box xaml :
<Combobox x:Name="cb" ItemSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedValue="{Binding Prod_Category,Mode=TwoWay}"/>
In my save product event :
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
I get Prod_Category to null even after doing all this.
You should use SelectedItem instead of SelectedValue, refer to this.
besides that what you are willing to do wasn't so clear, I've tried to implement what you asked for based on my understanding
public partial class MainWindow : Window,INotifyPropertyChanged
{
private ObservableCollection<Category> _categories;
public ObservableCollection<Category> Categories
{
get
{
return _categories;
}
set
{
if (_categories == value)
{
return;
}
_categories = value;
OnPropertyChanged();
}
}
private Category _prod_Category ;
public Category Prod_Category
{
get
{
return _prod_Category;
}
set
{
if (_prod_Category == value)
{
return;
}
_prod_Category = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Categories=new ObservableCollection<Category>()
{
new Category()
{
CategoryName = "Name1",
CategoryDescription = "Desc1"
},new Category()
{
CategoryName = "Name2",
CategoryDescription = "Desc2"
}
};
}
public void SaveButton_Click(object sender, RoutedEventArgs routedEventArgs)
{
if (Prod_Category!=null)
{
//add it to whatever you want
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Category
{
public String CategoryName { get; set; }
public String CategoryDescription { get; set; }
}
and the xaml
<StackPanel>
<ComboBox x:Name="cb" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedItem="{Binding Prod_Category,Mode=TwoWay}"/>
<Button Content="Save" Click="SaveButton_Click"></Button>
</StackPanel>
you should consider implementing the INotifyPropertyChanged interface to notify the UI if any changes occurs in one of the binded properties
In addition to #samthedev 's provided answer, I would also recommend you change your assignment of DataContext from:
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
to
tbl_Product prod = this.DataContext as tbl_Product;
if (tbl_Product != null)
{
DataOperations.AddProduct(prod);
}
This prevents any change of DataContext being accidentally bound to a different object and causing an unhandled exception due to the fact that DataContext does have tbl_Product as its base-type which can happen more often than you realise in WPF due to DataContext inheritance when someone changes something at a higher level.

How to implement dependency object/property on custom class?

Here is my Person Class:
public class Person
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}//close class
Here is my XAML:
<StackPanel>
<TextBox x:Name="txtLastName"
Height="50" Width="300"
DataContext="{Binding ElementName=_this, Path=PersonObject}"
Text="{Binding Path=LastName}" />
<Button Height="50" Width="100" x:Name="btnChangeValue" Content="Change Value" Click="btnChangeValue_Click"/>
</StackPanel>
Here is my XAML.CS
public partial class ClassDependency : Window
{
public Person objPerson = new Person();
public ClassDependency()
{
objPerson.LastName = "testing...";
InitializeComponent();
}
public Person PersonObject
{
get { return objPerson; }
set { objPerson = value; }
}
private void btnChangeValue_Click(object sender, RoutedEventArgs e)
{
objPerson.LastName = "New value after click....";
}
}//close class
My question is: After clicking "btnChangeValue" it does changing Last Name in my code behind but it is not reflection my textbox "txtLastName". How can I fix this??? Should I implement Dependency Property in my xaml.cs file?? I tried that too but no use.
public static readonly DependencyProperty PersonObjectProperty = DependencyProperty.Register("PersonObject", typeof(object), typeof(ClassDependency));
public Person PersonObject
{
get { return (Person)GetValue(PersonObjectProperty); }
set { SetValue(PersonObjectProperty, value); }
}
What should I do?? Please advice..
Try this:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("LastName"));
}
}
}
}
This way, the framework gets notified when the property changes. See INotifyPropertyChanged Interface.
The problem with your code is that you are not raising the PropertyChanged event, so the UI is not aware of the value change, on the setter of your dependency properties raise the PropertyChanged event as shown below:
public class Person : INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("LastName"));
}
}
}
}

Need a simple Example of cascading combo boxes using MVVM

Need a simple Example of cascading combo boxes using MVVM
Wpf / Silverlight
If I understand your question you want to have the next combobox to fill with data based on the previous value.
I have a generic ViewModel that you can have to capture the list of items and the selected item
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And in your view have this code somewhere.
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
You can refactor to make it nicer, use MVVM frameworks or derive the ItemListViewModel specifically for the list of items and have the load in there for better encapsulation. Its up to you.
If any parent combobox value gets changed then all child lists will get cleared.
HTH

Resources