ListBox bind to ObservableCollection is not updated with collection - wpf

I have next model:
public class MyModel
{
public ObservableCollection<MyObject> MyList {get; set;}
}
public class MyObject
{
MyObservableDictionary MyDictionary {get; set;}
}
public class MyObservableDictionary : ObservableCollection<EnymValue>
{
}
public class EnymValue : INotifyPropertyChanged
{
private MyEnum key;
private string value;
public MyEnum Key
{
get
{
return this.key;
}
set
{
this.key = value;
NotifyPropertyChanged("Key");
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public LanguageValue(MyEnum key, string value)
{
this.Key = key;
this.Value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public enum MyEnum
{
}
And on View I have a ListBox:
<ListBox x:Name="MyList" SelectionMode="Single" ItemsSource="{Binding Path=MyList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyDictionary, Mode=OneWay, Converter={StaticResource myEnumToTextConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(myEnumToTextConverter converter is just selects first element from collection and return it's value, or some specified constant if collection is null or empty)
I want my Model's list box to be updated on view, when any EnymValue values are changed.
Is it possible somehow to implement this?
Currently the view is not updated when Value changed.
I've tried to inherit EnymValue from INotifyPropertyChanged, but this didn't helped. Looks like PropertyChanged == null on EnymValue.NotifyPropertyChanged when property updated.

ObservableCollection is able to notify UI about changes when collection itself is changed(elemends are added or deleted). But ObservableCollection is not aware of changes that are happening when you modify one of it's items. To solve the problem you may subscribe to CollectionChange event of observable collection, and when new item is added, subscribe to new items's PropertyChanged. When PropertyChanged event is raised, you can trigger notification on your list OnPropertyChanged(()=>MyItems); You should be careful implementing this solution and remember to unsubscribe from the event's to avoid memory leaks.
An example of what I mean you can see in this answer.

Your MyDictionary should force a refresh. Easiest way is to re-assign its old value, and implement INPC in MyObject like below :
public class MyObject: INotifyPropertyChanged
{
MyObservableDictionary _myDictionary;
public MyObservableDictionary MyDictionary {
get
{
return _myDictionary;
}
set
{
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
public MyObject()
{
MyDictionary = new MyObservableDictionary();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Sample code to change Value :
private void Button_Click(object sender, RoutedEventArgs e)
{
// vm is ViewModel instance, vm is DataContext set for Window
var old = vm.MyList[0].MyDictionary;
vm.MyList[0].MyDictionary[0].Value = "aaaa";
vm.MyList[0].MyDictionary = old;
}
I tested this, and it displays changed value as "aaaa".

Related

WPF - How to get selected value binded to a table model

I have a combo box category that is binded to an ObservableCollection Categories based on tbl_Category with two properties CategoryName and CategoryDescription. Now I want to add the SelectedValue of the ComboBox to product table property Prod_Category
In my constructor :
cb.DataContext = Categories;
this.DataContext = new tbl_Product();
Combo box xaml :
<Combobox x:Name="cb" ItemSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedValue="{Binding Prod_Category,Mode=TwoWay}"/>
In my save product event :
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
I get Prod_Category to null even after doing all this.
You should use SelectedItem instead of SelectedValue, refer to this.
besides that what you are willing to do wasn't so clear, I've tried to implement what you asked for based on my understanding
public partial class MainWindow : Window,INotifyPropertyChanged
{
private ObservableCollection<Category> _categories;
public ObservableCollection<Category> Categories
{
get
{
return _categories;
}
set
{
if (_categories == value)
{
return;
}
_categories = value;
OnPropertyChanged();
}
}
private Category _prod_Category ;
public Category Prod_Category
{
get
{
return _prod_Category;
}
set
{
if (_prod_Category == value)
{
return;
}
_prod_Category = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Categories=new ObservableCollection<Category>()
{
new Category()
{
CategoryName = "Name1",
CategoryDescription = "Desc1"
},new Category()
{
CategoryName = "Name2",
CategoryDescription = "Desc2"
}
};
}
public void SaveButton_Click(object sender, RoutedEventArgs routedEventArgs)
{
if (Prod_Category!=null)
{
//add it to whatever you want
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Category
{
public String CategoryName { get; set; }
public String CategoryDescription { get; set; }
}
and the xaml
<StackPanel>
<ComboBox x:Name="cb" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedItem="{Binding Prod_Category,Mode=TwoWay}"/>
<Button Content="Save" Click="SaveButton_Click"></Button>
</StackPanel>
you should consider implementing the INotifyPropertyChanged interface to notify the UI if any changes occurs in one of the binded properties
In addition to #samthedev 's provided answer, I would also recommend you change your assignment of DataContext from:
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
to
tbl_Product prod = this.DataContext as tbl_Product;
if (tbl_Product != null)
{
DataOperations.AddProduct(prod);
}
This prevents any change of DataContext being accidentally bound to a different object and causing an unhandled exception due to the fact that DataContext does have tbl_Product as its base-type which can happen more often than you realise in WPF due to DataContext inheritance when someone changes something at a higher level.

How to update observable collection on property change and also store it on every change?

I have dynamic listbox contains textbox to display list items and so I can edit listbox item. My application setting file contains string collection which I want to bind for that listbox. I also want to update that setting files on every change of listbox item, I created class which implements INotifyProprtyChanged. I have converted string collection from settings file into observable collection of custom type which has string property. I bind textbox to property of that custom class and update source property on property change. I want to update observable collection as well. and that updates my app setting file as well. Please help me on this. Any help would be really appreciated. My code:
public class WindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<UrlModel> customcollection;
public ObservableCollection<UrlModel> CustomCollection
{
get { return customcollection; }
set
{
customcollection = value;
NotifyPropertyChanged("CustomCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public WindowViewModel()
{
List<string> customlist = Properties.Settings.Default.CustomList.Cast<string>().ToList();
List<UrlModel> urllist = new List<UrlModel>();
urllist = customlist.Select(item => new UrlModel() { urlString = item }).ToList();
CustomCollection = new ObservableCollection<UrlModel>(urllist);
}
}
public class UrlModel : INotifyPropertyChanged
{
private string url;
public string urlString
{
get { return url; }
set
{
url = value;
NotifyPropertyChanged("urlString");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = new WindowViewModel();
ListTwo.ItemsSource = ViewModel.CustomCollection;
}
private WindowViewModel viewModel;
public WindowViewModel ViewModel
{
get { return viewModel; }
set{
viewModel = value;
DataContext = value;
}
}
}
}
Add property changed event handler for each url when inserting into ObservableCollection
public WindowViewModel()
{
List<string> customlist = Properties.Settings.Default.CustomList.Cast<string>().ToList();
List<UrlModel> urllist = new List<UrlModel>();
urllist = customlist.Select(item => new UrlModel() { urlString = item }).ToList();
CustomCollection = new ObservableCollection<UrlModel>(urllist);
foreach(var model in CustomCollection)
{
model.PropertyChaged += SettingsUpdater; //Settings update fucntion
}
}
A naive implementation of SettingsUpdater would just update the whole list of urls in settings whenever one of them changes.
I believe you are using data template to make your listbox editable. If that is the case, while binding the text, include Text="{Binding urlString,UpdateSourceTrigger=PropertyChanged}" in the Xaml code.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding urlString,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>

Changing data using ICommand in MVVM

I have two windows: the parent and the child. There is the listbox in a parent window.
MainView:
<Button x:Name="btnUpdate" Content="Update"
Command="{Binding Path=MyCommand}" CommandParameter="{Binding ElementName=lstPerson, Path=SelectedItem}" />
<ListBox x:Name="lstPerson" ItemsSource="{Binding Persons}" />
I'm trying to change selected Person two-way update by using ICommand with parameter.
PersonViewModel:
private ICommand myCommand;
public ICommand MyCommand
{
get
{
if (myCommand == null)
{
myCommand = new RelayCommand<object>(CommandExecute, CanCommandExecute);
}
return myCommand;
}
}
private void CommandExecute(object parameter)
{
var ew = new EditWindow()
{
DataContext =
new EditViewModel()
{
Name = ((Person) parameter).Name,
Address = ((Person) parameter).Address
}
};
ew.Show();
}
But selected instance of Person don't changed in listbox. What do I need to write to the xaml or PersonViewModel to make it working?
P.S.
Here is my Person
public class Person : INotifyPropertyChanged
{
private string name;
private string address;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Address
{
get
{
return address;
}
set
{
address = value;
OnPropertyChanged("Address");
}
}
}
The parameter of your exceution command for the command is wrong. When your binding to SelectedItem of the list which is bound to an ObservableCollection<PersonViewModel>, the selected item will be of type PersonViewModel. Try initializing the ICommand asRelayCommandand modifyCommandExecute(PersonViewModel person)` accordingly.
Secondly, the ICommand is defined on PersonViewModel, but the Command should be on the ViewModel which holds the Persons collection. So, either you move the Command or you define the command on PersonViewModel in a way that it modifies the particular ViewModel, it is on. Than you can spare the CommandParameter, but bind the command like this:
and make CommandExecute something like this:
private void CommandExecute(object parameter)
{
// Modify this, ie. this.Name = something
}
Last thing, your ViewModel needs to implement INotifyPropertyChanged as well and forward the model change notifications. Otherwise changes will not be reflected, unless the binding to an actual property updates it. For example, if you bind like this
<TextBox Text="{Binding Name, Mode=TwoWay}" />
the Name property on the ViewModel will be updated, but if you call
Name = "ChuckNorris"
in your CommandExecute(..) method, the UI won't be updated, because no change notfication is fired.

How to bind a Property with a textblock From a Class

public class myClass : INotifyPropertyChanged
{
public string myName(string myNameIs)
{
Name = myNameIs;
return myNameIs;
}
public string My = "Hasan";
public string Name {
get { return My; }
set
{
My = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged( this, new PropertyChangedEventArgs(
propertyName));
}
}
}
.
XAML:
<TextBlock Height="42" Margin="107,245,0,0" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="159" DataContext="{Binding Source={StaticResource myClassDataSource}}"/>
This is working. But when i update property then it isn`t work?
Your code is rather confusing, you seem to be all over the place with it. I know this isn't the question you asked, but i thought i would point this out anyway:
your member variable is declared as public (public string My = "Hasan";)
your member variable has a totally different name to its property (My and Name)
you have a setter for the public property, and also a setting function (myName(string myNameIs))
you are returning the same value from the setting function as what you passed in
Here is an example of how you could rewrite it:
public class MyClass : INotifyPropertyChanged
{
//normal default constructor
public MyClass()
{
_name = "Hasan";
}
//extra constructor for when you need to set the name to something other than the default
//although this is really only useful if you have no setter on the Name property
public MyClass(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(
propertyName));
}
}
private string _name;
}
You just need to set the TextBlock (or it's parent's) DataContext property to an instance of this class.
Next bind the Text property to the backing property like this
<TextBlock Text="{Binding Name}"/>
Try going through a few tutorials online (or a book) instead of trying to forge your way through. It's easy once you get how DataBinding works.
Update: Once I formatted your question correctly, I could see the XAML you are using...
The mistake here is that you're trying to use the ElementName property (which is used to bind one UI element with another by name). This isn't what you're trying to achieve.

Need a simple Example of cascading combo boxes using MVVM

Need a simple Example of cascading combo boxes using MVVM
Wpf / Silverlight
If I understand your question you want to have the next combobox to fill with data based on the previous value.
I have a generic ViewModel that you can have to capture the list of items and the selected item
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And in your view have this code somewhere.
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
You can refactor to make it nicer, use MVVM frameworks or derive the ItemListViewModel specifically for the list of items and have the load in there for better encapsulation. Its up to you.
If any parent combobox value gets changed then all child lists will get cleared.
HTH

Resources