Bind ListView to ObservableCollection and Linq - c#

Hi Im trying to bind a ListView to a Linq, and make it react on changes in the collection.
public partial class MainWindow
{
readonly ObservableCollection<Person> _persons = new ObservableCollection<Person>();
readonly Team _team1 = new Team { Name = "Team 1" };
readonly Team _team2 = new Team { Name = "Team 2" };
readonly Team _team3 = new Team { Name = "Team 3" };
public MainWindow()
{
InitializeComponent();
_persons.Add(new Person {Name = "James", Team = _team1});
_persons.Add(new Person {Name = "John", Team = _team2});
_persons.Add(new Person {Name = "Peter", Team = _team3});
_persons.Add(new Person {Name = "Jack", Team = _team1});
_persons.Add(new Person {Name = "Jones", Team = _team2});
_persons.Add(new Person {Name = "Bauer", Team = _team3});
_persons.Add(new Person {Name = "Bo", Team = _team1});
_persons.Add(new Person {Name = "Ben", Team = _team2});
_persons.Add(new Person {Name = "Henry", Team = _team3});
TeamList1.ItemsSource = from person in _persons where person.Team == _team1 select person;
TeamList2.ItemsSource = from person in _persons where person.Team == _team2 select person;
TeamList3.ItemsSource = from person in _persons where person.Team == _team3 select person;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
_persons[0].Team = _team2;
}
}
And my Models
public class Person : INotifyPropertyChanged
{
private string _name;
public String Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
private Team _team;
public Team Team
{
get { return _team; }
set { _team = value; NotifyPropertyChanged("Team"); }
}
public override string ToString()
{
return string.Format("Name {0}\t{1}", Name, Team);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
public class Team : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
public override string ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
And the Xaml
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel>
<ListView Name="TeamList1"/>
<ListView Name="TeamList2"/>
<ListView Name="TeamList3"/>
</StackPanel>
<Button Content="Click Me" Click="ButtonClick"/>
</StackPanel>
</Grid>
When the ButtonClick is called I would like my ListView to reflect the changes it made on the _persons.

Just don't use linq, there are CollectionViews for this sort of thing which have filters, you then only have to refresh the views (See: How to: Filter data in a view). (If you must use linq there are also bindable extensions to it)

The LINQ query is evaluated when it is bound to the data source and the items in the list view are generated. The LINQ query will not be re-evaluated whenever you change the properties of a person inside the original list _persons. To achieve this, you have to do a custom implementation that listens to the PropertyChanged event of each Person in the original list and updates an ObservableCollection<Person> accordingly. This observable collection you would bind to the list view.

You need my ObservableComputations library. Using this library you can code like this:
TeamList1.ItemsSource = _persons.Filtering(person.Team == _team1);
TeamList2.ItemsSource = _persons.Filtering(person.Team == _team2);
TeamList3.ItemsSource = _persons.Filtering(person.Team == _team3);

Related

How can I have a multi-column dropdown box in C# WPF?

I'm trying to create a multi-column combobox in c# wpf like below. Any Ideas?
When one row is selected, only the State code show up, but all detail could show up in the drop down selection.
You can get a little creative and solve this problem. Say you have a combo box that's only 60px wide. So you want combo items to be displayed as the full state name and abbreviation, like CA - California, but if selected, you only want the abbr. CA.
I declare a class to represent a state like so:
public class State
{
public string ShortName { get; set; }
public string FullName { get; set; }
private string _displayName;
public string DisplayName
{
set
{
_displayName = value;
}
get
{
if (string.IsNullOrEmpty(_displayName))
return string.Format("{0} - {1}", ShortName, FullName);
else
return _displayName;
}
}
}
The trick is that you use DisplayName to display items on the combo box. Then, in the get of DisplayName, if it already has a value, you return it, if not, you concatenate the short and full names of states.
Then when you do data binding, you have a list of states as well as a SelectedState, and in the setter of that property, you set the DisplayName to ShortName.
So, my XAML:
<Grid>
<ComboBox ItemsSource="{Binding States}"
SelectedValue="{Binding SelectedState}"
DisplayMemberPath="DisplayName"
Name="CmbStates" Width="60" Height="32"/>
</Grid>
Then, in my code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private List<State> _states;
public List<State> States
{
get { return _states; }
set
{
_states = value;
OnPropertyChanged("States");
}
}
private State _selectedState;
public State SelectedState
{
get { return _selectedState; }
set
{
_selectedState = value;
SelectedState.DisplayName = SelectedState.ShortName;
OnPropertyChanged("SelectedState");
}
}
public MainWindow()
{
InitializeComponent();
States = new List<State>
{
new State() { FullName = "California", ShortName = "CA" },
new State() { FullName = "New York", ShortName = "NY" },
new State() { FullName = "Oregon", ShortName = "OR" }
};
DataContext = this;
}
}
Now you should have the full concatenated name in the list:
But only the abbreviation when selected:

Wpf two way binding in listbox

trying to do two way binding in the listbox unfortunately does not work.
Here's the xaml code:
<ListBox Margin="19,0,21,149" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here's the viewmodel and person code:
public class ViewModel
{
public ObservableCollection<Person> Items
{
get
{
return new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person : INotifyPropertyChanged
{
public string _name;
public int Age { get; set; }
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and MainWindow:
public MainWindow()
{
InitializeComponent();
model = new ViewModel();
this.DataContext = model;
}
I do not know what is wrong, trying to bind the property "Name", but it does not work. Please, let me know what may be wrong.
It was enough to change:
public class ViewModel
{
ObservableCollection<Person> _items = new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
public ObservableCollection<Person> Items
{
get
{
return _items;
}
}
}

C# WPF MVVM ComboBox SelectedItem

I know that it's been here a million times but I don't know what's wrong in my code. I tried everything but the ComboBox is not binding SelectedItem correctly.
Here is my complete sandbox solution. You can also find it on GitHub (https://github.com/LukasNespor/ComboBoxBinding).
BindableBase.cs
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void RaisePropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ContactModel.cs
public class ContactModel : BindableBase
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
_Id = value;
RaisePropertyChanged(nameof(Id));
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged(nameof(Name));
}
}
private string _Phone;
public string Phone
{
get { return _Phone; }
set
{
_Phone = value;
RaisePropertyChanged(nameof(Phone));
}
}
public override bool Equals(object obj)
{
if (obj != null || !(obj is ContactModel))
return false;
return ((ContactModel)obj).Id == this.Id;
}
public override string ToString()
{
return $"{Name} {Phone}";
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
...>
<Grid>
<ComboBox Width="200" Height="23"
SelectedItem="{Binding ViewModel.SelectedContact}" ItemsSource="{Binding ViewModel.Contacts}" />
</Grid>
MainWindows.xaml.cs
public partial class MainWindow : Window
{
public MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
ViewModel = new MainViewModel();
ViewModel.SelectedContact = new ContactModel
{
Id = 2,
Name = "Some Guy",
Phone = "+123456789"
};
}
}
MainViewModel.cs
public class MainViewModel : BindableBase
{
public List<ContactModel> Contacts
{
get
{
return new List<ContactModel>
{
new ContactModel() {Id = 1, Name = "John Doe", Phone = "+166666333" },
new ContactModel() {Id = 2, Name = "Some Guy", Phone = "+123456789" }
};
}
}
private ContactModel _SelectedContact;
public ContactModel SelectedContact
{
get { return _SelectedContact; }
set
{
_SelectedContact = value;
RaisePropertyChanged(nameof(SelectedContact));
}
}
}
You need do Sync the list with the current selected item:
1.Xaml:
<ComboBox ItemsSource="{Binding Contacts}" SelectedItem="{Binding SelectedContact}" IsSynchronizedWithCurrentItem="True" />
2.ViewModel:
public ObservableCollection<ContactModel> Contacts { get; set; }
public MainViewModel()
{
_model = new Model {Name = "Prop Name" };
Contacts = new ObservableCollection<ContactModel>
{
new ContactModel {Id = 1, Name = "John Doe", Phone = "+166666333"},
new ContactModel {Id = 2, Name = "Some Guy", Phone = "+123456789"}
};
SelectedContact = Contacts[0];
}
private ContactModel _SelectedContact;
public ContactModel SelectedContact
{
get { return _SelectedContact; }
set
{
_SelectedContact = value;
OnPropertyChanged(nameof(SelectedContact));
}
}

How to set up XAML ListView and create buttons to update a value

First off, apologies if this question doesn't make perfect sense - I am a complete newbie when it comes to C# and XAML.
I have a created this class of people:
class Student
{
private string studentID;
public string StudentID
{
get { return studentID; }
set
{
studentID = value;
NotifyPropertyChanged("StudentID");
}
}
private string firstName;
public string FirstName {
get { return firstName; }
set
{
firstName = value;
NotifyPropertyChanged("FirstName");
}
}
private string surname;
public string Surname
{
get { return surname; }
set
{
surname = value;
NotifyPropertyChanged("Surname");
}
}
private string group;
public string Group
{
get { return group; }
set
{
group = value;
NotifyPropertyChanged("Group");
}
}
private int cValue;
public int CValue
{
get { return cValue; }
set
{
cValue = value;
NotifyPropertyChanged("CValue");
}
}
private string teacher;
public string Teacher
{
get { return teacher; }
set
{
teacher = value;
NotifyPropertyChanged("Teacher");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
public Student() { }
public Student(string studentID, string firstName, string surname, string group, int cValue, string teacher)
{
StudentID = studentID;
FirstName = firstName;
Surname = surname;
Group = group;
CValue = cValue;
Teacher = teacher;
}
// strings used to create random students
private static readonly string[] firstNames = { "Adam", "Bob", "Carl", "David", "Edgar", "Frank", "George", "Harry", "Isaac", "Jesse", "Ken", "Larry" };
private static readonly string[] surnames = { "Adamson", "Bobson", "Carlson", "Davidson", "Edgarson", "Frankson", "Georgeson", "Harryson", "Isaacson", "Jesseson", "Kenson", "Larryson" };
private static readonly string[] groups = { "6a", "5b" };
private static readonly string[] teachers = { "Fred", "Jim"};
// method to create random students
public static IEnumerable<Student> CreateStudents(int count)
{
var people = new List<Student>();
var r = new Random();
for (int i=0; i< count; i++)
{
StringBuilder builder = new StringBuilder();
builder.Append("A");
builder.Append(i.ToString());
string num = builder.ToString();
var s = new Student()
{
StudentID = num,
FirstName = firstNames[r.Next(firstNames.Length)],
Surname = surnames[r.Next(surnames.Length)],
Group = groups[r.Next(groups.Length)],
Teacher = teachers[r.Next(teachers.Length)]
};
people.Add(s);
}
return people;
}
}
I have then created a list of these people objects, and I can bind this list to a list/grid view easily enough.
What I want to do though is have a plus and minus button on each item to add or take away 1 from that persons CValue. (I would upload a picture to demonstrate, but SO won't let me...)
How could I lay out the XAML and add bindings for this in a way that even an idiot like me can understand?
Thanks!
You need to use a RelayCommand or DelegateCommand on your Student VM. They are not part of WPF, but are both commonly used implementations of ICommand that invoke a delegate directly in your VM and are used extensively in MVVM. You can then bind your buttons Command directly to them:
class Student : INotifyPropertyChanged
{
ICommand AddCommand { get; private set; }
ICommand RemoveCommand { get; private set; }
public Student()
{
this.AddCommand = new RelayCommand(Add);
this.RemoveCommand = new RelayCommand(Remove);
}
private Add()
{
this.CValue++;
}
private Remove()
{
this.CValue--;
}
//snip rest of Student properties
}
And in your XAML student template:
<Button Command="{Binding AddCommand>" Content="Add"/>
<Button Command="{Binding RemoveCommand>" Content="Remove"/>
If you prefer to keep your Student object free from VM stuff (ie it's purely a model class), then you can implement the same commands on your parent VM, but pass the student object as the command parameter:
class StudentsVM: INotifyPropertyChanged
{
ICommand AddCommand { get; private set; }
ICommand RemoveCommand { get; private set; }
public StudentsVM()
{
this.AddCommand = new RelayCommand<Student>(Add);
this.RemoveCommand = new RelayCommand<Student>(Remove);
}
private Add(Student student)
{
student.CValue++;
}
private Remove(Student student)
{
student.CValue--;
}
//snip rest of Student properties
}
And in your XAML student template:
<Button Command="{Binding DataContext.AddCommand, ElementName=root}"
CommandParameter="{Binding}"
Content="Add"/>
<Button Command="{Binding DataContext.RemoveCommand, ElementName=root}"
CommandParameter="{Binding}"
Content="Remove"/>
Where "root" is your parent view. This is just one way of getting hold of the commands in the parent view, you could use RelativeSource instead if you prefer.
As u can see here, the PropertyChanged event is an interface event of INotifyPropertyChanged, so I think your class 'Student' needs to explicitly implement this interface.
public class Student : INotifyPropertyChanged
Maybe this implementation guide could help you.
You need to use Observable Collection instead of List
then all you have to do is manipulate this list as you want and it will automatically notify the UI with the changes

Why INotifyPropertyChanged's event PropertyChangedEventHandler is always null?

This is my sample code, why PropertyChangedEventHandler property is null?
the list is bounded to Listbox which should subscribe to the event. Shouldn't it?
public class Data<T> : INotifyPropertyChanged where T : class
{
T _data;
public T MyData
{
get { return _data; }
set
{
_data = value;
this.OnPropertyChanged("MyData");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var h = this.PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(property));
}
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public Window1()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
list.Add(new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } });
list.Add(new Data<Person> { MyData = new Person { Name = "Tom", Age = 33 } });
this.DataContext = list;
}
<Grid>
<ListBox Name="listbox1"
ItemsSource="{Binding}"
Style="{DynamicResource lStyle}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Label Width="100" Content="{Binding Path=MyData.Name}"></Label>
<Label Width="100" Content="{Binding Path=MyData.Age}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Probably because the ListBox hasn't had a chance to render and start listening for change events yet.
When I tested the following PropertyChanged was not null during the change inside listbox1_MouseLeftButtonDown.
Data<Person> p;
public MainWindow()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
this.p = new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } };
list.Add(this.p);
this.DataContext = list;
}
private void listbox1_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
p.DataContext = null;
}
In your provided code, the PropertyChanged event handler will be subscribed to. Why do you think that is not the case?
Changes like this will update the listbox correctly:
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData = new Person() { Name = "Bob", Age = 12 };
Remember that your Data class don't support property notify on individual properties,
so the following changes will not update the listbox.
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData.Name = "Bob";
list[0].MyData.Age = 12;

Categories