I have an viewmodel object which we call MyVM for now. It holds a list of data objects (let's say of type MyData, it has a List<MyEntry> property ListOfEntries) called AllDataObjects and has a property CurrentSelection returning the currently selected data object out of its list.
The mechanism responsible for changing the selected item works and assigns a different MyData object from AllDataObjects to the CurrentSelection property.
Now I have a DevExpress GridControl that needs a DataBinding referring to MyVM.CurrentSelection.ListOfEntries. However if I set this up, the binding will always point to the first ever assigned element, as when CurrentSelection gets a new MyData object instance assigned, the binding still only knows the reference to the old object.
Is there a way how I can bind my Grid to this nested property without having to reset and reinitialize the same MyData instance stored in CurrentSelecton over and over?
I managed to do this by letting MyVM implement the interface INotifyPropertyChanged and having the UserControl holding my Grid listen to its PropertyChanged event.
public class MyVMClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public List<MyData> AllDataObjects { get; set; }
private MyData CurrentSelection_ { get; set; }
public MyData CurrentSelection
{
get { return CurrentSelection_; }
set
{
CurrentSelection_ = value;
OnPropertyChanged(nameof(CurrentSelection));
}
}
public MyVM(List<MyData> allDataObjects)
{
AllDataObjects = allDataObjects;
}
}
and inside the UserControl's code:
public void Initialize(MyVMClass myVM)
{
MyVM = myVM;
MyVM.PropertyChanged += MyVM_PropertyChanged;
MyVM_PropertyChanged();
}
private void MyVM_PropertyChanged(object sender=null, PropertyChangedEventArgs e=null)
{
if (e == null || e.PropertyName == nameof(MyVMClass.CurrentSelection))
{
MyBindingSource.DataSource = MyVM.CurrentSelection.ListOfEntries;
}
}
Related
I have a listbox which i want to get updated when the items get added to a list. I understand I need to bind the listbox. I was trying to follow this question/answer.
I have a ViewModel which handles the list:
namespace TESTS
{
public class ViewModel : INotifyPropertyChanged
{
private List<Cars> _listCars;
public List<Cars> listCars
{
get
{
return _listCars;
}
set
{
if (_listCars == value)
{
return;
}
this.RaisePropertyChanged("Message");
_listCars = value;
this.RaisePropertyChanged("Message");
}
}
public ViewModel()
{
listCars = new List<Cars>();
}
protected void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property Changed");
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the class Cars:
public class Cars: INotifyPropertyChanged
{
public string model{ get; set; }
public string year{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
So I did the binding of listbox to the property path in my Viewmodel which is listCars.
<ListBox .... ItemsSource="{Binding listCars}">
So when in my Main.xaml.cs. I do a button click and add the item. It does not get added to the listbox even though its bind to the list on view model.
public sealed partial class MainPage : Page
{
public static ViewModel vm = new ViewModel();
public MainPage()
{
this.InitializeComponent();
this.DataContext = vm;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Cars x = new Cars();
x.model = "Ford";
x.Year = "1998";
vm.listCars.Add(x);
}
}
I hope I explained what i implemented well enough. Is there something wrong in my implementation of ViewModel. I am new to MVVM. Please help.
Use ObservableCollection<T>, not List<T>. The former is designed to be used with MVVM, the latter is not. You'll get all your notifications automatically. It's doable with List<T>, but you'll have to write much more code and the performance will be much worse, especially with big collections. Just don't do it.
If you create the collection in the constructor, assign it to a read-only property and never change its instance (and this is the way you should do it), you don't even need to implement INPC.
When implementing INPC, you're expected to call RaisePropertyChanged after you've changed the property, once, and with the property name that has been changed, not a random unrelated string.
I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection
I have 2 main classes. The first class represents a Cell that can have values X, O or Empty. I have implemented INotifyPropertyChanged on this.
public class Cell : INotifyPropertyChanged
{
private Symbol state;
public Symbol State
{
get { return state; }
set
{
if (value == Symbol.X || value == Symbol.O)
state = value;
OnPropertyChanged("State");
}
}
public Cell()
{
state = Symbol.Empty;
}
public enum Symbol
{
X, O, Empty
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
The second class contains an object of this class and is also set as the datacontext for my main window.
public class Board
{
private Cell testCell;
public Cell TestCell
{
get { return testCell; }
set { testCell = value; }
}
public Board()
{
TestCell = new Cell();
}
public void Cell_Click(int cellNum)
{
TestCell.State = Cell.Symbol.O;
}
}
In my mainwindow I have set the datacontext as board, and also contains a button_click function.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Board();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Board board = this.DataContext as Board;
board.Cell_Click(cellNum);
}
}
In my XAML I have bound to Cell object within board using a button style like this:
<Setter Property="Background" Value="{Binding TestCell,
Converter={StaticResource BGConverter}}"/>
BGConverter is a custom converter that accepts a Cell object and converts it into a Colors object. I believe I am indeed directly binding to an object that has INotify implemented, so there's no issue of nested objects. However the binding doesn't reflect changes when I click. When debugging, I found that PropertyChanged event is always null.
The closest answer I found for this is that the event will be subscribed to only if the class Cell is my datacontext. Atleast that's what I understood. How can I correct this problem?
Also I am fresh out of college, currently learning WPF on a new job, so any general recommendations are welcome too.
Thanks
Simply bind to TestCell.State instead of TestCell
I'm pretty new at this myself, but I believe your data context needs to implement INotifyPropertyChanged as well.
That is, your Board class needs to listen to the PropertyChanged event of the cells and fire its own PropertyChanged event when this happens.
I have an ObservableCollection of items bound to a listbox as the ItemsSource.
Some of these items are also located in another collection on the same ViewModel (call it CollectionTwo).
I want to be able to take the count of the item in Collection2 and display it for the respective item in CollectionOne. When CollectionTwo properties change (ie the Count), it must also be reflected back to CollectionOne.
I would guess the best way to do this in MVVM is to wrap items in CollectionOne with a viewmodel class with an extra Count property on it. Can someone point me to a good example of this? Or perhaps another method to tackle this problem that won't hugely weigh down my ItemsSource performance.
Thanks!
You can use inheritance to create a custom collection along these lines...
public class MyCollection<T> : ObservableCollection<T>, INotifyPropertyChanged
{
// implementation goes here...
//
private int _myCount;
public int MyCount
{
[DebuggerStepThrough]
get { return _myCount; }
[DebuggerStepThrough]
set
{
if (value != _myCount)
{
_myCount = value;
OnPropertyChanged("MyCount");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
This is a class that wraps an Observable Collection and puts a custom property in it. The property participates in change notification, but that depends upon your design.
To wire it up, you can do something like this...
public MyCollection<string> Collection1 { get; set; }
public MyCollection<string> Collection2 { get; set; }
public void Initialise()
{
Collection1 = new MyCollection<string> { MyCount = 0 };
Collection2 = new MyCollection<string> { MyCount = 0 };
Collection2.CollectionChanged += (s, a) =>
{
// do something here
};
}
You can also do something like...
Collection1.PropertyChanged += // your delegate goes here
I have created WPF MVVM application, and set WPFToolkit DataGrid binding to DataTable so I want to know how to implement DataTable property to notify changed. Currently my code is like below.
public DataTable Test
{
get { return this.testTable; }
set
{
...
...
base.OnPropertyChanged("Test");
}
}
public void X()
{
this.Test.Add(...); // I suppose this line will call to getter first (this.Test = get Test) and then it will call add letter, this mean that setter scope will never fire.
base.OnPropertyChanged("Test"); // my solution is here :) but I hope it has better ways.
}
Is it has another solution for this problem?
There are 2 ways your Table data could change: Either an element could be added/removed from the collection, or some properties from within an element could change.
The first scenario is easy to handle: make your collection an ObservableCollection<T>. Invoking .Add(T item) or .Remove(item) on your table will fire a change notification through to the View for you (and the table will update accordingly)
The second scenario is where you need your T object to implement INotifyPropertyChanged...
Ultimately your code should look something like this:
public class MyViewModel
{
public ObservableCollection<MyObject> MyData { get; set; }
}
public class MyObject : INotifyPropertyChanged
{
public MyObject()
{
}
private string _status;
public string Status
{
get { return _status; }
set
{
if (_status != value)
{
_status = value;
RaisePropertyChanged("Status"); // Pass the name of the changed Property here
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Now set the datacontext of your View to be an instance of your ViewModel, and bind to the collection, like:
<tk:DataGrid
ItemsSource="{Binding Path=MyData}"
... />
Hope this helps :)
Ian