Some irritations with ListView and DataContext - c#

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}}}">

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.

C# / WPF Bind in TabControl ItemsSource to property in code behind of List item

In my ViewModel I have a BindingList which holds my Views that should be displayed as tabs in my TabControl. The text for the tab is defined in the code behind of the view. I also defined a simple test class to test the binding which works perfectly. Only the binding to the code behind property does not work.
Xaml code for my TabControl:
<TabControl ItemsSource="{Binding TabControlContentList}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName, FallbackValue=FallbackValue}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
The BindingList which it is bound to:
void RaisePropertyChanged([CallerMemberName] string propertyName = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public BindingList<object> TabControlContentList
{
get => tabControlContentList;
set
{
tabControlContentList = value;
RaisePropertyChanged();
}
}
private BindingList<object> tabControlContentList = new BindingList<object>();
Test class:
class ControlTest
{
public string DisplayName { get; private set; } = "";
public ControlTest(string name) => DisplayName = name;
}
Xaml code behind of AnalysisView.xaml.cs:
public partial class AnalysisView
{
public string DisplayName { get; private set; } = "Analysis";
public AnalysisView()
{
InitializeComponent();
}
}
In my ViewModel I have the following code:
public AnalysisView analysisView = new AnalysisView();
TabControlContentList.Clear();
TabControlContentList.Add(new ControlTest("Menu 1"));
TabControlContentList.Add(new ControlTest("Menu 2"));
TabControlContentList.Add(analysisView);
TabControlContentList.Add(new ControlTest("Menu 3"));
TabControlContentList.Add(new ControlTest("Menu 4"));
My TabControl then shows five tabs. First two have the text "Menu 1" and "Menu 2" on them, the third one reads "FallbackValue", followed by "Menu 3" and "Menu 4".
I have no idea anymore why the binding on the property in code behind in AnalysisView.xaml.cs does not work. Is this maybe a general Wpf thing?
Before I present my answer, I highly recommend that you download and learn how to use Snoop for WPF, or use the Visual Studio built-in XAML runtime inspection tools. These allow you to look at bindings, data context, etc.
The main reason why your code doesn't work, is that a UserControl doesn't have a DataContext by default.
So, updating your AnalysisView like so will give it a DataContext pointing to itself:
public AnalysisView()
{
InitializeComponent();
DataContext = this;
}
However, the DataContext in a UserControl won't flow into your DataTemplate like your other objects. To make it work, you need to change your binding like so:
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.DisplayName, FallbackValue=ERROR!, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}" />
</DataTemplate>
Working with UserControls like this feels like a hack. So you might want to look for ways to design whatever app you're building a little differently.
Also, the code in your sample is not using INotifyPropertyChanged for properties, so you won't be getting any change notifications. You might want to learn more about the MVVM pattern.

C# WPF MVVM cannot show elements of list

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}" >

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.

WPF databinding to an other class

I've created a WPF UI. The following code exists in MainWindow.xaml.cs:
namespace AWPFProject
{
public partial class MainWindow : Window
{
private readonly ServiceLogic serviceLogic;
public MainWindow()
{
InitializeComponent();
serviceLogic = new ServiceLogic ();
}
}
}
Servicelogic is my central class. From there, methods or classes are called to handle stuff like database management.
Now, that ServiceLogic class has the values I'd like to bind to.
For example, I have a combobox where I can show my users. The XAML looks like this:
<ListBox Height="100" HorizontalAlignment="Left" Margin="6,44,0,0"
Name="listBox_detected" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=ServiceLogic.Users}" />
When I run the application, the list remains emtpy. What else do I need to do to get that information in my list?
You need to change a few things to make this work in your scenario:
Set the correct DataContext for your window:
public MainWindow()
{
InitializeComponent();
DataContext = new ServiceLogic();
}
Make sure that ServiceLogic has a public property named Users:
public List<User> Users { get; set; }
if you want to add/remove items to this List at runtime, consider using an ObservableCollection<T> as this will notify the UI of any changes automatically.
Update the binding logic of your xaml, so that you bind to the correct list. Also set the DisplayMemberPath property or add a template so that the objects are displayed nicely:
<ListBox ItemsSource="{Binding Path=Users}" DisplayMemberPath="Name"/>
or
<ListBox ItemsSource="{Binding Path=Users}">
<ListBox.ItemTemplate>
<DataTemplate>
<...your data template, like grid or stackpanel/>
</DataTemplate>
</ListBox.DataTemplate>
When using DisplayMemberPath, make sure the User-class has the correct properties. Add the following to User.cs:
public string Name
{
get { return _name; }
set { _name = value; }
}
Here ItemsSource="{Binding Path=ServiceLogic.Users}" you state that data has public property ServiceLogic
Second, you data is acquired through DataContext
Change constructor:
public MainWindow()
{
InitializeComponent();
serviceLogic = new ServiceLogic ();
DataContext = serviceLogic;
}
and change binding to this one:
<ListBox Height="100" HorizontalAlignment="Left" Margin="6,44,0,0"
Name="listBox_detected" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=Users}" />
In Binding I removed ServiceLogic because SL stands as data item. And Path - is the path of the property.
I think you need to set "DisplayMemberPath" property of ListBox.

Categories