WPF Binding ViewModel - c#

So I am currently trying to learn WPF with MVVM and I was following some tutorials online. Now that I have a simple Project, I tried to do it on my own but somehow the ListBox stays empty. It doesn't get bound to the ViewModel, I think. What am I missing here?
ViewModel:
public class PersonViewModel
{
ObservableCollection<Person> Personen { get; set; } = new ObservableCollection<Person>();
public PersonViewModel()
{
Personen.Add(new Person { Vorname = "My", Nachname = "Name", Email = "my#name.com" });
Personen.Add(new Person { Vorname = "Max", Nachname = "Mustermann", Email = "max#mustermann.de" });
Personen.Add(new Person { Vorname = "John", Nachname = "Doe", Email = "john#doe.com" });
Personen.Add(new Person { Vorname = "Jane", Nachname = "Doe", Email = "jane#doe.com" });
}
}
}
Also, I'm setting my DataContext like this:
public PersonenView()
{
InitializeComponent();
this.DataContext = new ViewModel.PersonViewModel();
}

You just need to give a public accessor to ObservableCollection Personen in your ViewModel.
You should also make the property readonly (or fire a property change notification from its setter):
public ObservableCollection<Person> Personen { get; }
= new ObservableCollection<Person>();

Related

How can I have two editable and updatable DataGridViews bounded to a list and sub-list?

I have a List<Input> named listInput that contains property of type a List<class> named friends:
List<Input> listInput=new List<Input>();
BindingList<Input> blInput;
BindingSource bsInput;
public class Input
{
public string Name { get; set; }
public List<Friend> friends{ get; set; }
}
public class Friend
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I have two DataGridView controls, named dgInputand dgFriends.
dgInput is bound to a BindingList like this:
blInput = new BindingList<Input>(listInput);
bsInput = new BindingSource(blInput, null);
dgInput.DataSource = bsInput;
dgInput is editable and updatable at run-time after user changes any cell on dgInput.
The question is: how can I bind dgFriends to sublist friends, so that it will automatically update when the dgInput curretn Row changes?
It's also important that dgFriens can be updated when an User changes any cell on dgFriends (the properties of listInput must preserve the changes).
Here's a simple example:
The first DataGridView uses the BindingSource you defined. The DataSource of this BindingSource is set to the List<Input> collection. This DGV can only show the Name property from the List<Input>, since you cannot present Rows containing both single values and collections of values.
The DataMember of the BindingSource must be empty (or null): if you set Name as DataMember, you'll get an array of Char as a result instead of a string.
A second BindingSource is created: its DataSource is set to the existing BindingSource. In this case, the DataMember is set explicitly to the Friends property, which represents a List<class> objects (a single object that represents a collection that will provide the data to the Rows of the second DGV).
This generates an active binding (a relationship) between the two BindingSource objects: when the first BindingSource.Current object changes, the second BindingSource will follow, showing its Current object (the List<Friend> linked to the current Name property).
All properties of the two classes are editable.
Note:
I've implemented the INotifyPropertyChanged interface in the Input class to notify changes of the Name property: it's not strictly required (or, it' not required at all) in this context, but you may want to have it there, you'll quite probably need it later.
List<Input> listInput = new List<Input>();
BindingSource bsInput = null;
public SomeForm()
{
InitializeComponent();
bsInput = new BindingSource(listInput, "");
var bsFriends = new BindingSource(bsInput, "Friends");
dataGridView1.DataSource = bsInput;
dataGridView2.DataSource = bsFriends;
}
public class Input : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string m_Name = string.Empty;
public string Name {
get => m_Name;
set { m_Name = value;
NotifyPropertyChanged(nameof(this.Name));
}
}
public List<Friend> Friends { get; set; } = new List<Friend>();
private void NotifyPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Friend {
public string FirstName { get; set; }
public string LastName { get; set; }
}
This is how it works:
Sample object initialization, for testing:
listInput.AddRange(new[] {
new Input() {
Name = "Name One", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of One"},
new Friend () { FirstName = "Second", LastName = "Friend of One"},
new Friend () { FirstName = "Third", LastName = "Friend of One"},
}
},
new Input() {
Name = "Name Two", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of Two"},
new Friend () { FirstName = "Second", LastName = "Friend of Two"},
new Friend () { FirstName = "Third", LastName = "Friend of Two"},
}
},
new Input() {
Name = "Name Three", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of Three"},
new Friend () { FirstName = "Second", LastName = "Friend of Three"},
new Friend () { FirstName = "Third", LastName = "Friend of Three"},
}
}
});

