Why INotifyPropertyChanged's event PropertyChangedEventHandler is always null? - c#

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;

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:

Wpf two way binding in listbox

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

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);
});

How to bind Selected Items in MVVM

I am using WPF, MVVM and DevExpress GridControl. There are two panels in my MainWindow.xaml. Panle1 has Grid and Panel2 has Textbox. I want that if i select an item from Grid in Panel1 it's name should display in that Panle2 Textbox. Iwrote Code but it is not working. Can you Please help me to solve this?
*In NameModel From Models Folder I wrote:*
private NameModelClass _selectedCustomer;
public NameModelClass SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
LastName = value.LastName;
OnPropertyChanged("SelectedCustomer");
}
}
public List<Namess> ListPerson { get; set; }
void CreateList()
{
ListPerson = new List<Namess>();
for (int i = 0; i < 10; i++)
{
ListPerson.Add(new Namess(i));
}
}
public class Namess
{
public Namess(int i)
{
FirstName = "FirstName" + i;
LastName = "LastName" + i;
Age = i * 10;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}
In MianWindow.xaml I wrote:
<dxdo:LayoutPanel Caption="Grid" Caption="Panel1" x:Name="abc1">
<Grid>
<dxg:GridControl x:Name="grid" Height="233" ItemsSource="{Binding ListPerson}" AutoGenerateColumns="AddNew" HorizontalAlignment="Left" VerticalAlignment="Top" SelectedItem="{Binding SelectedNames}">
<dxg:GridControl.View>
<dxg:TableView ShowTotalSummary="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Panel2" x:Name="abc1">
<TextBox Width="166" Background="White" Height="33" HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="{Binding Path=LastName}"/>
</dxdo:LayoutPanel>
I am new to MVVM and c#. I f my query is not clear to you please ask me. Thank you.
I do it this way:
private Namess _selectedCustomer;
public Namess SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
OnPropertyChanged("SelectedCustomer");
}
}
public List<Namess> ListPerson { get; set; }
void CreateList()
{
ListPerson = new List<Namess>();
for (int i = 0; i < 10; i++)
{
ListPerson.Add(new Namess(i));
}
}
public class Namess : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public Namess(int i)
{
FirstName = "FirstName" + i;
LastName = "LastName" + i;
Age = i * 10;
}
public string FirstName { get; set; }
private string _lastName;
public string LastName
{
get
{
return _lastName;
}
set
{
if(value==_lastName)
return;
_lastName=value;
OnPropertyChanged("LastName");
}
}
public int Age { get; set; }
}
}
and in your view:
<dxdo:LayoutPanel Caption="Grid" Caption="Panel1" x:Name="abc1">
<Grid>
<dxg:GridControl x:Name="grid" Height="233" ItemsSource="{Binding ListPerson}" AutoGenerateColumns="AddNew" HorizontalAlignment="Left" VerticalAlignment="Top" SelectedItem="{Binding SelectedNames,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<dxg:GridControl.View>
<dxg:TableView ShowTotalSummary="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Panel2" x:Name="abc1">
<TextBox Width="166" Background="White" Height="33" HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="{Binding Path=SelectedCustomer.LastName,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
</dxdo:LayoutPanel>
Bsically I changed the type of SelectedCustomer to one of the collection of items. In the view you can set the binding of your TextBox directly to a property of the SelectedCustomer.
It looks like you forgot to raise the INPC (INotifyPropertyChanged) event for the "LastName" string.
So try this (changed is in the setter below):
public NameModelClass SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
LastName = value.LastName;
OnPropertyChanged("SelectedCustomer");
OnPropertyChanged("LastName"); //<-- new
}
}
}
You have to send out INPCs so that the binding knows to update to the new value. The displayed binding won't "grab" the new value for LastName unles you raise that event.
Have you tried:
SelectedItem="{Binding SelectedNames, Mode=TwoWay}"
After looking at it more, your main Namess Class could do with implementing INotifyPropertyChanged
With each property raising the property changed event when it ahem changes.
Also using an observable collection so when you add and remove items it also raises changes.
That way, the notification change system receives the notify of property changes to change the view accordingly via bindings.

