C# WPF MVVM cannot show elements of list - c#

Im trying to write a simple WPF app with MVVM pattern, but showing elements of a list does not work I ame pretty sure that something is wrong with binding because its my first time with it
<Window.Resources>
<local:ViewModel x:Key="test"/>
</Window.Resources>
<Grid>
<ListView Name="lstPersons" ItemsSource="{Binding test.peopleList}" >
<ListView.View>
<GridView.Columns>
<GridViewColumn Header="name" DisplayMemberBinding="{Binding name}" />
<GridViewColumn Header="surname" DisplayMemberBinding="{Binding surname}" />
View Model fragment:
public class ViewModel
{
private personModel.Root peopleDB = new personModel.Root();
public ViewModel()
{ }
public List<personModel.Person> peopleList
{
get { return peopleDB.people; }
}
Model class fragment:
public class Root
{
public List<Person> people;
public Root()
{
people = new List<Person>();
people.Add(new Person("aa", "aa", 1, new Adress("bb", "cc")));
people.Add(new Person("bb", "bb", 1, new Adress("bb", "cc")));
people.Add(new Person("cc", "cc", 1, new Adress("bb", "cc")));
}
}
public class Person
{
public string name { get; set; }
public string surname { get; set; }
public int age { get; set; }
public Adress address { get; set; }
tried couple of things with binding but none of them worked :/

The problem here sounds like your DataContext is not set.
There's multiple ways of doing that. As escull638 said, you could manually hardcode the DataContext in with the Window using either XAML
<Window.DataContext>
<local:ViewModel>
</Window.DataContext>
or Code-Behind
this.DataContext = new ViewModel();
and change your binding now that the .DataContext is set correctly
<ListView ItemsSource="{Binding peopleList}">
But keep in mind that hardcoding the .DataContext like this is typically only be used at the highest level in your application, and it should not be something common to see when working with WPF. Controls in WPF are intentially "lookless", and the binding system is used to pass them their data, so by doing something like hardcoding the DataContext means you cannot use the control with any other data object, which kind of defeats one of the biggest advantages of using WPF.
Another solution would be to change the Source property of your binding so it points to the static object defined in <Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource test}, Path=peopleList}">
I prefer this way because it's obvious just looking at the ListView XAML that you are binding to a static source, and it saves all kind of headaches later when you're trying to pass a dynamic source into the Control and discovering the DataContext isn't set to what you expect it.
As a side note, if you're having trouble understanding what the DataContext is for or how it works, I tend to link beginners to this answer of mine which explains it in more detail :)

Set the DataContext to the viewmodel by adding this to your xaml file:
<Window.DataContext>
<local:ViewModel>
</Window.DataContext>
Then when you need to bind something you can just use:
<ListView Name="lstPersons" ItemsSource="{Binding peopleList}" >

Related

Data Binding a List to an already bound object c#

Edit: The marked solution is correct, however if anyone is having similar problems using SQLite avoid calling the database using using as shown in my example as it messes up lazy loading as the context is closed before it can lazy load. To fix this create a new Object as that will cause lazy loading to fully copy the object passed back from the db so either do that in "using" or manually close the db after you have copied.
I have made a simple example to describe the problem
<Grid>
<StackPanel>
<ListView x:Name="People" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListView x:Name="PhoneNumbers" ItemsSource="{Binding PhoneNumbers}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
So I have a ListView bound to an object let's call it Person() this object has a property of type collection that I want to access. I want to bind a list property in this case just a list of strings to x:name="PhoneNumber" how can I do this? I'm setting the ItemSource of the parent ListView in the code behind. I would like to know any information you have on this topic as I have not been able to find a whole lot on the subject read all of the c# docs, searched stack most I was able to find was to use an ObservableList which I tried, it didn't work. Looking for a data dump any information you think could get me moving in the right dirrection would be greatly appreciated as my actual use case is much more complicated than this example.
I'm aware this would be easier using MVVM but for now I want to get a basic prototype up and running without getting in to MVVM. The end goal is to move to MVVM.
Edit : Literally just binding in code behind atm
Simple example VV
public LoadingWindow()
{
InitializeComponent();
using (var db = new PeopleContext())
{
var Test = db.People.ToList();
if (Test != null)
{
People.ItemsSource = Test;
}
}
}
Example Class
public class People
{
public string Name { get; set; } = "";
// I did read that using an observable list was needed but I tried it and it didn't work
// I'd prefer not to use an observable list
public List<string> PhoneNumbers { get; set; } = new List<string>();
}
Probably the question is not clear, but I figured it out. First of all, you should bind your ListView to ObservableCollection or BindingList in order to get UI updated when your collections are changed. The second problem is that you are not using property to bind your list, you are trying to set your ListView's ItemsSource property to the field, which is not possible. You are using this code var Test = DatabaseInfo.ToList(); Test is a field and then you are trying to bind to it, it is clear that it won't work. You should add a property to your MainWindow BindingList<Person>, and then in the MainWindow constructor fill it with information from DataBase.
So I just created a simple project, as I don't have DataBase I filled collections manually.
In MainWindow I have property BindingList<Person>
public partial class MainWindow : Window
{
public BindingList<Person> People { get; set; }
public MainWindow()
{
InitializeComponent();
People = new BindingList<Person>();
Person person = new Person() { Name = "Jonh" };
Person person2 = new Person() { Name = "Mike" };
People.Add(person);
People.Add(person2);
PeopleList.ItemsSource = People;
}
}
Person class is defined like this
public class Person
{
public BindingList<string> PhoneNumbers { get; set; }
public string Name { get; set; }
public Person()
{
PhoneNumbers = new BindingList<string>();
PhoneNumbers.Add("1");
PhoneNumbers.Add("2");
PhoneNumbers.Add("3");
PhoneNumbers.Add("4");
}
}
I used same XAML, your XAML code doesn't have any problems
<ListView x:Name="PeopleList">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListView x:Name="PhoneNumbers" ItemsSource="{Binding PhoneNumbers}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is result
Just use BindinList<T> or ObservableCollection<T> and it should work.