DataGrid ListCollectionView sorting

I have a view model that exposes an ListCollectionView and data grid that is bound to it. For some reason when swapping the ListCollectionView and creating a new one from the source collection the sorting is lost for the new items that are added to the source collection.
The source collection is an ObservableCollection.
The sorting is correct for the items that already exist in source collection on ListCollectionView creation.
I dont use GetDefaultView but instead create ListCollectionView myself whenever required.
When adding new items to the source collection no sorting is done and items appear at the end of the list.
I raise INotifyPropertyChanged when swapping ListCollectionView.
Anyone knows why i get such behavior ?
The problem is probably in however you create your listcollectionview. ( You should have posted that code. )
I put some experimental code together, which works as I expected. I just use a listbox but this will make no difference.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding AddPersonCommand}" Grid.Column="1"/>
<ListBox ItemsSource="{Binding People}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Viewmodel
public class MainWindowViewModel : BaseViewModel
{
private RelayCommand addPersonCommand;
public RelayCommand AddPersonCommand
{
get
{
return addPersonCommand
?? (addPersonCommand = new RelayCommand(
() =>
{
People.Add(new Person { FirstName = "Adam", LastName = "Barlow" });
}
));
}
}
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public ListCollectionView LCV { get; set; }
public MainWindowViewModel()
{
LCV = (ListCollectionView)CollectionViewSource.GetDefaultView(People);
LCV.SortDescriptions.Add(
new SortDescription("LastName", ListSortDirection.Ascending));
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
When I click the button it adds Ken Barlow to the observablecollection and it appears top of the listbox.
I also tried binding to LCV:
And instantiating that passing people in via the ctor.
public class MainWindowViewModel : BaseViewModel
{
private RelayCommand addPersonCommand;
public RelayCommand AddPersonCommand
{
get
{
return addPersonCommand
?? (addPersonCommand = new RelayCommand(
() =>
{
Person person = new Person { FirstName = "Adam", LastName = "Barlow" };
People.Add(person);
}
));
}
}
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public ListCollectionView LCV { get; set; }
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
LCV = new ListCollectionView(People);
LCV.SortDescriptions.Add(
new SortDescription("LastName", ListSortDirection.Ascending));
}
}
This also works.

c# DataGridView autogenerate columns show only property of base class

I have a base class (person) and 2 derivated class(person_registered) and derivated class(person_not_registered
I have a **list of with inner objects mix of person_registered and person_not_registered **
I have a dataGridView, for show the list, and I have set dataGridView.Datasource=registry(the list of ), with the dataGridView.autogeneratecolumns=true;
but the dataGridView show me only the columns of the properties of the base class and NOT show me the properties of classes derivated
Why??
It doesn't show because you have it backwards.
It should be Person : Registered not Registered : Person
Try these classes:
public class Person : Registered
{
public int ID { set; get; }
public string FirstName { set; get; }
public string LastName { set; get; }
}
public class Registered
{
public DateTime DateOfRegister { set; get; }
}
Then fill your dataGridView like this:
// Create the Data
List<Person> myData = new List<Person>();
myData.Add(new Person() { ID = 5, FirstName = "Jamie", LastName = "White", DateOfRegister = Convert.ToDateTime("Dec 25, 2015") });
myData.Add(new Person() { ID = 10, FirstName = "Mike", LastName = "Smith", DateOfRegister = Convert.ToDateTime("Jan 5, 2016") });
myData.Add(new Person() { ID = 25, FirstName = "Joe", LastName = "Yang", DateOfRegister = Convert.ToDateTime("Feb 28, 2016") });
// Pass the data to the dataGrid
dataGridView1.DataSource = myData;
Public **class person**
{String *Name*; String *Surname*;}
Public **class registered:person**
{ datetime *dateOfRegister*;}
public **class registry:list<person>**
{ public new void Add(Person item)
....}
In Form Grid:
dgvNotRegister.DataSource = this.anagrafica; //list of person<br>
dgvNonRegister.AutoGenerateColumns = true;
dgvNotRegister **show**:
Name Surname
but NOT: show dateOfRegister

WPF toolkit CheckListBox SelectedItemsOverride not working

I have a check list box from the wpf toolkit 2. I cannot get all of the selected items. I read that I am supposed to use SelectedItemsOverride to get all of my selected items but it does not seem to work. I put a break point in the setter for TestClassSelected but it is never fired. Any Ideas?
<xctk:CheckListBox Name="MyCheckList"
ItemsSource="{Binding TestClassCollection}"
DisplayMemberPath="DisplayName"
SelectedItemsOverride="{Binding TestClassSelected}" />
.
public IEnumerable<TestClass> TestClassCollection
{
get { return _testClassCollection; }
set
{
_testClassCollection = value;
OnPropertyChanged("TestClassCollection");
}
}
public List<TestClass> TestClassSelected
{
get { return _testClassSelected; }
set
{
_testClassSelected = value;
OnPropertyChanged("TestClassSelected");
}
}
.
public class TestClass
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String DisplayName {
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
This is my test data I have been using.
TestClassCollection = new List<TestClass>
{
new TestClass {FirstName = "FIrstName", LastName = "LastName"},
new TestClass {FirstName = "Brad", LastName = "Holder"},
new TestClass {FirstName = "Sam", LastName = "Ryans"},
new TestClass {FirstName = "Ryan", LastName = "Thomas"},
new TestClass {FirstName = "Lee", LastName = "Rod"},
new TestClass {FirstName = "Amanda", LastName = "Gustaf"},
new TestClass {FirstName = "Chris", LastName = "Holems"},
new TestClass {FirstName = "Doug", LastName = "Schnitzel"},
new TestClass {FirstName = "Lisa", LastName = "Bull"},
new TestClass {FirstName = "Fred", LastName = "Simpson"},
new TestClass {FirstName = "Scott", LastName = "Rogers"}
};
try add Mode and UpdateSourceTrigger at binding
<xctk:CheckListBox Name="MyCheckList"
ItemsSource="{Binding TestClassCollection}"
DisplayMemberPath="DisplayName"
SelectedItemsOverride="{Binding TestClassSelected,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
update:
I'd checked example code from closed issue at Extended WPF Toolkit CodePlex site.
Try to change TestClassSelected property to ObservableCollection. (Still keep UpdateSourceTrigger described above in .xaml)
public ObservableCollection<TestClass> TestClassSelected
I faced the same problem and solved it by assigning the private field of the property
In your example
private ObservableCollection<TestClass> _testClassSelected=new ObservableCollection<TestClass>();
I combined previous answers/comments and what worked for me was to bind SelectedItemsOverride to an ObservableCollection<T>, include UpdateSourceTrigger=PropertyChanged, and attach a method to the ObservableCollection<T>'s event CollectionChanged.
The XAML:
<xctk:CheckListBox
ItemsSource="{Binding AllItems}"
DisplayMemberPath="Display"
ValueMemberPath="Value"
SelectedItemsOverride="{Binding Path=DataContext.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</xctk:CheckListBox>
Code in ViewModel:
public IEnumerable<Item> AllItems { get; set; }
public ObservableCollection<Item> SelectedItems { get; set; }
public ViewModel()
{
SelectedItems = new ObservableCollection<Item>();
SelectedItems.CollectionChanged += SelectedItems_CollectionChanged;
}
private void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Handle collection changed event
}

How to bind a List to a ComboBox?

I want to connect a BindingSource to a list of class objects and then objects value to a ComboBox.
Can anyone suggest how to do it?
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
is my class and I want to bind its name field to a BindingSource which could be then associated with a ComboBox
As you are referring to a combobox, I'm assuming you don't want to use 2-way databinding (if so, look at using a BindingList)
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country(string _name)
{
Cities = new List<City>();
Name = _name;
}
}
List<Country> countries = new List<Country> { new Country("UK"),
new Country("Australia"),
new Country("France") };
var bindingSource1 = new BindingSource();
bindingSource1.DataSource = countries;
comboBox1.DataSource = bindingSource1.DataSource;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Name";
To find the country selected in the bound combobox, you would do something like: Country country = (Country)comboBox1.SelectedItem;.
If you want the ComboBox to dynamically update you'll need to make sure that the data structure that you have set as the DataSource implements IBindingList; one such structure is BindingList<T>.
Tip: make sure that you are binding the DisplayMember to a Property on the class and not a public field. If you class uses public string Name { get; set; } it will work but if it uses public string Name; it will not be able to access the value and instead will display the object type for each line in the combo box.
For a backgrounder, there are 2 ways to use a ComboBox/ListBox
1) Add Country Objects to the Items property and retrieve a Country as Selecteditem. To use this you should override the ToString of Country.
2) Use DataBinding, set the DataSource to a IList (List<>) and use DisplayMember, ValueMember and SelectedValue
For 2) you will need a list of countries first
// not tested, schematic:
List<Country> countries = ...;
...; // fill
comboBox1.DataSource = countries;
comboBox1.DisplayMember="Name";
comboBox1.ValueMember="Cities";
And then in the SelectionChanged,
if (comboBox1.Selecteditem != null)
{
comboBox2.DataSource=comboBox1.SelectedValue;
}
public MainWindow(){
List<person> personList = new List<person>();
personList.Add(new person { name = "rob", age = 32 } );
personList.Add(new person { name = "annie", age = 24 } );
personList.Add(new person { name = "paul", age = 19 } );
comboBox1.DataSource = personList;
comboBox1.DisplayMember = "name";
comboBox1.SelectionChanged += new SelectionChangedEventHandler(comboBox1_SelectionChanged);
}
void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
person selectedPerson = comboBox1.SelectedItem as person;
messageBox.Show(selectedPerson.name, "caption goes here");
}
boom.
Try something like this:
yourControl.DataSource = countryInstance.Cities;
And if you are using WebForms you will need to add this line:
yourControl.DataBind();
If you are using a ToolStripComboBox there is no DataSource exposed (.NET 4.0):
List<string> someList = new List<string>();
someList.Add("value");
someList.Add("value");
someList.Add("value");
toolStripComboBox1.Items.AddRange(someList.ToArray());
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
public class City
{
public string Name { get; set; }
}
List<Country> Countries = new List<Country>
{
new Country
{
Name = "Germany",
Cities =
{
new City {Name = "Berlin"},
new City {Name = "Hamburg"}
}
},
new Country
{
Name = "England",
Cities =
{
new City {Name = "London"},
new City {Name = "Birmingham"}
}
}
};
bindingSource1.DataSource = Countries;
member_CountryComboBox.DataSource = bindingSource1.DataSource;
member_CountryComboBox.DisplayMember = "Name";
member_CountryComboBox.ValueMember = "Name";
This is the code I am using now.
As a small addition to this, I tried to incorporate something similar to this code, and was frustrated that adding/removing from the list was not reflected in the ComboBox. This is because the Add/Remove does not trigger the OnPropertyChange.
If you want to Add/Remove and have them reflected in the ComboBox, you will need to change List<> to ObservableCollection
List<Country> Countries
Should be replaced with
private ObservableCollection<Country> countries;
public ObservableCollection<Country> Countries
{
get { return countries; }
set
{
countries= value;
OnPropertyChanged("Countries");
}
}
Where OnPropertyChanged and ObservableCollection comes from
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
All of this is more eloquently expressed in a previous explanation here

Categories