Binding not working properly in treeview WPF - c#

I have an Employee Class as shown below:
public class Employee : INotifyPropertyChanged
{
public Employee()
{
_subEmployee = new ObservableCollection<Employee>();
}
public string Name { get; set; }
public ObservableCollection<Employee> SubEmployee
{
get { return _subEmployee; }
set
{
_subEmployee = value;
NotifiyPropertyChanged("SubEmployee");
}
}
ObservableCollection<Employee> _subEmployee;
public event PropertyChangedEventHandler PropertyChanged;
void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
I am creating a collection of employee class in Main window constructor
and adding it to an observable collection of employee as shown below:
public partial class MainWindow : Window
{
public ObservableCollection<Employee> Emp { get; private set; }
public MainWindow()
{
InitializeComponent();
Emp = new ObservableCollection<Employee>();
Emp.Add(new Employee(){Name = "Anuj"});
Emp.Add(new Employee() { Name = "Deepak" });
Emp.Add(new Employee() { Name = "Aarti" });
Emp[0].SubEmployee.Add(new Employee(){Name = "Tonu"});
Emp[0].SubEmployee.Add(new Employee() { Name = "Monu" });
Emp[0].SubEmployee.Add(new Employee() { Name = "Sonu" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Harsh" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Rahul" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Sachin" });
this.DataContext = this;
}
}
I have set the DataContext as self.
Now, in xaml file I have created a hierarchical template of treeview and binded data as shown below:
<Window x:Class="WpfApplication3.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>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubEmployee}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Now when I keep, TreeView ItemsSource="{Binding Emp}" , binding works properly
and I can see the tree view structure after running the code.
However when I keep TreeView ItemsSource="{Binding}", I see no result after running the code.
To my understanding, keeping ItemSource = "{Binding}" means I am binding to the evaluated value of the current datacontext.
As my datacontext is set to self, ItemSource = "{Binding}" should mean I am binding to the only property of DataContext i.e. Emp and I should get proper result.
Please help me in understanding the problem I am getting in keeping binding as
ItemSource = "{Binding}".

"To my understanding, keeping ItemSource = "{Binding}" means I am binding to the evaluated value of the current datacontext."
Correct AND that is the issue. ItemsSource expects the binding source to be of type IEnumerable but you are binding to Window.
"...should mean I am binding to the only property of DataContext i.e. Emp and I should get proper result."
No. No such "single property" assumption exists in WPFs binding conventions.
Change...
this.DataContext = this;
To...
this.DataContext = Emp;
Or, alternatively, change binding in XAML and specify the correct member on the DataContext to bind to using Path...
<TreeView ItemsSource="{Binding Path=Emp}">

Related

how to databind the datagrid Mvvm

Kinda stuck on this mvvm binding the datagrid with my viewmodel im making a ObservableCollection and then pass the dummy data into the gridview but im not sure how to do it I started doing it without mvvm and had no problem now im kinda stuck and unsure how to do it
this is my View - UserView.xaml
Grid>
<DataGrid x:Name="dt_Users" ItemsSource="{Binding UserItems}" HorizontalAlignment="Left" Height="100" Margin="161,222,0,0" VerticalAlignment="Top" Width="585" />
</Grid>
My viewmodel - userviewmodel.cs
public class UserViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
ObservableCollection<User> UserItems = new ObservableCollection<User>();
private void fetchgrid()
{
UserItems.Add(new User { firstname = "h", lastname = "h" });
UserItems.Add(new User { firstname = "h", lastname = "h" });
}
}
and my model - user.cs
public class User
{
public string firstname { get; set; }
public string lastname { get; set; }
}
what am I overseeing?
Make sure to assign an instance of UserViewModel to the DataContext of your view.
Either in XAML, for example:
<Window>
<Window.DataContext>
<UserViewModel />
</Window.DataContext>
<Grid>
<DataGrid ... />
</Grid>
</Window>
or in code-behind, for example in the constructor:
public MainWindow()
{
InitializeComponent();
this.DataContext = new UserViewModel();
}
Alternatively define the instance of the view model in App.xaml and use {StaticResource} to set the DataContext in XAML.
There are many ways to do it.
You can only bind to public properties so the first thing to do is to change the UserItems field into a property in the view model:
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> UserItems { get; } = new ObservableCollection<User>();
...
}
Then you also need to set the DataContext of the view to an instance of your view model class. The easiest way to do this is to add the following line to the constructor of the view in UserView.xaml.cs:
public UserView()
{
InitializeComponent();
DataContext = new UserViewModel(); //<-- add this
}

How to update item in UWP ListView with AdvancedCollectionView source

I am using AdvanceCollectionView from Windows Community Toolkit as a source for a XAML ListView, to allow sorting and filtering. I am having problems with updating the ListView.
To replicate the issue, I've created a simple Person class. In MainPage XAML I have a ListView MyXAMLList and a Button EditButton. In the MainPage code, I have an ObservableCollection<Person> MyPersonList and AdvancedCollectionView MyPersonACV. In Page_Loaded event I add a person to the list and use AdvancedCollectionView as a source for the list view:
Person p = new Person
{
Name = "John",
Age = 35
};
MyPersonList.Add(p);
MyPersonACV = new AdvancedCollectionView(MyPersonList, true);
MyXAMLList.ItemsSource = MyPersonACV;
This works and I can see John in the list.
In the EditButton code I try to update the item on the list but this isn't working. Both the ObservableCollection and the AdvancedCollectionView are updated, but the XAML list is still displaying the old name "John" instead of "Mary".
MyPersonList[0].Name = "Mary";
Debug.WriteLine(MyPersonList[0].ToString());
Debug.WriteLine(MyPersonACV[0].ToString());
I've tried updating the MyXAMLList.SelectedItem instead, but the same result:
Person p = (Person)MyXAMLList.SelectedItem;
p.Name = "Mary";
I've also tried adding MyPersonACV.Refresh(); but doesn't help.
What am I doing wrong? How can I update an item in the list?
Full code below
Person class:
class Person
{
public string Name {get; set;}
public int Age { get; set; }
public override string ToString()
{
return Name;
}
}
MainPage XAML:
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Loaded="Page_Loaded">
<Grid>
<StackPanel Orientation="Vertical">
<ListView Height="Auto" Width="Auto" x:Name="MyXAMLList" SelectionMode="Single" IsItemClickEnabled="True"/>
<StackPanel Orientation="Horizontal">
<Button x:Name="EditButton" Content="Edit" Click="EditButton_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Page>
MainPage cs:
using Microsoft.Toolkit.Uwp.UI;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MainPage : Page
{
private ObservableCollection<Person> MyPersonList = new ObservableCollection<Person>();
private AdvancedCollectionView MyPersonACV;
public MainPage()
{
this.InitializeComponent();
}
private void EditButton_Click(object sender, RoutedEventArgs e)
{
//Change name
MyPersonList[0].Name = "Mary";
//Person p = (Person)MyXAMLList.SelectedItem;
//p.Name = "Mary";
Debug.WriteLine(MyPersonList[0].ToString());
Debug.WriteLine(MyPersonACV[0].ToString());
//MyPersonACV.Refresh();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
//create person
Person p = new Person
{
Name = "John",
Age = 35
};
//add to list
MyPersonList.Add(p);
//set up ListView source
MyPersonACV = new AdvancedCollectionView(MyPersonList, true);
MyXAMLList.ItemsSource = MyPersonACV;
}
}
}
I noticed you override the ToString() method to display each item of ListView. When you update the Name property, even if the value of Name property has updated, since there is no binding relationship between Name property and ListViewItem, and the ToString() method isn't triggered when you update data, the UI isn't updated. It'sbetter to customize the appearance of items using DataTemplate, binding the Name property to the element(e.g. TetxBlock) and implement INotifyPropertyChanged interface. In this case, when the Name proeprty changes, it will provide change notifications to the binding and the UI will update. For exmaple:
.xaml:
<ListView Height="Auto" Width="Auto" x:Name="MyXAMLList" SelectionMode="Single" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
.cs:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string name { get; set; }
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged();
}
}
public int Age { get; set; }
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void EditButton_Click(object sender, RoutedEventArgs e)
{
//Change name
MyPersonList[0].Name = "Mary";
}

