observable collection not getting updated on UI change - wpf

I am trying to bind an observable collection to a user control but it is not getting updated on user change but it is getting updated when the user control is changed through code. Following is an example i tried. It might be a bit long but it is working so you can copy and paste the code as it is.
Please see my question at the end of the post.
--Customer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TestMVVM
{
class Customer : INotifyPropertyChanged
{
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
RaisePropertyChanged("LastName");
}
}
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(property));
}
}
#endregion
}
}
--UCTextBox.xaml
<UserControl x:Class="TestMVVM.UCTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="40" Width="200">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120" />
</Grid>
--UCTextBox.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TestMVVM
{
///
/// Interaction logic for UCTextBox.xaml
///
public partial class UCTextBox : UserControl, INotifyPropertyChanged
{
public UCTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UCTextBox),
new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(textChangedCallBack)));
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UCTextBox pasTextBox = (UCTextBox)property;
pasTextBox.txtTextControl.Text = (string)args.NewValue;
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
NotifyPropertyChanged("Text");
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
-- Window1.xaml
<Window x:Class="TestMVVM.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMVVM"
Title="Window1" Height="300" Width="300">
<Grid>
<local:UCTextBox x:Name="txtUC" />
<Button Height="23" HorizontalAlignment="Left" Margin="39,0,0,82"
Name="btnUpdate" VerticalAlignment="Bottom" Width="75" Click="btnUpdate_Click">Update</Button>
<Button Height="23" Margin="120,0,83,82" Name="btnChange" VerticalAlignment="Bottom" Click="btnChange_Click">Change</Button>
</Grid>
-- Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace TestMVVM
{
///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : Window
{
CustomerHeaderViewModel customerHeaderViewModel = null;
public Window1()
{
InitializeComponent();
customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
txtUC.DataContext = customerHeaderViewModel.Customers[0];
Binding binding = new Binding();
binding.Source = customerHeaderViewModel.Customers[0];
binding.Path = new System.Windows.PropertyPath("FirstName");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
txtUC.SetBinding(UCTextBox.TextProperty, binding);
}
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(customerHeaderViewModel.Customers[0].FirstName);
}
private void btnChange_Click(object sender, RoutedEventArgs e)
{
txtUC.Text = "Tom";
}
}
class CustomerHeaderViewModel
{
public ObservableCollection Customers { get; set; }
public void LoadCustomers()
{
ObservableCollection customers = new ObservableCollection();
customers.Add(new Customer { FirstName = "Jim", LastName = "Smith", NumberOfContracts = 23 });
Customers = customers;
}
}
}
When i run the Window1.xaml my user control shows the data as "Jim". Now when i change the text to say "John" and click on Update, the messagebox still shows "Jim" that means the observable collection is not getting updated. When i click on Change button the user control changes the data to "Tom". Now when i click on Update button the Messagebox shows "Tom". Can anyone please tell me how to achieve the updation of observable collection by changing the data in user control rather than through code?

That's because you're not handling the txtTextControl.TextChanged event, so your Text dependency property is never updated.
Anyway, you don't need to handle that manually with a DependencyPropertyChangedCallback and an event handler, you can just bind the txtTextControl.Text to the Text dependency property :
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120"
Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UCTextBox}}}"/>

An observable collection, observers the collection only. You will get notified when items are added or deleted not when fields of a single items did change. That is something completely different. Like Thomas Levesque said, you just need to bind the right property.

Related

Validation of a column entry to a dynamically created DataGrid - compiles but displays nothing

