WPF binding CombobBoxItem base on property - wpf

My View Model class:
class Student : INotifyPropertyChanged
{
private string name;
private bool isVisible;
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
OnPropertyChanged("PersonName");
}
}
public bool IsVisible
{
get { return isVisible; }
set
{
isVisible = value;
OnPropertyChanged("IsVisible");
}
}
}
My Students collection that store all my objects:
public ObservableCollection<Student> Students { get; set; }
XAML:
<ComboBox x:Name="cbStudents"
ItemsSource="{Binding Students}"
SelectionChanged="cbInterfaces_SelectionChanged"/>
So in some point i want to disappear several Students from my ComboBox so i just change IsVisible value to False.
Any idea how to do that using XAML ?

You can have your Students collection return only visible students.
//All students (visible and invisible)
ObservableCollection<Students> _AllStudents = GetAllStudentsFromDataSource();
//only visible students
ObservableCollection<Students> _VisibleStudents = new ObservableCollection<Students>();
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
//your property
public ObservableCollection<Student> Students { get{ return _VisibleStudents; } }
In the case of your check box toggling the visibility of students, your checkbox can be bound to a command like this:
<Checkbox IsChecked="{Binding IsCheckboxChecked}" Command={Binding ToggleStudents}" />
And your view model has an extra control for the checkbox toggle and the command:
bool _IsCheckboxChecked = false;
public bool IsCheckboxChecked {
get { return _IsCheckboxChecked;}
set {
if(_IsCheckboxChecked != value)
{
_IsCheckboxChecked = value;
}
}
}
public ICommand ToggleStudents
{
get;
internal set;
}
private void ToggleStudentsCommand()
{
ToggleStudents = new RelayCommand(ToggleStudentsExecute);
}
public void ToggleStudentsExecute()
{
_VisibleStudents.Clear();
if(_IsCheckboxChecked){
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
}
else
{
foreach(var _s in _AllStudents.Where(x => x.IsVisible == false)){
_VisibleStudents.Add(_s);
}
}
OnPropertyChanged("Students");
}
Your xaml doesn't need to change.

Related

MVVM Datagrid Binding SelectedItem not updating