ListView not updated after adding new items to List

I have a ListView bounded to a List of a class I created. When doing an operating, it was supposed to add/remove items from the list, but my ListView wasn't updated even though I used INotifyPropertyChanged.
If I use ObservableCollection, it works but I need to have the list sorted, and ObservableCollection doesn't do sorting for WPF4.0 :(
Any way I can make the List binding work? Why didn't it work even though I used INotifyPropertyChanged?
XAML:
<ListView BorderThickness="0" ItemsSource="{Binding SelectedValues, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Padding="5">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Value}"></GridViewColumn>
VM:
private List<CheckBoxItem> _selectedValues = new List<CheckBoxItem>();
public List<CheckBoxItem> SelectedValues
{
get { return _selectedValues; }
set
{
_selectedValues = value;
OnPropertyChanged();
}
}
private void UnselectValueCommandExecute(CheckBoxItem value)
{
value.IsSelected = false;
SelectedValues.Remove(value);
//OnPropertyChanged("SelectedValues");
OnPropertyChanged("IsAllFilteredValuesSelected");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
The CheckBoxItem class contains 2 properties, Value and IsChecked, which I don't think is relevant here.
So basically, I have a button which uses the UnselectValueCommandExecute to remove items from the list, and I should see the list updated in the UI, but I'm not.
When I debug, I can see the SelectedValues list updated, but not my UI.
You need a CollectionViewSource in your UI.
The XAML:
<Window x:Class="WavTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource Source="{Binding TestSource}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Order"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Description"/>
</Window>
The code behind:
namespace WavTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
this.DataContext = vm;
vm.TestSource.Add(new TestItem { Description="Zero", Order=0 });
}
}
public class ViewModel
{
public ObservableCollection<TestItem> TestSource { get; set; }
public ViewModel()
{
TestSource = new ObservableCollection<TestItem>();
TestSource.Add(new TestItem { Description = "Second", Order = 2 });
TestSource.Add(new TestItem { Description = "Third", Order = 3 });
TestSource.Add(new TestItem { Description = "First", Order = 1 });
}
}
public class TestItem
{
public int Order { get; set; }
public string Description { get; set; }
}
}
Explanation:
The ObservableCollection raises the PropertyChanged event as you expect, but you cannot sort it.
So, you need the CollectionView to sort it and bind the sorted collection to you ListView/ListBox.
As you can see, adding an element after the DataContext initialization affects the UI sorting the last added item ("Zero") correctly.
You need to use ObservableCollection because this raises a collection changed event which your wpf ListView will pick up on.
How about doing
Public ObservableCollection<object> MyList
{
get
{
return new ObservableCollection<object>(MySortedList);
}
}
and then whenever you change your sorted list raise a property changed event for MyList.
This obviously depends how you would like to sort your list as it might be possible to sort the ObservableCollection your question needs more info.