Following many examples here, I created a dynamically created DataGrid from a collection. When I include the DataValidation component for one of the columns, the following code compiles, but displays nothing. Without the validation component, it works. I would appreciate it very much if an experienced member could point out my error.
Thank very much.
i. konuk
XAML:
<Window x:Class="SimpleGridX.MainWindow"
xmlns:local="clr-namespace:SimpleGridX"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer HorizontalAlignment="Left" Margin="10,10,0,0"
VerticalAlignment="Top" Width="497" Height="299"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" >
<DataGrid Name="dgSimpleX" AutoGenerateColumns="True"
SelectionUnit="FullRow" SelectionMode="Extended"
CanUserReorderColumns="False" CanUserAddRows="True">
<DataGridTextColumn Header="Age">
<DataGridTextColumn.Binding>
<Binding Path="[Age]">
<Binding.ValidationRules>
<local:MyValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid>
</ScrollViewer>
</Grid>
</Window>
Window code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace SimpleGridX
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
// Create an instance of the PersonCollection class
PersonCollection people =
new PersonCollection();
InitializeComponent();
dgSimpleX.SelectionUnit = DataGridSelectionUnit.CellOrRowHeader;
dgSimpleX.ItemsSource = people;
dgSimpleX.Items.SortDescriptions.Add(new SortDescription("FirstName", ListSortDirection.Descending));
}
}
}
Object Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows.Controls;
using System.Globalization;
namespace SimpleGridX
{
class Person : INotifyPropertyChanged
{
private string firstName;
private string lastName;
private float age;
private string occupation;
// Each property calls the OnPropertyChanged method
// when its value changed, and each property that
// affects the Person's Description, also calls the
// OnPropertyChanged method for the Description property.
public string FirstName
{
get
{
return firstName;
}
set
{
if (firstName != value)
{
firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("Description");
}
}
}
public string LastName
{
get
{
return lastName;
}
set
{
if (this.lastName != value)
{
this.lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("Description");
}
}
}
public float Age
{
get
{
return age;
}
set
{
if (this.age != value)
{
this.age = value;
OnPropertyChanged("Age");
OnPropertyChanged("Description");
}
}
}
public string Occupation
{
get { return occupation; }
set
{
if (this.occupation != value)
{
this.occupation = value;
OnPropertyChanged("Occupation");
OnPropertyChanged("Description");
}
}
}
// The Description property is read-only,
// and is composed of the values of the
// other properties.
public string Description
{
get
{
return string.Format("{0} {1}, {2} ({3})",
firstName, lastName, age, occupation);
}
}
// The ToString returns the Description,
// so that it is displayed by default when
// the Person object is a binding source.
public override string ToString()
{
return Description;
}
#region INotifyPropertyChanged Members
/// Implement INotifyPropertyChanged to notify the binding
/// targets when the values of properties change.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(
string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(
this,
new PropertyChangedEventArgs(
propertyName));
}
}
#endregion
}
#region ValidationRule
public class MyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
float myage = (float)0.0;
try
{
if (((string)value).Length > 0)
{
myage = (float)Decimal.Parse((string)value, NumberStyles.Any, cultureInfo);
}
}
catch
{
return new ValidationResult(false, "Illegal ccharacters");
}
if ((myage < (float)0.0) || (myage > (float)75.0))
{
return new ValidationResult(false, "Not in range");
}
else
{
return new ValidationResult(true, null);
}
}
}
#endregion
}
Collection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
namespace SimpleGridX
{
class PersonCollection : ObservableCollection<Person>
{
public PersonCollection()
{
// Load the collection with dummy data
//
Add(new Person()
{
FirstName = "Elin",
LastName = "Binkles",
Age = 26,
Occupation = "Professional"
});
Add(new Person()
{
FirstName = "Samuel",
LastName = "Bourts",
Age = 28,
Occupation = "Engineer"
});
Add(new Person()
{
FirstName = "Alan",
LastName = "Jonesy",
Age = 37,
Occupation = "Engineer"
});
Add(new Person()
{
FirstName = "Sam",
LastName = "Nobles",
Age = 25,
Occupation = "Engineer"
});
}
}
}
The Age property should be bound without square brackets.
Also.
It'll be bound to two columns if you leave autogenerate true.
Edit:
Also.
You need any columns inside a columns collection.
What you have there produces an error when I try it, because it tries to make your age column into an item and then the code tries to set itemssource.
<DataGrid.Columns>
<DataGridTextColumn Header="Age">
……
</DataGrid.Columns>

INotifyPropertyChanged Event is not raising

