Wpf two way binding in listbox - c#

trying to do two way binding in the listbox unfortunately does not work.
Here's the xaml code:
<ListBox Margin="19,0,21,149" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here's the viewmodel and person code:
public class ViewModel
{
public ObservableCollection<Person> Items
{
get
{
return new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person : INotifyPropertyChanged
{
public string _name;
public int Age { get; set; }
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and MainWindow:
public MainWindow()
{
InitializeComponent();
model = new ViewModel();
this.DataContext = model;
}
I do not know what is wrong, trying to bind the property "Name", but it does not work. Please, let me know what may be wrong.

It was enough to change:
public class ViewModel
{
ObservableCollection<Person> _items = new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
public ObservableCollection<Person> Items
{
get
{
return _items;
}
}
}

Related

CollectinView Grouping doesnt show all Items in the last Group

im working on an Mobile App for Android in Xamarin Forms, and have one problem. I have a ObservableCollection that i fill with the following Model.
private int _category_ID;
public int Category_ID
{
get
{
return _category_ID;
}
set
{
_category_ID = value;
OnPropertyChanged("_category_ID");
}
}
private string _category_Name;
public string Category_Name
{
get
{
return _category_Name;
}
set
{
_category_Name = value;
OnPropertyChanged("_category_Name");
}
}
private string _category_Description;
public string Category_Description
{
get
{
return _category_Description;
}
set
{
_category_Description = value;
OnPropertyChanged("_category_Description");
}
}
public CategoryModel(string name, List<ProductModel> products) : base(products)
{
Category_Name = name;
}
That works fine and all Categorys and Items shows right when i Debugging in the ObservableCollection. Example: Categroy 1 = 2 Items Category 2 = 3 Items Category 3 = 4 Items.
That works.
But my problem is, that when i use a CollectionView like this
<CollectionView ItemsSource="{Binding ObservCollectionCategory}" IsGrouped="True">
<CollectionView.ItemTemplate>
<DataTemplate>
<ScrollView>
<Grid Padding="10">
<StackLayout BackgroundColor="Blue">
<Label Text="{Binding Product_Name}"/>
<Label Text="{Binding Product_Description}"/>
</StackLayout>
</Grid>
</ScrollView>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate >
<Label Text="{Binding Category_Name}"/>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
The View shows me only the first item of the last categroy and the other ignores. All other Categorys shows right.
When i create a blank Categroy at the end of the ObservableCollection then all Items, from the last category, shows in the View. But i have an empty Group then at the end.
I have try to show me the count in the header, and the count the right one (4).
You could try the code below.
Model:
public class ProductModel : INotifyPropertyChanged
{
private string _product_Name;
public string Product_Name
{
get
{
return _product_Name;
}
set
{
_product_Name = value;
OnPropertyChanged("Product_Name");
}
}
private string _product_Description;
public string Product_Description
{
get
{
return _product_Description;
}
set
{
_product_Description = value;
OnPropertyChanged("Product_Description");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CategoryModel : List<ProductModel>, INotifyPropertyChanged
{
private string _category_Name;
public event PropertyChangedEventHandler PropertyChanged;
public string Category_Name
{
get
{
return _category_Name;
}
set
{
_category_Name = value;
OnPropertyChanged("Category_Name");
}
}
public CategoryModel(string name, List<ProductModel> diary) : base(diary)
{
Category_Name = name;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Code behind:
public ObservableCollection<CategoryModel> ObservCollectionCategory { get; set; } = new ObservableCollection<CategoryModel>();
public Page18()
{
InitializeComponent();
ObservCollectionCategory.Add(new CategoryModel("Categroy 1", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
}));
ObservCollectionCategory.Add(new CategoryModel("Categroy 2", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
new ProductModel(){ Product_Name="item3", Product_Description="Description3"},
}));
ObservCollectionCategory.Add(new CategoryModel("Categroy 3", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
new ProductModel(){ Product_Name="item3", Product_Description="Description3"},
new ProductModel(){ Product_Name="item4", Product_Description="Description4"},
}));
this.BindingContext = this;
}
Output:

after implementing iNotifypropertychanged in ViewModel SelectedItem is not working on listview

Hello I am using MVVM in Xamarin Forms. I am trying to Bind my Listview's SeletedItem to my ViewModel. I binded it and worked fine. but when i have implemented INotifyPropertyChanged to update the view for some other component it stopped working.
I want it to work even INotifyPropertyChanged is implemented in my ViewModel. I am figuring out the problem why its happened. I searched on internet and Xamarin's documentation and could not find the reason.
My View
<ListView ItemsSource="{Binding PersonsList}"
CachingStrategy="RecycleElement"
HasUnevenRows="True"
SelectionMode="None"
SelectedItem="{Binding SelectedPerson}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="5">
<Label Text="{Binding FullName}"
FontSize="Medium"
TextColor="Orange"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel()
{
PersonsList = new ObservableCollection<User>
{
new User(){ UserId = 1, FullName = "John" },
new User(){ UserId = 2, FullName = "Alex" },
new User(){ UserId = 3, FullName = "Ellen" },
new User(){ UserId = 4, FullName = "Grace" }
};
}
public ObservableCollection<User> PersonsList { get; set; }
private User _selectedPerson { get; set; }
public User SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson != value)
{
_selectedPerson = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Model
public class User
{
public int UserId { get; set; }
public string FullName { get; set; }
}
Hope to get some useful advice.
Change your SelectedPerson property to call OnPropertyChanged().
private User _selectedPerson { get; set; }
public User SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson != value)
{
_selectedPerson = value;
OnPropertyChanged();
}
}
}

ComboBox SelectedItem not getting set when using custom ItemTemplate

I have a ComboBox with custom ItemTemplate, it has a CheckBox which is binding to a ViewModel of that page.
What I am trying to do is to set ComboBox item selected to the first checked value whenever ComboBox is closed, but the value is not getting set accordingly.
Is there anything that I am missing?
Xaml:
<ComboBox x:Name="myComboBox" ItemsSource="{Binding ComboBoxList}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="150">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="{Binding}" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsDropDownOpen, ElementName=myComboBox}" ComparisonCondition="NotEqual" Value="True">
<core:InvokeCommandAction Command="{Binding DropDownClosedCommand}"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private string header;
public string Header
{
get { return header; }
set
{
header = value;
RaisePropertyChange(nameof(Header));
}
}
private Person selectedPerson;
public Person SelectedPerson
{
get { return selectedPerson; }
set { selectedPerson = value; RaisePropertyChange(nameof(SelectedPerson)); }
}
private ObservableCollection<Person> comboBoxList;
public ObservableCollection<Person> ComboBoxList
{
get { return comboBoxList; }
set { comboBoxList = value; }
}
public DelegateCommand DropDownClosedCommand { get; set; }
public MainViewModel()
{
Header = "My Header";
ComboBoxList = new ObservableCollection<Person> {
new Person() { Name = "Person 1", IsChecked = false },
new Person() { Name = "Person 2", IsChecked = false },
new Person() { Name = "Person 3", IsChecked = false },
new Person() { Name = "Person 4", IsChecked = false }
};
DropDownClosedCommand = new DelegateCommand(OnDropDownClosed);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnDropDownClosed(object e)
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
}
}
public class Person
{
public string Name { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
Finally, I figured out the issue that was preventing ComboBox to set SelectedItem. It was because SelectedPerson was not getting set on UI thread, so I wrap that code in dispatcher and it's working as expected.
Code:
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
});

WPF - MVVM - ComboBox SelectedItem

I have ViewModel(implemented INotifyPropertyChanged) in the background and class Category which has only one property of type string. My ComboBox SelectedItem is bind to an instance of a Category. When i change the value of instance, SelectedItem is not being updated and Combobox is not changed.
EDIT: code
Combobox:
<ComboBox x:Name="categoryComboBox" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2"
Margin="10" ItemsSource="{Binding Categories}"
DisplayMemberPath="Name" SelectedValue="{Binding NodeCategory, Mode=TwoWay}"/>
Property:
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
and what I am trying is: when I set
NodeCategory = some_list_of_other_objects.Category;
to have that item selected in Combobox with appropriate DisplayMemberPath
The category you are setting in this line -
NodeCategory = some_list_of_other_objects.Category;
and one present in your Categories collection(ItemsSource="{Binding Categories}") should be referring to same object. If they are not then SelectedItem won't work.
Solution 1 -
You can also try to use SelectedValuePath like this -
<ComboBox x:Name="categoryComboBox"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
and in code you can do something like this -
private string _NodeCategory;
public string NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
and set selected item like this -
NodeCategory = some_list_of_other_objects.Category.Name;
and use selected value like this -
Category selectedCategory =
some_list_of_other_objects.FirstOrDefault(cat=> cat.Name == NodeCategory);
or
Category selectedCategory =
Categories.FirstOrDefault(cat=> cat.Name == NodeCategory);
Solution 2 -
Another possible solution can be -
NodeCategory =
Categories.FirstOrDefault(cat=> cat.Name == some_list_of_other_objects.Category.Name);
this way your NodeCategory property will have the reference of an object in Categories collection and SelectedItem will work.
Your XAML needs a couple of modifications but I think the real problem is with the code you have posted which I don't think is telling the full story.
For starters, your combobox ItemSource is bound to a property called Categories but you do not show how this property is coded or how your NodeCategory property is initially synced with the item.
Try using the following code and you will see that the selected item is kept in sync as the user changes the value in the combobox.
XAML
<Window x:Class="WpfApplication1.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">
<StackPanel>
<ComboBox x:Name="categoryComboBox"
Grid.Column="1"
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="10"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedItem="{Binding NodeCategory}" />
<Label Content="{Binding NodeCategory.Name}" />
</StackPanel>
Code-behind
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Category> _categories = new ObservableCollection<Category>
{
new Category { Name = "Squares"},
new Category { Name = "Triangles"},
new Category { Name = "Circles"},
};
public MainWindow()
{
InitializeComponent();
NodeCategory = _categories.First();
this.DataContext = this;
}
public IEnumerable<Category> Categories
{
get { return _categories; }
}
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
From my little example:
Note: This is setting just a string (or a category from another list), but the basics should be same here:
Basically this is done:
private void button1_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ComboBoxSampleViewModel).SelectCategory("Categorie 4");
}
Here is my XAML:
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="76,59,0,0"
Name="comboBox1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding List.Categories}"
DisplayMemberPath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
<Button Content="Button" Height="27" HorizontalAlignment="Left"
Margin="76,110,0,0" Name="button1" VerticalAlignment="Top"
Width="120" Click="button1_Click" />
</Grid>
and in the ViewModel of the Window
class ComboBoxSampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public CategoryList List { get; set; }
public ComboBoxSampleViewModel()
{
this.List = new CategoryList();
NodeCategory = List.Selected;
}
private ComboBoxSampleItemViewModel nodeCategory;
public ComboBoxSampleItemViewModel NodeCategory
{
get
{
return nodeCategory;
}
set
{
nodeCategory = value;
NotifyPropertyChanged("NodeCategory");
}
}
internal void SelectCategory(string p)
{
this.List.SelectByName(p);
this.NodeCategory = this.List.Selected;
}
}
With the help of this little class:
public class CategoryList
{
public ObservableCollection<ComboBoxSampleItemViewModel> Categories { get; set; }
public ComboBoxSampleItemViewModel Selected { get; set; }
public CategoryList()
{
Categories = new ObservableCollection<ComboBoxSampleItemViewModel>();
var cat1 = new ComboBoxSampleItemViewModel() { Name = "Categorie 1" };
var cat2 = new ComboBoxSampleItemViewModel() { Name = "Categorie 2" };
var cat3 = new ComboBoxSampleItemViewModel() { Name = "Categorie 3" };
var cat4 = new ComboBoxSampleItemViewModel() { Name = "Categorie 4" };
Categories.Add(cat1);
Categories.Add(cat2);
Categories.Add(cat3);
Categories.Add(cat4);
this.Selected = cat3;
}
internal void SelectByName(string p)
{
this.Selected = this.Categories.Where(s => s.Name.Equals(p)).FirstOrDefault();
}
}
And this Item ViewModel
public class ComboBoxSampleItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
If Combobox is bound to object class of the View Model, while the SelectionBoxItem of the sender object (in the SelectionChanged in code behind) has not that type, it means it's still loading.
ComboBox combo = sender as ComboBox;
if (combo.SelectionBoxItem.GetType() == typeof(BindedClass))
{
// Not loading
}

