I have the exact problem as this guy in the Silverlight Forum and the accepted answer is :
In this case, your property didn't actually change value. You added
something to your List, but the list is the same List so when the
DependencyProperty mechanism sees that the actual value (reference to
your List) didn't change, it didn't raise your OnChanged handler
This is a great explication but not an answer to fix this problem. I can find on Google many suggestion for WPF but not for Silverlight.
The problem is describe as this one : You have a DependencyProperty that is called when the variable is initialized but after then nothing is updated.
public partial class MyGrid : UserControl
{
public MyGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register(
"Shapes", typeof(ObservableCollection<ModelItem>), typeof(MyGrid), new PropertyMetadata(OnShapesPropertyChanged));
public ObservableCollection<ModelItem> Shapes
{
private get { return (ObservableCollection<ModelItem>)GetValue(ShapesProperty); }
set { SetValue(ShapesProperty, value); }
}
private static void OnShapesPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyGrid)o).OnShapesPropertyChanged(e); //Fire Only Once
}
private void OnShapesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
dg.ItemsSource = e.NewValue as ObservableCollection<ModelItem>;
}
}
//--------
public class ViewModel : INotifyPropertyChanged
{
public Model Model { get; set; }
public RelayCommand cmd;
public ObservableCollection<ModelItem> ModelItemCollection
{
get
{
return Model.ModelItem;
}
}
public ViewModel()
{
Model = new Model();
Model.PropertyChanged += Model_PropertyChanged;
}
void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.PropertyName);
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItemCollection"));
}
}
public ICommand AddCmd
{
get { return cmd ?? (cmd = new RelayCommand(a => Model.ModelItem.Add(new ModelItem {Name = "asd"}))); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
///----------------------
public class Model: INotifyPropertyChanged
{
public ObservableCollection<ModelItem> ModelItem { get; set; }
public Model()
{
ModelItem = new ObservableCollection<ModelItem>();
ModelItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ModelItem_CollectionChanged);
}
void ModelItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ModelItem
{
public String Name { get; set; }
}
Even with explicit call of PropertyChanged() nothing is updated.
What is the workaround to let know the DependencyProperty that the ObservableCollection has elements that have changed?
Pseudocode:
BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();
Look here: forcing a WPF binding to 'refresh' ...
Try this, usually works :)
Related
I'm using this technique for navigation between views: https://social.technet.microsoft.com/wiki/contents/articles/30898.simple-navigation-technique-in-wpf-using-mvvm.aspx
I have the main ViewModel with menu buttons bound to SelectedViewModel property change commands:
class MainViewModel : INotifyPropertyChanged
{
public ICommand SomeViewCommand { get; set; }
public ICommand OtherViewCommand { get; set; }
private object selectedViewModel;
public event PropertyChangedEventHandler PropertyChanged;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public MainViewModel()
{
SomeViewCommand = new RelayCommand<object, object>(null, (object o) => OpenSomeView());
OtherViewCommand = new RelayCommand<object, object>(null, (object o) => OpenOtherView());
}
private void OpenSomeView()
{
SelectedViewModel = new SomeViewModel();
}
private void OpenOtherView(object obj)
{
if(SelectedViewModel != null && SelectedViewModel.GetType() == typeof(SomeViewModel))
{
SomeViewModel s = (SomeViewModel)SelectedViewModel;
// always 0
if (s.NumberOfChanges > 0)
{
MessageBox.Show("test", "Error");
}
// SelectedViewModel = new OtherViewModel(); after confirmation dialog
}
else
SelectedViewModel = new OtherViewModel();
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
If I'm in SomeView, I'd like to check its property (number of changes) before switching to OtherView and show a confirmation dialog to the user to confirm their action. I need the current value, but any property seems to have its initialization value. Why?
What would be the cleanest way of doing this? I know it can be done by making the property static, but that seems dirty to me.
In OnPropertyChanged method you can set NumberOfChanges.
I am trying to pass strings between forms. Why does it not? Am I missing something or is it an error in the program or what?
On UserControl3
UserControl1 u1;
public UserControl3()
{
u1 = new UserControl1();
InitializeComponent();
}
On UserControl3
public void materialCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if (materialCheckBox1.Checked)
{
u1.toUserControl3 = "GOINTHEBOX!";
}
else
{
u1.toUserControl3 = string.Empty;
}
}
On UserControl1
public string toUserControl3
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
On UserControl1
public void textBox1_TextChanged(object sender, EventArgs e)
{
}
Changing the Text property on a control through a piece of code doesn't necessarily mean the value control will update. Typically you need some sort of binding between your property, in this case toUserControl3, and your control. You need a way to tell your control that value changed so it knows to update.
You could accomplish databinding in the following way:
Create a new class to handle state and binding: This eliminated any need to pass controls into constructors of other controls.
public class ViewModel : INotifyPropertyChanged
{
public string TextBoxText => CheckBoxChecked ? "GOINTOTHEBOX!" : string.Empty;
public bool CheckBoxChecked
{
get { return _checkBoxChecked; }
set
{
_checkBoxChecked = value;
OnPropertyChanged("CheckBoxChecked");
}
}
private bool _checkBoxChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is your main form
public void Form1
{
public Form1(ViewModel viewModel)
{
UserControl1.DataBindings.Add("TextBoxTextProperty", viewModel, "TextBoxText");
UserControl3.DataBindings.Add("MaterialCheckBoxCheckedProperty", viewModel, "CheckBoxChecked");
}
}
UserControl1
public void UserControl1()
{
public string TextBoxTextProperty
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
}
UserControl3
public void UserControl3()
{
public bool MaterialCheckBoxCheckedProperty
{
get { return materialCheckBox1.Checked; }
set { materialCheckBox1.Checked = value; }
}
}
I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Maybe I don't understand the ObservableCollection well enough. But as far as I knew it was similar to a normal list, but with event triggers so that you can react to changes.
So I have this Windows store app. And in this application I have a main BusinessModel class which is the main source for all data in my client application. This data will be updated when the server has made some changes elsewhere. In the future I'd like to have this class update the ViewModels for specific data updates etc.
So I also have a ViewModel class which contains, at least in my PoC's so far, a copy of that list (also in the near future this list will have an enriched version of the list).
Since it's a copy they should be both separate instances and have their own separate items.
However when I update the copy in the ViewModel, the BusinessModel version changes with it.
And vice versa.
I can't seem to figure out why this is happening. Underneath you will find the classes and their functions:
//the BusinessModel Class
public class ModelStuff : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _modelStuff;
public ObservableCollection<DataObject> modelStuff
{
get
{
return _modelStuff;
}
set
{
_modelStuff = value;
NotifyPropertyChanged("modelStuff");
}
}
private static ModelStuff businessModel;
public static ModelStuff BusinessModel
{
get
{
if (businessModel == null)
{
businessModel = new ModelStuff();
}
return businessModel;
}
}
public ModelStuff()
{
modelStuff = new ObservableCollection<DataObject>();
modelStuff.Add(new DataObject(0));
modelStuff.Add(new DataObject(1));
modelStuff.Add(new DataObject(2));
modelStuff.Add(new DataObject(3));
modelStuff.Add(new DataObject(4));
modelStuff.Add(new DataObject(5));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the ViewModel class
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _visibleStuff;
public ObservableCollection<DataObject> visibleStuff
{
get
{
return _visibleStuff;
}
set
{
_visibleStuff = value;
NotifyPropertyChanged("visibleStuff");
}
}
private static ViewModel tvm;
public static ViewModel TVM
{
get
{
if (tvm == null)
{
tvm = new ViewModel();
}
return tvm;
}
}
public ViewModel()
{
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the TestObjects
public class DataObject
{
public int testNumber { get; set; }
public String testStr { get; set; }
public DataObject(int i)
{
testNumber = i;
testStr = "testje";
}
}
//A randomly placed button invokes this function when clicked.
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
ViewModel.TVM.visibleStuff[0].testNumber = 100;
int i1 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
//Second version but vice versa
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ViewModel.TVM.visibleStuff[0].testNumber;
ModelStuff.BusinessModel.modelStuff[0].testNumber = 100;
int i1 = ViewModel.TVM.visibleStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
Where has my reasoning gone wrong?
Why is this happening?
And more importantly, how can I prevent this behaviour?
As far as I can see, your line of code:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
is not making a copy of the underlying objects at all. It is adding the same DataObjects from the original list to a new ObservableCollection.
You need to clone the DataObjects individually and add them to the new collection. Something like this should do it:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber).Select(i => new DataObject(i.testNumber)));
I'm trying to bind some XAML code to a property in my ViewModel.
<Grid Visibility="{Binding HasMovies, Converter={StaticResources VisibilityConverter}}">
...
</Grid>
My ViewModel is setup like this:
private bool _hasMovies;
public bool HasMovies
{
get { return _hasMovies; }
set { _hasMovies = value; RaisePropertyChanged("HasMovies"); }
}
In the constructor of the ViewModel, I set the HasMovies link:
MovieListViewModel()
{
HasMovies = CP.Connection.HasMovies;
}
in CP:
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
private ObservableCollection<Movie> _movies;
public ObservableCollection<Movie> MovieList
{
get { return _movies; }
set
{
_movies = value;
RaisePropertyChanged("MovieList");
RaisePropertyChanged("HasMovies");
_movies.CollectionChanged += MovieListChanged;
}
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
What am I doing wrong? How should I change this binding so that it reflects the current state of CP.Connection.HasMovies?
Either directly expose the object in the ViewModel and bind directly through that (so that the value is not just copied once which is what happens now) or subscribe to the PropertyChanged event and set HasMovies to the new value every time it changes in your source object.
e.g.
CP.Connection.PropertyChanged += (s,e) =>
{
if (e.PropertyName = "HasMovies") this.HasMovies = CP.Connection.HasMovies;
};
First of all, the setter for a collection type, such as your MovieList property, is not called when you change the content of the collection (ie. Add/Remove items).
This means all your setter code for the MovieList property is pointless.
Secondly, it's very silly code. A much better solution, is to use NotifyPropertyWeaver. Then your code would look like this, in the viewmodel:
[DependsOn("MovieList")]
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
Alternatively you would have to add a listener for the CollectionChanged event when you initialize the MovieList property the first time (no reason to have a backing property, really really no reason!), and then call RaisePropertyChanged("HasMovies") in the event handler.
Example:
public class CP : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CP()
{
MovieList = new ObservableCollection<Movie>();
MovieList.CollectionChanged += MovieListChanged;
}
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}