Combobox with Checkboxes

I am trying to make a combobox that shows check boxes, and selecting each checkbox will update the text of the combobox to display everything that has been checked. For some strange reason it works only on the first item in the checkbox and it has completely baffled me as to why. I have a dummy project that is pretty small that demonstrates it...
public partial class MainWindow : Window
{
public ObservableCollection<DataObject> Collection { get; set; }
#region Private Methods
public MainWindow()
{
InitializeComponent();
Collection = new ObservableCollection<DataObject>();
Collection.Add(new DataObject { Name = "item1" });
Collection.Add(new DataObject { Name = "item2" });
Collection.Add(new DataObject { Name = "item3" });
Collection.Add(new DataObject { Name = "item4" });
Collection.Add(new DataObject { Name = "item5" });
this.DataContext = Collection;
}
#endregion
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = sender as CheckBox;
DataObject data = chk.DataContext as DataObject;
if ((bool)chk.IsChecked)
data.CboItems.Add(data.Name);
else if (data.CboItems.Contains(data.Name))
data.CboItems.Remove(data.Name);
}
}
public class DataObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
private string cbotext;
public string CBOText {
get
{
return cbotext;
}
set
{
cbotext = value;
FirePropertyChanged("CBOText");
}
}
public ObservableCollection<string> CboItems { get; set; }
private void FirePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public DataObject()
{
CboItems = new ObservableCollection<string>();
CboItems.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CboItems_CollectionChanged);
}
void CboItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
string text = string.Empty;
foreach (string item in CboItems)
{
if (text == string.Empty)
text = item;
else
text += ", " + item;
}
CBOText = text;
}
}
and the Xaml...
<ComboBox Text="{Binding CBOText}" Width="150" Height="30" ItemsSource="{Binding}" x:Name="cbo" HorizontalContentAlignment="Stretch" IsEditable="True" Margin="12,12,342,270">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I can see the event CBOText string getting set correctly and firing PropertyChanged, but the combobox does not reflect it unless it's the first item. Quite weird, any ideas?
Your bindings are setup incorrectly. Try something like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<DataObject> Collection { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
#region Private Methods
public MainWindow()
{
InitializeComponent();
Collection = new ObservableCollection<DataObject>();
Collection.Add(new DataObject { Name = "item1" });
Collection.Add(new DataObject { Name = "item2" });
Collection.Add(new DataObject { Name = "item3" });
Collection.Add(new DataObject { Name = "item4" });
Collection.Add(new DataObject { Name = "item5" });
this.DataContext = this;
}
#endregion
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = sender as CheckBox;
DataObject data = chk.DataContext as DataObject;
string combinedText = string.Empty;
foreach (var item in this.Collection)
{
if (item.IsChecked.HasValue && item.IsChecked.Value)
{
if (combinedText == string.Empty)
combinedText = item.Name;
else
combinedText += ", " + item.Name;
}
}
CboText = combinedText;
}
private string _cboCombinedText = "" ;
public string CboText
{
get
{
return this._cboCombinedText; }
set
{
this._cboCombinedText = value;
PropertyChanged(this, new PropertyChangedEventArgs("CboText"));
}
}
public class DataObject
{
private bool? _isChecked = false;
public string Name { get; set; }
public bool? IsChecked { get { return _isChecked; } set { _isChecked = value; } }
}
}
And xaml:
<ComboBox Text="{Binding CboText}" Width="150" Height="30" ItemsSource="{Binding Path=Collection}" x:Name="cbo" HorizontalContentAlignment="Stretch" IsEditable="True" Margin="12,12,342,270">
<ComboBox.ItemTemplate >
<DataTemplate >
<CheckBox Content="{Binding Name}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Categories