What do I need to further qualify the DataContext for a binding?

The files I have created and will be referring to in this question are:
TechnicainSelectionView.xaml
TechnicianSelectionView.cs
TechnicianSelectionViewModel.cs
Technician.cs (Code First Entity)
I have the following xaml in my TechnicanSelectionView.xaml
<UserControl xmlns etc... here"
d:DesignHeight="48" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="Select a Technican to run the test" FontWeight="Bold"></Label>
<ComboBox ItemsSource="{Binding Technicians, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
The Technicians property to which the ItemSource is set to bind to states that it Cannot resolve Technicians due to an unknown DataContext.
So if we look to my TechnicianSelectionView.cs code-behind...
public partial class TechnicianSelectionView : UserControl
{
public TechnicianSelectionViewModel ViewModel { get; private set; }
public TechnicianSelectionView()
{
InitializeComponent();
Technician.GenerateSeedData();
ViewModel = new TechnicianSelectionViewModel();
DataContext = ViewModel;
}
}
... we see that I am setting the view's DataContext to my TechnicianSelectionViewModel ...
public class TechnicianSelectionViewModel : ViewModelBase
{
public ObservableCollection<Technician> Technicians { get; set; }
public TechnicianSelectionViewModel()
{
Technicians = new ObservableCollection<Technician>();
}
public bool IsLoaded { get; private set; }
public void LoadTechnicians()
{
List<Technician> technicians;
using (var db = new TestContext())
{
var query = from tech in db.Technicians
select tech;
foreach (var technician in query)
{
Technicians.Add(technician);
}
}
IsLoaded = true;
}
}
Techicians is a property on my ViewModel...
So having already set the DataContext for the view, why can't it resolve Technicians on the ViewModel as the DataContext/property it is going to bind to?
EDIT:
As per a concern in a comment below. This is a design time problem and not compile time. I should have indicated this at the start.
You need to specify the type of data context in the xaml to get design-time support. Even though you assigned the data context in code-behind, the designer is not going to recognize that.
Try putting the following in your xaml:
d:DataContext="{d:DesignInstance vm:TechnicianSelectionViewModel}"
See this link for more details.
In my Xamarin Forms Xaml file I used the following lines in the header (ContentPage tag) and it worked perfectly as I wanted.
Basically now
the intellisense shows the fields in the binding
my Resharper is able to rename the binding in the Xaml file if I refactor the name of the property
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:YourApplicationName.ViewModels;assembly=YourApplicationName"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type vm:CurrentPageViewModel}}"

How do you perform Binding with a DataGridView in WPF?

I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}

Categories