In WPF, how do I two-way bind a Checkbox's IsChecked to property to List<>.Contains?

Sorry if the wording of my question is not great or if this has been answered somewhere, I've searched, but I don't really know how well to explain what I am trying to do.
Here's a simple testbed I've partially set up to help explain:
MainWindow.xaml:
<Window x:Class="wpfExample.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:wpfExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
<ListBox Grid.Column="1" ItemsSource="{Binding Interests}" Margin="0,4,4,4">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
MainWindow.xaml.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace wpfExample
{
public class Person
{
public string Name { get; set; }
public ObservableCollection<Guid> Interests { get; set; }
}
public class Interest
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Data
{
public ObservableCollection<Person> People { get; set; }
public ObservableCollection<Interest> Interests { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new Data
{
People = new ObservableCollection<Person>
{
new Person {Name="Fred", Interests=new ObservableCollection<Guid>() },
new Person {Name="Jane", Interests=new ObservableCollection<Guid>() },
new Person {Name="Zach", Interests=new ObservableCollection<Guid>() }
},
Interests = new ObservableCollection<Interest>
{
new Interest {Name="Gardening", Id=Guid.NewGuid() },
new Interest {Name="Writing", Id=Guid.NewGuid() },
new Interest {Name="Avoiding Tax", Id=Guid.NewGuid() }
}
};
InitializeComponent();
}
}
}
So I have a DataContext that contains two lists. One contains Interests, which have a name and an ID. The other contains People which have a name and a list of IDs of interests.
When a Person is selected in the UI, I want to be able to add and remove IDs of interests to their respective list, hence the ListView in column 1 is bound to the list of Interests, but how do I correctly bind up the IsChecked property of the checkboxes in the list?
In my full project, I've been able to successfully read properties of the selected Person's interest list by using a MultiBinding for IsChecked with a MultiValueConverter to pass both the Id of the Interest and the List of Interests of the Person through together (since you can't use binding with the parameter for a 'normal' value converter). I feel that this solution is a little bit of an abuse of the converter, but I'm happy to stick with it if necessary.
How do I implement a system that will allow me to add and remove Interest Guids to a Person's list of interests when the checkbox is toggled? Is there a cleaner way of doing this? I don't want to change the model if it can be avoided.
I wouldn't say your MultiConverter solution is an abuse at all; you are doing exactly what converters should (take a set of data and converting to the target type, then going back).
That being said; converters are a bit of a mess due to their generality (use of object), and that goes double for Multi Converters, so if you want a different solution I would recommend creating a view model wrapper for Interest; say Selectable<T>:
public class Selectable<T>
{
public T Data {get; set;}
public bool Selected {get; set;}
}
Then update your property
public ObservableCollection<Selectable<Interest>> Interests { get; set; }
And bind IsChecked directly to Selected
Then whenever the selected Person is changed you can update the Selected property appropriately; store the previous value to the other Person, etc. It somewhat depends on when the user action needs to be reflected in the model.
All that being said, both approaches will be perfectly valid, it just comes down to what you are most comfortable with.

Binding an observable collection to a datagrid?

Recently I've been trying to learn WPF, in an attempt to learn WPF I've been creating small little project to get familiar with WPF. Currently, at the moment I'm having trouble with bindings in WPF. More specifically binding an observable collection to a datagrid. you can see my code below
<Window x:Class="Progress_bar_example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="False" Height="287" HorizontalAlignment="Left"
Margin="20,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="471"
ItemsSource="{Binding personsInformation}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding .firstName}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
and the back end code is
namespace Progress_bar_example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public class myDataContext
{
private ObservableCollection<PersonData> personsInformation;
}
public class PersonData
{
public String firstName;
//public String sureName;
//public int dayOfBirth;
//public int monthOfBirth;
//public int yearOfBirth;
}
public ObservableCollection<PersonData> personsInformation;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
personsInformation = new ObservableCollection<PersonData>();
PersonData person = new PersonData()
{
firstName = "Thomas"
};
personsInformation.Add(person);
ContentRendered += Window_ContentRendered;
}
private void Window_ContentRendered(object sender, EventArgs e)
{
//this.DataContext = _dt;
}
}
}
change your datagrid code as in datagridtextcolumn column name should not bind with Dot(.)
<DataGrid AutoGenerateColumns="False" Height="287" HorizontalAlignment="Left" Margin="20,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="471" ItemsSource="{Binding personsInformation}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding firstName}"/>
</DataGrid.Columns>
</DataGrid>
There are a few things preventing your example from working correctly, thankfully, most are little tweaks.
The first thing to be mindful of, is that, in general, you should always be binding to public Properties, rather than fields as you are currently doing.
So your desired binding properties should become:
// More Code
public ObservableCollection<PersonData> PersonsInformation { get; set; }
public class PersonData
{
public String FirstName { get; set; }
// More Code
}
(Note I've made the properties start with upper case letters, which is generally good practice for public properties.
There are many excellent explanations already on this site and others for more information on this topic.
In addition, when you assign the DataContext, then initialise the property personsInformation, you are essentially changing the personsInformation field without notifying the UI - this change will not be picked up, and any further changes to the new collection (or the assignment of the property itself), will not be reflected in the UI.
The easiest fix for you at this stage, is to simply initialise personsInformation at some point before the line this.DataContext = this;
e.g:
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
personsInformation = new ObservableCollection<PersonData>();
this.DataContext = this;
// Code that modifies personsInformation.
}
Note: A fairly common cause of errors around binding to Collections (particularly an ObservableCollection) is getting tripped up with the distinction between assigning the Property holding the collection, and modifying that collection. The ObservableCollection itself implements INotifyPropertyChanged, which provide notifications when the items are added/removed/cleared. If you assign a new collection to your personsInformation, no notification will be raised (unless you have implemented INotifyPropertyChanged and raised an appropriate notification (there are a wealth of excellent guides on that topic).
There are other things you could consider, like assigning the DataContext elsewhere, involving ViewModels, implementing iNotifyPropertyChanged.
Also, binding to .firstName should work as you intend, but the . is not required. I tend to write my bindings like {Binding Path=firstName} which is purely a style preference (it reads clearly to me), you can omit the path as you have done {Binding firstName}.
But this should get your example up and running, and allow you to get on with some fun exploration/testing.

Having trouble binding ViewModel to ComboBox

I have a viewmodel setup as the following
public class cDriveListVM
{
public string Drive { get; set; }
public cDriveListVM(string name)
{
Drive = name;
}
}
I declare the observablecollection in the window and set its datacontext to this observable collection.
public ObservableCollection<cDriveListVM> DriveList { get; set; }
private void dl()
{
DriveList = new ObservableCollection<cDriveListVM>();
DriveList.Add(new cDriveListVM("drive 1"));
DriveList.Add(new cDriveListVM("drive 2"));
this.DataContext = DriveList;
}
Xml for combobox:
<ComboBox x:Name="Drive_ComboBox" ItemsSource="{Binding Path=Drive}" HorizontalAlignment="Center" IsReadOnly="True" Grid.Column="0" Grid.Row="0" Width="300" Margin="10" SelectionChanged="Drive_Changed" Height="22" VerticalAlignment="Top"/>
I am just learning how to use Viewmodel so I am unsure what I am doing wrong, any help would be appreciated. I updated the xml file it results in the following combbox.
There are a few problems with this code.
One, the binding is set up wrong. Since the property with the viewmodel collection is DriveList, the binding should be ItemsSource="{Binding Path=DriveList}".
Two, you are attempting to display a field from your viewmodel, which is not doable. WPF's binding engine only works with properties, so the viewmodel should have a property:
public string Drive { get; set; }
And finally, the DisplayMemberPath should match the property name from the viewmodel: DisplayMemberPath="Drive".
Update: I just noticed that the DataContext is the observable collection itself -- I probably missed it on the first read. In that case, you want to bind directly to the data context:
ItemsSource="{Binding}"
And set DisplayMemberPath to the property you want to display:
DisplayMemberPath="Drive"

Some irritations with ListView and DataContext

I've wrote the following C# and XAML Code:
namespace ListViewExample1
{
public partial class MainWindow : Window
{
public ObservableCollection<MyColleague> myCollegues = new ObservableCollection<MyColleague>();
public MainWindow()
{
myCollegues.Add(new MyColleague() { Name = "Tim", Surname = "Meier" });
myCollegues.Add(new MyColleague() { Name = "Martin", Surname = "Hansen" });
myCollegues.Add(new MyColleague() { Name = "Oliver", Surname = "Drumm" });
InitializeComponent();
}
public ObservableCollection<MyColleague> MyColleagues
{
get { return this.myCollegues; }
}
}
public class MyColleague
{
public String Name { get; set; }
public String Surname { get; set; }
}
}
XAML-Code:
<Grid>
<ListView ItemsSource="{Binding}" DataContext="{Binding RelativeSource={RelativeSource ListViewExample1:MainWindow}, Path=myCollegues}">
<ListView.View >
<GridView >
<GridViewColumn Header="Name" Width="150" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Surname" Width="150" DisplayMemberBinding="{Binding Surname}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Now I need to set the datacontext, but at this point I have some irritations. Which DataContext-Syntax is right?
There are dozens of ways to set DataContext; no one is inherently right.
It's worth noting that there's no reason to set DataContext on an items control at all if all you need is to bind one property (ItemsSource, in this case). Setting DataContext simplifies binding multiple properties, because all of the bindings use the same context.
If you want to do data binding without any code-behind (as you said in a comment), the example you've chosen isn't very good, since you're creating the object in code-behind. Try creating a class with a parameterless constructor, e.g.:
public class MyColleagueCollection : ObservableCollection<MyColleague>
{
public MyColleagueCollection()
{
Add(new MyColleague() { Name = "Tim", Surname = "Meier" });
Add(new MyColleague() { Name = "Martin", Surname = "Hansen" });
Add(new MyColleague() { Name = "Oliver", Surname = "Drumm" });
}
}
Then you can do:
<ListView>
<ListView.ItemsSource>
<local:MyColleagueCollection/>
</ListView.ItemsSource>
...
</ListView>
Or you could set the DataContext, and set the ItemsSource to "{Binding}". Or create the object in a resource dictionary, and bind using StaticResource.
You could also create your collection as a property (not a field, as x0r correctly points out) of the Window class and do this:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"...
which now makes the Window itself the data context object, allowing any element in the window to bind to its properties directly without using RelativeSource.
Or (we're nowhere near done), you can give the window a name, and then bind to it by name:
<ListView ItemsSource=`{Binding ElementName=MyWindow, Path=MyCollection}"...
Let's not even get into using an ObjectDataProvider.
What most people end up doing - this is as close to a "right" answer as you're going to find - is creating a view model class for the main window, instantiating it in the window's constructor, and setting the window's DataContext to that object. From that point on, any view that the main window displays is bound to a property of that view model. See Josh Smith's article on the Model/View/ViewModel pattern for a really good worked example.
Binding and data contexts are incredibly versatile. This of course also means that there are a lot of things you can get wrong. It goes with the territory. Once you understand them, though, you rarely run into real problems.
Your binding to myCollegues can't work, because you bind to a field. You have to bind to the property, which is MyColleagues in your case.
The simplest solution to set your DataContext would be in code-behind, assuming that MainWindow.xaml contains the ListView:
public MainWindow()
{
[...]
InitializeComponent();
DataContext = this;
}
Instead of keeping your data in code-behind, a better solution would be to use the MVVM approach. There you keep your data in a separate class, and then set the DataContext to an instance of this class.
To bind in XAML use the following syntax:
<ListView ItemsSource="{Binding MyCollegues}"
DataContext="{Binding Path=., Mode=FindAncestor, RelativeSource={RelativeSource AncestorType={x:Type ListViewExample1:MainWindow}}}">

Categories