I had a Model class named as FormModel.cs while coding WPF using C#..
Below is my code. I am clueless that even though debugger is going into setter method of Name, PropertyChanged flag is not raising.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace MVVM.Models
{
public class FormModel : INotifyPropertyChanged
{
private string _name;
public FormModel()
{
_name = "";
_bColor = "";
_fColor = "";
}
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private string _bColor;
public string BColor
{
get { return _bColor; }
set { _bColor = "RED"; }
}
private string _fColor;
public string FColor
{
get { return _fColor; }
set { _fColor = "BLUE"; }
}
public void apply(string Name, string BColor, string FColor)
{
this.Name = Name;
this.BColor = BColor;
this.FColor = FColor;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged!=null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
if(propName=="Name")
{
MessageBox.Show("Hi" + Name);
}
}
}
}
}
Here are remaining Files I edited in question in quest on request
MainViewModel.cs
using MVVM.Models;
using MVVM.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Windows;
namespace MVVM.ViewModels
{
public class MainViewModel : ViewModelBase
{
private FormModel FModel;
private ICommand refresh;
public ICommand Refresh
{
get { return refresh; }
set { refresh = value; }
}
public MainViewModel()
{
FModel = new FormModel();
string s = "Pratik";
object o=(object)s;
refresh = new RelayCommand(new Action<object>(getGreet));
}
public void getGreet(object s1)
{
FModel.apply("dsf", "sf", "sf");
MessageBox.Show(s1.ToString());
}
private string _name;
public string Name
{
get { return _name; }
set {
_name = value;
FModel.Name = value;
}
}
}
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
}
}
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace MVVM.ViewModels
{
public abstract class ViewModelBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler!=null)
{
if (propertyName == "Name")
{
//Command
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
}
Below are View Files
MainView.xaml
<Window x:Class="MVVM.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:MVVM.ViewModels"
Title="UI" Height="300" Width="300">
<Grid Background="RED">
<TextBox Name="TT" TextWrapping="Wrap" Text="{Binding Name}" Margin="20,15,160,225" />
<TextBox TextWrapping="Wrap" Text="Back Color" Margin="20,73,195,167" />
<TextBox TextWrapping="Wrap" Text="Font Color" Margin="20,122,195,118"/>
<Label Content="getGreet" HorizontalAlignment="Left" Margin="187,90,0,0" VerticalAlignment="Top"/>
<Button Content="Finish" Command="{Binding Refresh}" HorizontalAlignment="Left" Margin="112,163,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
MainView.xaml.cs
//using MVVM.Models;
using MVVM.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace MVVM.Views
{
/// <summary>
/// Interaction logic for UI.xaml
/// </summary>
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
And Main Files...
App.xaml
<Application x:Class="MVVM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="OnStartup">
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
using MVVM.ViewModels;
using MVVM.Views;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace MVVM
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public void OnStartup(object sender, StartupEventArgs e)
{
// Create the ViewModel and expose it using the View's DataContext
MainView view = new MainView();
view.DataContext = new MainViewModel();
view.Show();
}
}
}
Please help.
You are binding to the Name property of your MainViewModel not the Name property of your Model. Even thought this is essentially a passthrough to the Model.Name property, the binding is attaching to the PropertyChanged event of the MainViewModel which doesn't get raised in your setter.
Add an OnPropertyChanged call in the setter of your MainViewModel.Name property and it should work.
public string Name
{
get { return _name; }
set
{
_name = value;
FModel.Name = value;
OnPropertyChanged("Name");
}
}

Send Selected Object to another Control in WPF/Silverlight?

I am working on a WPF Projet, in which I have a view with two usercontrols on it. This is basically a UserControl with a grid on it and another one with a edit panel to edit the selected object in the DataGrid. The edit panel control, consists of textboxes to edit properties of the selected object in the other control and a button to save. What I would like to do is to pass the selected object to the edit panel,that is each time a object is selected in the grid, the edit panel updates to select that same object. What is the best way to do this, please help?An example would be super :0)
The best way to deal with this is using the MVVM pattern, where both of your user controls bind to the same ViewModel.
The grid can bind to your collection (List<>) of objects that you want to show, and it can also bind its SelectedRow/SelectedItem property to a corresponding property on the ViewModel called SelectedItem (or similar). This means that every time a row is selected in the grid, the underlying data object will be populated into the property on the ViewModel.
You then bind your details user control to the same SelectedItem property on the ViewModel. Check this very simple example of a DataGrid and TextBox binding to the same SelectedItem property:
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfApplication11
{
public class MyViewModel : INotifyPropertyChanged
{
public List<Customer> MyList
{
get { return _myList; }
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
public Customer SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private Customer _selectedItem;
private List<Customer> _myList;
}
public class Customer
{
public string Name { get; set; }
}
}
MainWindow
<Window x:Class="WpfApplication11.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:swm="clr-namespace:System.Windows.Media;assembly=WindowsBase"
xmlns:swm1="clr-namespace:System.Windows.Media;assembly=PresentationCore"
Title="MainWindow" Height="289" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="True" Margin="12,12,12,38" Name="dataGrid1" ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,222,0,0" Name="label1" VerticalAlignment="Top" Width="57" />
<TextBox Text="{Binding Path=SelectedItem.Name, Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="60,222,0,0" Name="textBox1" VerticalAlignment="Top" Width="267" />
</Grid>
</Window>
MainWindow code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication11
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyViewModel vm = new MyViewModel();
vm.MyList = new List<Customer>(new Customer[] { new Customer() { Name = "Bugs Bunny" }, new Customer() { Name = "Elmer Fudd" } });
this.DataContext = vm;
}
}
}
If you run this and then select a row in the grid the name of the customer will be populated into the textbox underneath. If you then modify the name in the textbox and remove focus from it (TAB out of it) then the row in the datagrid will get updated with the new name - all through binding.
For further information, there have previously been a few thousand questions on Stack Overflow regarding the MVVM pattern with WPF, many of them specifically about master-detail views like the one you want.
In your XAML markup, just bind the edit panel's object to the selected object in the grid object.

