Data Binding a List to an already bound object c# - 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.

Related

Combobox binding issue

My ComboBox does not get populated with data.
Class Employee set to public, has variables such as:
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
Code on UserControl:
public IEnumerable<csEmployee> employeeList;
public ObservableCollection<csEmployee> _employeeSorted { get; set; }
public ucAddClient()
{
InitializeComponent();
//Establish connection
var GetMyData = new DataAccess();
//Get data by procedure
employeeList = GetMyDataPV.ExecuteStoredProc<csEmployee>("procedure", new {KeyDate = Key_to_extract});
employeeList = employeeList.Where(record => record.EmployeeLevelID > 300);
_employeeSorted = new ObservableCollection<csEmployee>(employeeList.Where(record => record != null));
}
And WPF:
<ComboBox x:Name="cbAddManager"
Foreground="#FF4D648B"
FontSize="12"
IsEditable="True"
ItemsSource="{Binding _employeeSorted}"
DisplayMemberPath="FirstName"
PreviewKeyDown="cbAddManager_PreviewKeyDown"
Width="200">
<!--<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width ="50" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Width ="50" Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>-->
</ComboBox>
Do you have any idea, why ComboBoxis not populated? When I do this in code (I add it in user control class) it gets data needed.
Im not sure if Im binding it correctly?
That is because you assign a new instance of a collection to your _employeeSorted property after InitializeComponent. At that time, the binding is already set up and does not get notified that you have updated the property from null, because you do not implement INotifyPropertyChanged.
There are multiple ways to solve the issue:
Initialize the collection before InitializeComponent and work on this same collection if you intend to change it, using Clear and Add instead of creating a new instance on changes.
Implement the INotifyPropertyChanged interface and use it to notify changes to your property so that the bindings are updated the the changes are applied in the user interface, e.g.:
public partial class MyUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<csEmployee> _employeeSortedField;
public ObservableCollection<csEmployee> _employeeSorted
{
get => _employeeSortedField;
set
{
if (_employeeSortedField == value)
return;
_employeeSortedField = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Expose a depenedency property for the collection instead and bind it to a collection in your view model that is passed as data context of the UserControl, thus moving the data access out it and separating the view from the business logic and data (recommended, see below MVVM).
Another issue might be that you do not set your data context to the UserControl itself in XAML (which is not recommened by the way, although it might solve your issue). In this case, the binding is unable to resolve the property at runtime (a binding error will be shown in the output window).
<UserControl x:Class="YourProject.YourControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
As a note, it seems that you mix your business logic with your UserControl (view). Leverage the MVVM design pattern to create view models and seprate both concerns instead. Furthermore, if you set the data context of your UserControl to itself, you break data context inheritance.

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

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"

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.

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