My ViewModel has a 30 second data refresh service delegate method:
public Task OnDataRefreshed(List<MyType> data)
{
this.Data = data;
LongRunningGetDetailsAsync();
return Task.FromResult(0);
}
Public property Data is displayed and refreshed in the view properly.
The intention here is not to await the async task (fire-and-forget) LongRunningGetDetailsAsync() as it will introduce a significant delay before the Data is displayed if executed sync. I want to show Data ASAP and then let async task fetch the details at its own pace and let the view binding catch up then.
private async Task LongRunningGetDetailsAsync()
{
foreach (MyType dataitem in this.Data)
{
dataitem.Details = await _apiEndpointService.GetDetails(dataitem.Id);
}
}
LongRunningGetDetailsAsync() is where the binding is not firing. I set a break point at the end of LongRunningGetDetailsAsync watching Data.Details - the Data.Details are there, but it is never displayed in the view.
Thank you in advance for your time!
EDIT:
Changed to
public async Task OnDataRefreshed(ObservableCollection<MyType> data)
{
this.Data = data;
await LongRunningGetDetailsAsync();
}
still have the same issue. Data is bound to Mvx.MvxListView. If the list is long and an item happens to be out of view, once scrolled to, it displays the updated model OK.
"Data":
public class MyType
{
public string MyProperty { get; set; }
public string Details { get; set; }
}
private ObservableCollection<MyType> _data;
public ObservableCollection<MyType> Data
{
get { return _data; }
set
{
if (SetProperty(ref _data, value))
{
RaisePropertyChanged(() => Data);
}
}
}
View binding:
<Mvx.MvxListView
local:MvxBind="ItemsSource Data"
local:MvxItemTemplate="#layout/listitem"
... />
listitem:
<TextView local:MvxBind="Text MyProperty" ...
<TextView local:MvxBind="Text Details" ...
First, use await keyword when you're calling your function and mark your function as async.
public async Task OnDataRefreshed(List<MyType> data)
{
this.Data = data;
await LongRunningGetDetailsAsync();
}
Now as I understand, you want to show items in your UI as soon as you get each of them. If your bindings are correct, the changes above should do what you need. You should probably use ObservableCollection<> instead of List<> for Data property though.
Since you're changing the Details property you need to let the view know that it has changed. You would need to use the same logic as you do for your Data property:
private string _details;
public string Details
{
get { return _details; }
set
{
if (SetProperty(ref _details, value))
{
RaisePropertyChanged(() => Details);
}
}
}
As a side note, try to avoid setters for your lists. In the future you may have other logic connected to your lists which, when overwritten, just complicates things. Instead, since it is exposed as an ObservableCollection<T> you can call Clear and then Add on the value from the getter.
Related
I'm not sure what is the best method to load async data when a bound property changes.
I have a ListView and when an item is selected I display new content with a detailed data. The detail item is obtained from an async method.
Data method:
private async void ShowDetailAsync()
{
if (SelectedItem?.Id != null)
{
detailViewModel.Item = await storage.GetDetailItemAsync(SelectedItem.Id);
}
else
{
detailViewModel.Item = null;
}
}
ViewModel property:
public GearItemListViewModel SelectedItem
{
get => selectedItem;
set { this.SetValue(ref selectedItem, value); ShowDetailAsync(); }
}
Now it works as fire-and-forget async method, but how would it be the best approach to load it without risk of desynchronized view and data when user rapidly browses the records in the ListView (first click takes longer to load the record than the other)?
Or is there another method how to switch and load detail items, without async call in the property setter?
I like to use the "asynchronous property" approach described in my MSDN article on async data binding.
In this case, your detailViewModel.Item property would change type from TItem to NotifyTask<TItem>, and your ShowDetailAsync becomes:
private void ShowDetail()
{
if (SelectedItem?.Id != null)
{
detailViewModel.Item = NotifyTask.Create(storage.GetDetailItemAsync(SelectedItem.Id);
}
else
{
detailViewModel.Item = null;
}
}
Note that the method is synchronous. It is synchronously starting an asynchronous operation.
With this change, your data binding would need to update to reference Item.Result instead of Item. You can then data bind to other properties such as Item.IsNotCompleted if you want to show a spinner/loading indicator, and Item.IsFaulted to notify the user of an error (with the previous async void approach, any errors would be raised directly on the UI thread).
Ok let's say we have a Parent Window bound to a DataContext now assume we fire a Child Window and set its data context to that of the parent window. Now the items in the child window are bound to the data context in TwoWay mode. When the user changes something in this child window the data in the source viewmodel will be updated. But what if the user closes the window with DialogResult == false? How to rollback the data to its original?
For now I'm binding the data in OneWay mode and applying the changes only when the DialogResult == true. But I guess there must be a more elegant way to do this.
IEditableObject
In the same way that WPF can use System.ComponentModel.INotifyPropertyChanged you can also use the interface System.ComponentModel.IEditableObject. This interface has the following useful methods:
BeginEdit()
EndEdit() - not really required for our use
CancelEdit()
Prior to displaying your window, call BeginEdit() on your datacontext. Now any changes reflect via the usual INotifyPropertyChanged so that the parent receives updates as usual.
If the user cancels, then call CancelEdit() at which point your viewmodel should rollback the changes. The free set of steak knives is that your parent window is informed of the rollback via INotifyPropertyChanged thus "reverting" the parent window too.
Otherwise call EndEdit() which honestly isn't required as the changes are done already.
Obviously you would need to remember somehow the prior values so that you can revert any changes.
IRevertibleChangeTracking
I've just read about System.ComponentModel.IRevertibleChangeTracking which offers the methods AcceptChanges() and RejectChanges(). Though arguably this is the interface to use for accepting and rolling back changes, it's not entirely clear whether changes made in the interim should be broadcast in the WPF scenario. Perhaps someone can help me here.
Tell me more...
Change the UpdateSourceTrigger on the binding to be explicit and call the UpdateSource method only when the OK button is clicked.
UpdateSourceTrigger
For example, you can bind the confirmed values as one-way, readonly in the main view and the uncommitted ones to the dialog text boxes.
ViewModel
class MyVM : MVVM.ViewModel.ViewModelBase
{
private string name1;
public string Name1
{
get { return name1; }
set {
name1 = value;
OnPropertyChanged(() => Name1);
}
}
string name1Conf;
public string Name1Conf
{
get { return name1Conf; }
}
private string name2;
public string Name2
{
get { return name2; }
set
{
name2 = value;
OnPropertyChanged(() => Name2);
}
}
string name2Conf;
public string Name2Conf
{
get { return name2Conf; }
}
private bool commitMe;
public bool CommitMe
{
get { return commitMe; }
set {
commitMe = value;
OnPropertyChanged(() => CommitMe);
if (commitMe)
{
DoCommit();
}
}
}
private void DoCommit()
{
name1Conf = name1;
name2Conf = name2;
OnPropertyChanged(() => Name1Conf);
OnPropertyChanged(() => Name2Conf);
}
}
CodeBehind
private void button_Click(object sender, RoutedEventArgs e)
{
var win2 = new WPFDialog();
win2.DataContext = myVM;
var res = win2.ShowDialog();
myVM.CommitMe = res == true;
}
I want to make a transition to a reactive view model / model.
I've used 3 scenarios so far:
"ValueA": The model value is only accessed from one view model at a time and the value is only changed through the view model
=> simple property in model, forwarding property with PropertyChanged in view model
"ValueB": The model value is accessed from several view models and/or changes from other sources
=> property with event in model, forwarding property and translation from changed event to PropertyChanged in view model
"ValueC": A value only used in the view model
=> no property in model, property backed by own field with PropertyChanged in view model
This is my "current" approach:
class Model
{
public string ValueA {get;set;}
private string valueB;
public event ValueBChangedEvent ValueBChanged;
public string ValueB
{
get
{
return valueB;
}
set
{
valueB = value;
ValueBChanged();
}
}
}
class ViewModel : INotifyPropertyChanged
{
private Model model;
public string ValueA
{
get
{
return model.ValueA;
}
set
{
model.ValueA = value;
OnPropertyChanged();
}
}
ViewModel()
{
model.ValueBChanged += model_ValueBChanged;
}
private void model_ValueBChanged()
{
OnPropertyChanged("ValueB");
}
public string ValueB
{
get
{
return model.ValueB;
}
set
{
model.ValueB = value;
// no change notification since done via model
}
}
private string valueC;
public string ValueC
{
get
{
return valueC;
}
set
{
valueC = value;
OnPropertyChanged();
}
}
}
This is how I intend to model them using reactive extensions:
class ReactiveModel
{
public string ValueA {get;set;}
private ISubject<string> valueB = new Subject<string>();
public ISubject<string> ValueB
{
get
{
return valueB;
}
}
}
class ReactiveViewModel : INotifyPropertyChanged
{
private ReactiveModel model;
public string ValueA
{
get
{
return ???;
}
set
{
???
}
}
private ReactiveProperty<string> valueB = model.valueB.ToReactiveProperty();
public Reactive<string> ValueB
{
get
{
return valueB;
}
// no setter since access via ValueB.Value which is read-write
}
private ISubject<string> _valueC = new Subject<string>();
private ReactiveProperty<string> valueC = _valueC.ToReactiveProperty();
public ReactiveProperty<string> ValueC
{
get
{
return valueC;
}
// no setter since access via ValueC.Value which is read-write
}
}
Summary:
"ValueA": I have no clue for this case
"ValueB": works at first glance but does neither propagate changes from view model to model nor the other way.
"ValueC": this works as intended
I'd be happy if I had a solution for ValueA and ValueB.
ValueB: View model is responsible for updating model. ReactivePropertyuses only IObservable interface from your model properties and reads values from ValueB(does not write anything).
ReactiveProperty is changed by view through Value property.
ReactiveProperty implements IObservable and you should subscribe to changes to get new values.
ValueA: We can make a ReactiveProperty on the view model side an subscribe to propagate the changed value to the model.
Here is the code for the solution:
class ReactiveModel
{
public string ValueA {get;set;}
private readonly Subject<string> valueB = new Subject<string>();
public IObservable<string> ValueB
{
get
{
return valueB;
}
}
public void UpdateB(string newValue)
{
valueB.OnNext(newValue);
}
}
class ReactiveViewModel
{
private readonly ReactiveModel model;
private readonly ReactiveProperty<string> valueA;
private readonly ReactiveProperty<string> valueB;
public ReactiveViewModel(ReactiveModel model)
{
this.model = model;
valueA = new ReactiveProperty<string>(model.ValueA);
valueA.Subscribe(x => model.ValueA = x);
valueB = model.ValueB.ToReactiveProperty();
valueB.Subscribe(model.UpdateB);
}
public IObservable<string> ValueA
{
get
{
return valueA;
}
}
public ReactiveProperty<string> ValueB
{
get
{
return valueB;
}
}
}
XAML would be in both cases:
<TextBox Text="{Binding ValueA.Value, UpdateSourceTrigger=PropertyChanged}"/>
This is a bit of a contentious topic but I personally don't see property change notification as being specific to the view model and view, I therefore use B but I add INPC to the models as well in my data layer. This can be done in a post-processing build step using Fody or by wrapping the models in a proxy using something like Castle Dynamic Proxy. I personally use the latter, although it requires integration with your ORM so as to not hammer performance i.e. you don't want your database code loading a model object and then thinking that object has changed because you've tried to update it use the proxy wrapper (this is especially true when you turn IList<> into an ObservableCollection).
Your current approach doesn't seem to make a lot of sense. You are implementing events to signal when the Model changes so the View Model can take action. However only the View Model should change the Model, therefore events are completely unnecessary.
The View Model is responsible for making changes to the Model, therefore it should know when a change has been performed, as it is the source of said change.
A pure MVVM approach would be something like this:
public class MyModel
{
public string MyValue { get; set; }
...
}
public class MyViewModel
{
private MyModel _Model;
public string MyModelValue
{
get { return _Model.MyValue; }
set
{
_Model.MyValue = value;
//Notify property changed.
}
}
...
}
It is not the responsibility of the Model to notify the View of changes, instead it is the responsibility of the ViewModel to signal these changes. The Model should not be exposed to the View, but instead the properties of the Model that the View requires should be exposed.
Think of it this way.
The user changes the MyModelValue property in a TextBox on the View.
The View notifies the ViewModel of the change.
The ViewModel changes the Model.
The only purpose of INotifyPropertyChanged is when the above process is reversed, where the ViewModel needs to tell the View that a property has changed:
A method in the ViewModel is called that updates MyModelValue.
The ViewModel notifies the View of the change.
The View updates the TextBox.
The pattern of exposing only properties of the Model that the view requires is not always followed, instead you may see the entire Model being exposed to the View, but as I have said many times before, MVVM is a pattern, not the law. Implementing INotifyPropertyChanged in the Model is perfectly acceptable.
I have TaskViewModel class with a lot of different properties. The simplified piece of code is below:
internal class TaskViewModel : ViewModelBase
{
private TaskModel _model;
public long Id
{
get { return _model.Id; }
set
{
_model.Id = value;
RaisePropertyChanged("Id");
}
}
public string Title
{
get { return _model.Title; }
set
{
_model.Title = value;
RaisePropertyChanged("Title");
}
}
public DateTime? Date
{
get { return _model.Date; }
set
{
_model.Date = value;
RaisePropertyChanged("Date";);
}
}
private RelayCommand _updateCommand;
public RelayCommand UpdateCommand
{
get
{
return _updateCommand
?? (_updateCommand = new RelayCommand(
() =>
{
// somehow update _model
}));
}
}
}
And I have TaskView where I could edit the instance of TaskViewModel. Also I have a few validation rules, for example, if Titleis empty I can't update model and have to reestablish previous Title. That's why I cannot use "{Binding Mode=TwoWay}.
The question is what is the best way to update view model.
I have two ways to do it:
Add property of TaskViewModel type to the instance and bind all properties of this to the view and than using ICommand for updating properties in main instance if all validations rules are performing. But in this case I need to keep whole copy of object.
Using "{Binding Mode=TwoWay, UpdateSourceTrigger=Explicit}" for necessary properties and than in code-behind using event handlers call binding.UpdateSource(). But in that case I have to implement validation logic in code-behind too, which looks like a bad way in mvvm-approach.
May be you should recommend the best way for this task.
Thanks in advance.
UPDATE:
For example of the typical validation case, Title mustn't be empty. If I changed the Title property from "Buy milk" to "Buy mi" it would be valid, but I don't want to update my model after every change of every property and save it to a storage. So I have to implement SaveCommand which will update the model. But also I need to have a possibility to rollback all the changes, so I can't change current view model properties directly by using Mode=TwoWay binding.
So the problem is how to update all changed properties on demand if they are valid?
I am developing Windows Universal app. I have one GridView which has one textblock and a button. The gridview gets data of un-purchased objects from a service. The button is for purchasing particular object. So if user clicks on button that object is purchased & gridview gets refreshed to remove purchased item from it.
I am illustrating my requirement in simplified manner. I tried two ways, both are not working. Can you please suggest me solution regarding it.
First way I used is to inherit Model class with ViewModel class so I can access methods of ViewModel class, but it throws StackOverflowException in ViewModelBase at SetProperty<T> method.
P.S. - I don't want to migrate to any framework like MVVMLight, etc.
ViewModel.cs
public class ViewModel : ViewModelBase
{
public ViewModel()
{
DataCollection = new ObservableCollection<Model>();
for (int i = 1; i < 10; i++)
{
DataCollection.Add(new Model { Number = i });
}
}
private ObservableCollection<Model> _DataCollection;
public ObservableCollection<Model> DataCollection
{
get { return _DataCollection; }
set { this.SetProperty(ref this._DataCollection, value); }
}
}
Model.cs
public class Model : ViewModel
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
var obj = DataCollection.Where(varNum => varNum.Number == x).FirstOrDefault();
if (obj != null)
{
DataCollection.Remove(obj);
}
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
2nd way I keep that isolated, so I was not able to access the methods.
ViewModel.cs is same as above
Model.cs
public class Model : ViewModelBase
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
// How to access ViewModel's DataCollection property or
// a method which sets un-purchased objects in DataCollection property
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Well, in the first example you're getting a StackOverflowException because your ViewModel instantiates 9 Models each time - and since your Model is an extension of ViewModel, each one of those instantiates 9 more Models and an infinite recursion happens. That doesn't answer your main question, though :)
Your class names are confusing to me, because in MVVM a "Model" is simply a representation of the data and methods to manipulate it, whereas the ViewModel requests this data from the Model and presents it via publicly accessible properties that are retrieved from the View via binding. The View knows about the ViewModel, the ViewModel knows about the Model and the Model just knows about the data. In any case you shouldn't be binding directly from the View to the Model!
You'll want to house the RelayCommand in your ViewModel so your View can bind to it, and depending on what you want to happen when a user purchases an item (store it in a database, track this in another variable, simply remove from the view without doing anything else, etc.) you may or may not need to write additional logic for when this occurs. Generally you'll want the ViewModel to handle user input and update both the presentation object as well as notify the Model a change was made, if this is something your app requires. Think of it as the Model holds the actual data whereas the ViewModel only holds what the user sees.
Unfortunately, without knowing what you're trying to do in a little more detail it's hard to give more specific advice than this!