Combobox INotifyPropertyChanged event not raised!

I created a combobox and set observable collection as the itemsource and implemented INotifyPropertyChanged on the observable collection item. Even after that, when I select different item in the combobox, the OnPropertyChange method is not invoked. I think I am not making the binding properly. Could any one please correct me/ suggest me in this regard.
---------------------------------MainPage.xaml---------------------------------------------------
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="MasterDetailsUpdate.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<StackPanel Width="300">
<ComboBox Name="cboName"></ComboBox>
<TextBox Name="tbxName" Text="{Binding Path=name,Mode=TwoWay,ElementName=cboName}" ></TextBox>
</StackPanel>
</UserControl>
---------------------------MainPage.xaml.cs-----------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace MasterDetailsUpdate
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<Person> persons = new ObservableCollection<Person>();
persons.Add(new Person { city = "c1", name = "n1" });
persons.Add(new Person { city = "c2", name = "n2" });
persons.Add(new Person { city = "c3", name = "" });
persons.Add(new Person { city = "c4", name = "" });
persons.Add(new Person { city = "c5", name = "n1" });
cboName.ItemsSource = persons;
cboName.DisplayMemberPath = "name";
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private string _city;
public string name
{
set
{
_name = value;
OnPropertyChanged("name");
}
get
{
return _name;
}
}
public string city
{
set
{
_city = value;
OnPropertyChanged("city");
}
get
{
return _city;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Thank You
No you aren't binding right, your textbox binding should look like this:-
<TextBox Name="tbxName" Text="{Binding Path=SelectedItem.name, Mode=TwoWay, ElementName=cboName}" />
A ComboBox doesn't have a name property. It does have a SelectedItem property which will be the Person instance currently selected. You then want the name property of that instance. Hence the path you need in the binding is SelectedItem.name.
Now you should find that when you edit the textbox and move the focus elsewhere then PropertyChange event will fire and you should see the change in the combo box.
Note by default textbox does not update its value until the user presses tab or in some other way moves the focus on.

How to force ListBox to reload properties of ListBoxItems

Is there any way, how to force ObservableCollection to fire CollectionChanged?
I have a ObservableCollection of objects ListBox item source, so every time I add/remove item to collection, ListBox changes accordingly, but when I change properties of some objects in collection, ListBox still renders the old values.
Even if I do modify some properties and then add/remove object to the collection, nothing happens, I still see old values.
Is there any other way around to do this? I found interface INotifyPropertyChanged, but I don't know how to use it.
I agree with Matt's comments above. Here's a small piece of code to show how to implement the INotifyPropertyChanged.
===========
Code-behind
===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click += addButton_Click;
this.names = new Nicknames();
dockPanel.DataContext = this.names;
}
void addButton_Click(object sender, RoutedEventArgs e)
{
this.names.Add(new Nickname(myName.Text, myNick.Text));
}
}
public class Nicknames : System.Collections.ObjectModel.ObservableCollection<Nickname> { }
public class Nickname : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
string nick;
public string Nick
{
get { return nick; }
set
{
nick = value;
Notify("Nick");
}
}
public Nickname() : this("name", "nick") { }
public Nickname(string name, string nick)
{
this.name = name;
this.nick = nick;
}
public override string ToString()
{
return Name.ToString() + " " + Nick.ToString();
}
}
}
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<DockPanel x:Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignment="Center">Name: </TextBlock>
<TextBox Text="{Binding Path=Name}" Name="myName" />
<TextBlock VerticalAlignment="Center">Nick: </TextBlock>
<TextBox Text="{Binding Path=Nick}" Name="myNick" />
</TextBlock>
<Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
</Grid>
Modifying properties on the items in your collection won't fire NotifyCollectionChanged on the collection itself - it hasn't changed the collection, after all.
You're on the right track with INotifyPropertyChanged. You'll need to implement that interface on the class that your list contains. So if your collection is ObservableCollection<Foo>, make sure your Foo class implements INotifyPropertyChanged.

Resources