I'm new to WPF and MVVM and i've an applicaton that uses Entity Framework to connect to database and a datagrid to show the users of the application.
The users CRUD operations are made in a separate window and not in the datagrid.
My problems are related with the update of datagrid.
The insert operation is ok but the update is not.
View 1 (Users List):
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users, Mode=TwoWay}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False">
</DataGrid>
ViewModel :
class UserListViewModel: NotificationClass
{
UserDBContext _db = null;
public UserListViewModel()
{
_db = new UserDBContext();
Users = new ObservableCollection<User>(_db.User.ToList());
SelectedUser = Users.FirstOrDefault();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
OnProprtyChanged();
}
}
private User _selectedUser;
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnProprtyChanged();
}
}
public RelayCommand Edit
{
get
{
return new RelayCommand(EditUser, true);
}
}
private void EditUser()
{
try
{
UserView view = new UserView();
view.DataContext = SelectedUser;
view.ShowDialog();
if (view.DialogResult.HasValue && view.DialogResult.Value)
{
if (SelectedUser.Id > 0){
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
updatedUser.Username = SelectedUser.Username; //this doesn't do nothing, object is already with the new username ?!
}
_db.SaveChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
after _db.SaveChanges(), datagrid should not be updated ?
Model:
class UserDBContext: DbContext
{
public UserDBContext() : base("name=DefaultConnection")
{
}
public DbSet<User> User { get; set; }
}
View 2 (User detail)
public partial class UserView : Window
{
public UserView()
{
InitializeComponent();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
User object
class User: NotificationClass
{
public int Id { get; set; }
public string Username { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}
NotificationClass
public class NotificationClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnProprtyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
if i close and open view 1, the new username is updated..
could someone help ? thanks
Just implementing INotifyPropertyChanged isn't enough, you have to explicitly invoke PropertyChanged (or in your case OnPropertyChanged) when a property changed.
See also https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
You can do it like so
class User : NotificationClass
{
private int _id;
private string _username;
private string _createdBy;
private DateTime? _createdOn;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Username
{
get => _username;
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
}
}
public string CreatedBy
{
get => _createdBy;
set
{
if (value == _createdBy) return;
_createdBy = value;
OnPropertyChanged();
}
}
public DateTime? CreatedOn
{
get => _createdOn;
set
{
if (value.Equals(_createdOn)) return;
_createdOn = value;
OnPropertyChanged();
}
}
}
it worked ! many thanks #nosale !
what about the change made to SelectedUser being reflected in my context ?
if i do this :
SelectedUser.Username = "test";
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
i was thinking that SelectedUser object has the "test" username and updatedUser has the old username, but not .. updatedUser already have "test"

How to add sublist in wpf using mvvm?

namespace colourchanges
{
public class Group
{
public string Name { get; set; }
//its a class for adding parent list using group class
}
public class EmployeeTree : INotifyPropertyChanged
{
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
GroupStaff.Add(new Group { Name = "Designers" });
GroupStaff.Add(new Group { Name = "Developers" });
GroupStaff.Add(new Group { Name = "Managers" });
//here we are declaring list for adding parent list
}
private List<Group> _GroupStaff;
public List<Group> GroupStaff
{
get { return _GroupStaff; }
set
{
_GroupStaff = value;
RaisePropertyChanged("GroupStaff");
}
}
//creates a list for parentlist
private Group _selectedGroupStaff;
public Group selectedGroupStaff
{
get { return _selectedGroupStaff; }
set
{
_selectedGroupStaff = value;
if (selectedGroupStaff.Name == "Designers")
{
City = "Chennai";
Country = "India";
Email = "Designer#gmail.com";
MobileNo = 9094117917;
Address = "Annanagar";
}
else if (selectedGroupStaff.Name == "Developers")
{
City = "Trichy";
Country = "India";
Email = "Developer#gmail.com";
MobileNo = 9094667878;
Address = "Koyambedu";
}
else if (selectedGroupStaff.Name == "Managers")
{
City = "Salem";
Country = "India";
Email = "Manager#gmail.com";
MobileNo = 9094154678;
Address = "Arumbakkam";
}
RaisePropertyChanged("selectedGroupStaff");
}
}//for selecting parent list in order to bind to textbox
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
//properties of parent list to bind to textbox
public string City
{
get { return _City; }
set
{
_City = value;
RaisePropertyChanged("City");
}
}
public string Country
{
get { return _Country; }
set
{
_Country = value;
RaisePropertyChanged("Country");
}
}
public string Email
{
get { return _Email; }
set
{
_Email = value;
RaisePropertyChanged("Email");
}
}
public long MobileNo
{
get { return _MobileNo; }
set
{
_MobileNo = value;
RaisePropertyChanged("MobileNo");
}
}
public string Address
{
get { return _Address; }
set
{
_Address = value;
RaisePropertyChanged("Address");
}
}
///raise property changed event handler code
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
//how to add sub list for designers developers and managers in the constructor
Let the following be your Model class
public class Group
{
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
public Group()
{
Items = new ObservableCollection<Group>();
}
public ObservableCollection<Group> Items { get; set; }
}
and at the ViewModel's constructor, you can add the Items.
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
Group rootGroup = new Group(){Name ="Manager"};
Group childGroup = new Group(){Name = "Developer"};
rootGroup.Items.Add(childGroup);
this.GroupStaff.Add(rootGroup);
}
This is for Hierarchical structure. Hope you are looking for this.
And your XAML should be like this
<TreeView Name="GroupTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>

DisplayMemberPath databinding

I have a puzzled problem of databinding in WPF.
There is a listbox in XAML which it has linked with ItemSource,
but when it runs, it shows the lists of class names.
so I have applied to DisplayMemberPath, but it doesn't helpful.
and also I'm wondering how I can access inside class from generic class.
Thanks.
result
puzzled.Member
puzzled.Member
puzzled.Member
puzzled.Member
<DockPanel>
<ListBox Name="lbxMbrList" DockPanel.Dock="Left" Width="200" Padding="10"></ListBox>
<ContentControl />
</DockPanel>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
members.Add(new Member("superman", "123-1234567", "address1"));
members.Add(new Member("batman", "111-111111", "address2"));
members.Add(new Member("goodman", "222-222222", "address3"));
members.Add(new Member("badman", "333-333333", "address4"));
lbxMbrList.ItemsSource = members;
lbxMbrList.DisplayMemberPath = members.MemberDetails; //<<it won't helpful
//var i = members.member.Name; //<<how can I access inside class?
//if (i == "superman")
//{
// MessageBox.Show("superman");
//}
}
public class Member
{
private string _name;
private string _phone;
private string _address;
public string Name { get { return _name; } set { _name = value; } }
public string Phone { get { return _phone; } set { _phone = value; } }
public string Address { get { return _address; } set { _address = value; } }
public Member() { }
public Member(string name, string phone, string address)
{
_name = name; _phone = phone; _address = address;
}
public string lbxMember
{
get { return string.Format("{0} - {1}", Name, Phone, Address); }
}
}
class MemberList : IEnumerable<Member>
{
private ObservableCollection<Member> memberList = new ObservableCollection<Member>();
public Member this[int i]
{
get {return memberList[i];}
set {memberList[i] = value;}
}
public void Add(Member member)
{
memberList.Add(member);
}
public void Remove(Member member)
{
memberList.Remove(member);
}
public IEnumerator<Member> GetEnumerator()
{
return memberList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public Member member { get; set; } //<< it think I has misunderstood it
public string MemberDetails
{
get
{ return string.Format("{0} - {1}", member.Name, member.Phone, member.Address); }
}
}
You are assigning the output of your MemberDetails property to the DisplayMemberPath. Instead, you need to assign the name of the property as a string.
lbxMbrList.DisplayMemberPath = "MemberDetails";
For what its worth, this will be easier to work with if you use an ItemTemplate in the ListBox.
[Edit]
Also, as #Blam mentions in his answer, your MemberDetails property is defined in the wrong class, it needs be in the Member class.
lbxMbrList.DisplayMemberPath = "lbxMember";
or
lbxMbrList.DisplayMemberPath = "MemberDetails";
And MemberDetails need to be a property of Member (not MemberList)

dynamic binding wpf

Hi all this is my first question :)
This exemple tested on winform application and wpf application and the problem with binding on WPF
winform all works fine with ICustomTypeDescriptor and grid draw only columns added to Dictionary Properties (Name Age) and Male excluded
WPF all properties of the class person drawed on grid (Name Age Male)
any idea about this situation or interfaces equivalent of ICustomTypeDescriptor in wpf ?
<Grid>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="90,30,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="325" />
</Grid>
List<Person> persons = new List<Person>();
persons.Add(new Person("Aymane", 30));
persons.Add(new Person("Raouia", 30));
grid.ItemsSource = persons; //wpf
grid.DataSource = persons; //winform
public class Person : ICustomTypeDescriptor
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
public Person()
{
Properties.Add("Name", null);
Properties.Add("Age", null);
}
public Person(string name, object value)
: base()
{
Male = true;
Name = name;
Age = value;
}
public bool Male { get; set; }
public object Age { get { return Properties["Age"]; } set { Properties["Age"] = value; } }
public object Name { get { return Properties["Name"]; } set { Properties["Name"] = value; } }
#region ICustomTypeDescriptor Members
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(attributes, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return ((ICustomTypeDescriptor)this).GetEvents(null);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
props.Add(new PersonPropertyDescriptor("Name", attributes));
props.Add(new PersonPropertyDescriptor("Age", attributes));
return new PropertyDescriptorCollection(props.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
class PersonPropertyDescriptor : PropertyDescriptor
{
public PersonPropertyDescriptor(string name, Attribute[] attrs)
: base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return typeof(Person); }
}
public override object GetValue(object component)
{
return ((Person)component).Properties[Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(object); }
}
public override void ResetValue(object component)
{
((Person)component).Properties[Name] = null;
}
public override void SetValue(object component, object value)
{
((Person)component).Properties[Name] = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
}
To gain control over the column generation handle the AutoGeneratingColumn event, have you can suppress the generation of a column by seting e.Cancel = true;
In your case:
private void DataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid != null)
{
ICustomTypeDescriptor typeDescriptor =
dataGrid.Items[0] as ICustomTypeDescriptor;
if (typeDescriptor != null)
{
var props = typeDescriptor.GetProperties();
if (!props.Contains((PropertyDescriptor)e.PropertyDescriptor))
{
e.Cancel = true;
}
}
}
}
With the DataGrid definition of:
<DataGrid
AutoGenerateColumns="True"
Height="311"
HorizontalAlignment="Left"
Name="dataGrid1"
VerticalAlignment="Top"
Width="509"
AutoGeneratingColumn="DataGridAutoGeneratingColumn">
Gives the desired result.
Here the correct implementation of ICustomTypeDescriptor & ITypedList
namespace CustomTypeDescriptor
{
class Row : ICustomTypeDescriptor { }
class RowsCollection : List<Row>, ITypedList { }
class Table : IListSource, IEnumerable<Row>, IEnumerator<Row>
{
RowsCollection Rows { get; set; }
}
}

WPF ComboBox Binding

So I have the following model:
public class Person
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String Address { get; set; }
public String EMail { get; set; }
public String Phone { get; set; }
}
public class Order
{
public Person Pers { get; set;}
public Product Prod { get; set; }
public List<Person> AllPersons { get; set; }
public Order(Person person, Product prod )
{
this.Pers = person;
this.Prod = prod;
AllPersons = database.Persons.GetAll();
}
}
And I have a WPF window used to edit an order.
I set the DataContext to Order.
public SetDisplay(Order ord)
{
DataContext = ord;
}
I have the following XAML:
<ComboBox Name="myComboBox"
SelectedItem = "{Binding Path=Pers, Mode=TwoWay}"
ItemsSource = "{Binding Path=AllPersons, Mode=OneWay}"
DisplayMemberPath = "FirstName"
IsEditable="False" />
<Label Name="lblPersonName" Content = "{Binding Path=Pers.FirstName}" />
<Label Name="lblPersonLastName" Content = "{Binding Path=Pers.LastName}" />
<Label Name="lblPersonEMail" Content = "{Binding Path=Pers.EMail}" />
<Label Name="lblPersonAddress" Content = "{Binding Path=Pers.Address}" />
However, the binding does not seem to work.......When I change the selected item , the labels do not update ....
Regards!!
Any reply is appreciated !!
Your model will need to fire change notifications. See INotifyPropertyChanged and INotifyCollectionChanged.
For INotifyPropertyChanged, you could use a base ViewModel class such as this one. For collections, ObservableCollection<T> does the hard work for you. However, in your case your collection won't change after the UI is bound to it, so you shouldn't need an observable collection. Regardless, I'd generally recommend using observable collections in your view model layer to save head-scratching should the code ever change.
An example of what this would look like is:
public class Person : ViewModel
{
private string firstName;
private string lastName;
private string email;
private string phone;
public string FirstName
{
get
{
return this.firstName;
}
set
{
if (this.firstName != value)
{
this.firstName = value;
OnPropertyChanged(() => this.FirstName);
}
}
}
public string LastName
{
get
{
return this.lastName;
}
set
{
if (this.lastName != value)
{
this.lastName = value;
OnPropertyChanged(() => this.LastName);
}
}
}
// and so on for other properties
}
public class Order : ViewModel
{
private readonly ICollection<Person> allPersons;
private Person pers;
private Product prod;
public Person Pers
{
get
{
return this.pers;
}
set
{
if (this.pers != value)
{
this.pers = value;
OnPropertyChanged(() => this.Pers);
}
}
}
public Product Prod
{
get
{
return this.prod;
}
set
{
if (this.prod != value)
{
this.prod = value;
OnPropertyChanged(() => this.Prod);
}
}
}
// no need for setter
public ICollection<Person> AllPersons
{
get
{
return this.allPersons;
}
}
public Order(Person person, Product prod )
{
this.Pers = person;
this.Prod = prod;
// no need for INotifyCollectionChanged because the collection won't change after the UI is bound to it
this.allPersons = database.Persons.GetAll();
}
}

Resources