Why INotifyPropertyChanged's event PropertyChangedEventHandler is always null?

This is my sample code, why PropertyChangedEventHandler property is null?
the list is bounded to Listbox which should subscribe to the event. Shouldn't it?
public class Data<T> : INotifyPropertyChanged where T : class
{
T _data;
public T MyData
{
get { return _data; }
set
{
_data = value;
this.OnPropertyChanged("MyData");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var h = this.PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(property));
}
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public Window1()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
list.Add(new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } });
list.Add(new Data<Person> { MyData = new Person { Name = "Tom", Age = 33 } });
this.DataContext = list;
}
<Grid>
<ListBox Name="listbox1"
ItemsSource="{Binding}"
Style="{DynamicResource lStyle}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Label Width="100" Content="{Binding Path=MyData.Name}"></Label>
<Label Width="100" Content="{Binding Path=MyData.Age}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Probably because the ListBox hasn't had a chance to render and start listening for change events yet.
When I tested the following PropertyChanged was not null during the change inside listbox1_MouseLeftButtonDown.
Data<Person> p;
public MainWindow()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
this.p = new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } };
list.Add(this.p);
this.DataContext = list;
}
private void listbox1_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
p.DataContext = null;
}
In your provided code, the PropertyChanged event handler will be subscribed to. Why do you think that is not the case?
Changes like this will update the listbox correctly:
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData = new Person() { Name = "Bob", Age = 12 };
Remember that your Data class don't support property notify on individual properties,
so the following changes will not update the listbox.
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData.Name = "Bob";
list[0].MyData.Age = 12;

Categories