How to connect model and view model property using ReactiveUI - c#

I am using MVVM with ReactiveUI. I have a property in the model that I want to display and be able to edit in the UI. Is there any simple way to do this using ReativeUI? The following properties should be fulfilled:
The model property implements INotifyPropertyChanged
The model property can be changed from the view model or from the model
Updates from within the model can be made on any thread
Updates from the view model should use a throttle so that not every keystroke becomes a model update
The application can be run with a UI or command line only, and the code should also be runnable in unit tests and integration tests
When using an UI, the PropertyChanged event of the ViewModel needs to be raised on the UI thread
The throttle can't be blocking in either run mode
The code should be robust, i.e. not have the risk of causing deadlocks or reverting back to old values.
I somehow imagined this would be a standard case of how to wire a view model to a model but haven't managed to get this to work, and can't really figure out any way to make it work without quite a lot of code for a seemingly simple task.
Sample code of a non-working implementation:
public interface IModel : INotifyPropertyChanged
{
string MyProperty { get; set; }
}
public class SomeViewModel : ReactiveObject
{
private readonly IModel model;
public SomeViewModel(IModel model)
{
MyProperty = String.Empty;
this.model = model;
var inputThrottleTime = TimeSpan.FromMilliseconds(500);
var scheduler = RxApp.MainThreadScheduler is AvaloniaScheduler
? RxApp.MainThreadScheduler
: RxApp.TaskpoolScheduler;
// This doesn't work. If updates are made in the model inputThrottleTime apart, the old value might be reassigned to the model.
// And also, WhenAnyValue shouldn't be used to listen on properties that might be updated on background threads according to ReactiveUI devs.
this.WhenAnyValue(x => x.model.MyProperty).ObserveOn(scheduler).Subscribe(p => MyProperty = p);
this.WhenAnyValue(x => x.MyProperty).Skip(1).Throttle(inputThrottleTime, scheduler)
.Subscribe(p => model.MyProperty = p);
}
[Reactive] public string MyProperty { get; set; }
}

You can use data binding. Chose your control component in view(xaml). After that you give a datacontext for this view. There are different ways for giving data context and item source for controls. You can do this in xaml file like this:
<Window.DataContext>
<vm:ViewModels.MainWindowViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Name}"/>
After that you can access variables. This variables must have get set properties and you can use ReactiveUI in here.
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
I used this in Avalonia. For more information you can look this: https://docs.avaloniaui.net/docs/data-binding

Related

In which class should wrapped EF entities be saved?

I have a WPF application which is written with an implementation of MVVM. There's no extra framework for the MVVM pattern.
My entities from EF db first are wrapped in their own viewmodels and I have a modelcontroller to load them into their viewmodels from a 'window' viewmodel.
Example of an entity viewmodel:
public class PurchaseOrderViewModel : ViewModels.ViewModelBase
{
private someType _prop;
public someType Prop
{
get
{
return _prop;
}
set
{
_prop = value;
OnPropertyChanged();
}
}
// ...
// Other Properties
// ...
public PurchaseOrderViewModel() {
// default constructor for LINQ
}
public PurchaseOrderViewModel(purchaseorder entity)
{
// load values from entity in properties
}
}
Example of a window viewmodel:
public class MainViewModel: ViewModels.ViewModelBase
{
private IModelController modelController = new ModelController();
private List<PurchaseOrderViewModel> _poList;
public List<PurchaseOrderViewModel> POList
{
get
{
return _poList;
}
set
{
_poList = value;
OnPropertyChanged();
}
}
// ...
// Other Properties
// ...
public MainViewModel()
{
POList = modelController.GetPurchaseOrders();
}
}
Example of ModelController:
public class ModelController : IModelController
{
public List<PurchaseOrderViewModel> GetPurchaseOrders()
{
using (var model = new DBContext())
{
return model.purchaseorders
.Select(new PurchaseOrderViewModel { /* assign properties */ })
.ToList();
}
}
}
Where am I supposed to save this wrapped viewmodel (PurchaseOrderViewModel) once the user is done editing it? As I see it, there are 2 options:
Create a save function in each viewmodel that points back to the modelController, but this feels like an inappropriate approach.
Create a save function in the modelcontroller and pass the viewmodel as an argument
It's most likely that I'm missing something in the MVVM pattern, but please point me in the right direction. Thank you!
EDIT: I excluded the view (MainView) from the info provided, but this view binds directly to the properties exposed by MainViewModel.
First up, I problably wouldn't name it ModelController as that's slightly confusing makes people think you are speaking MVC. Instead, if you call it xxxxService (e.g. PurchaseOrdersService) it makes more sense and it no longer feels "inappropriate" because having a VM delegate the actual work is what many users of IoC do. Plus it keeps your VM clean.
NOTE: By "service" I don't necessarily mean that your VM will be calling a WCF service directly (nor should you). Service is just a means to achieve something in an abstract and encapsulated way on behalf of clients. Examples include:
saving information to a DB
getting the current log mechanism
They can even be facades whereby they create a WCF client proxy and call a remote service on your behalf without you having to know the details.
So a typical flow is:
Command >> View code behind >> VM >> Service
The reason I include the view's code behind is that typically this is where you:
Catch exceptions
The starting point of async/await for asynchonous calls to your VM and service
Now when you pass context fromt the VM back to the service, there is no rule on what exactly you pass however I see no reason to pass VM to the service because that contains information the service doesn't care about.
Just pass the M which your VM should have bound to in the first place and continued to update via binding.

How to access text from a TextBox from one view and display it in a TextBlock on another view in WPF C#

I have a view in wpf, that has a range of different boxes, for example, First/Last Name (TextBox), Date of Birth (DatePickers), Marital Status (ComboBox) etc.
What I want to be able to do, is get the text entered into the TextBoxes and show them in a TextBlock on a seperate view.
I have added properties for all the corresponding items in there retrospective ViewModels, but from there on in, I'm unsure on how to implement this any further.
Other questions I have looked at aren't very clear or easy to follow.
You simply need to set the DataContext of both views to the same instance of your ViewModel.
<StackPanel>
<Local.EditableView DataContext={Binding Person} />
<Local.ReadOnlyView DataContext={Binding Person} />
</StackPanel>
There are multiple ways to achieve this. I assuming you are not using any framework like Caliburn.Micro.
Simple Approach:
Create a global static class that shares information across multiple ViewModel.
Now, from the first ViewModel, update the static class property using the ViewModel property setter, something like
private string _lastName;
public string LastName{
get{
return _lastName
}
set{
_lastName = value;
SharedClass.LastName = value;
}
}
Now access this shared class from the other ViewModel.
One approach is to use a Mediator to communicate between view models.
You would typically register a "target" view model -- "colleague" -- with the mediator for certain operations that the view model is interested in and provide a callback action for what is supposed to happen when that operation occurs. Then the other view model -- the one performing the operation that the target is interested in -- would notify the mediator when the operation happens, and the mediator would then perform the associated action on all the colleagues that are registered for that operation.
Here's an example of a mediator:
static class Mediator
{
private static Dictionary<string, List<Action<Object>>> _tokenCallbacks
= new Dictionary<string, List<Action<object>>>();
internal static void Register(string token, Action<Object> callback)
{
token = token.ToLower();
if (_tokenCallbacks.ContainsKey(token))
{
var l = _tokenCallbacks[token];
var found = false;
foreach (var existingCallback in l)
{
if (existingCallback.Equals(callback))
{
found = true;
break;
}
}
if (!found) l.Add(callback);
}
else
{
var l = new List<Action<Object>>(new[] { callback });
_tokenCallbacks.Add(token, l);
}
}
internal static void NotifyColleagues(string callbackToken, Object args)
{
callbackToken = callbackToken.ToLower();
if (_tokenCallbacks.ContainsKey(callbackToken))
_tokenCallbacks[callbackToken].ForEach((x) => x(args));
}
}
Those views and their view models should reference a shared model of the data.
So that when data is entered in one view, its view model updates the model and the model update triggers a update in the other view model and finally in the other view.
If you have lots of cross viewmodel communication, use Messenger. That acts as a mediator and simplified lots of issues like this. You can either implement one yourself or use either the MVVM light or Prism toolkits.

Correct way of getting unique ViewModels from MVVM Light ViewModelLocator

In my UWP app im using MVVM Light and its ViewModelLocator to get ViewModels.
My ViewModelLocator looks like this, im passing guid to GetInstance to get unique VM.
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MyViewModel>();
}
public MyViewModel MyVM => ServiceLocator.Current.GetInstance<MyViewModel>(Guid.NewGuid().ToString());
}
I have usercontrol which needs to have unique VM, as I can have multiple instances of this user control in my app. Here is how im getting the ViewModel:
<UserControl x:Class="My.App.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Path=MyVM, Source={StaticResource ViewModelLocator}}">
...
</UserControl>
Is this the correct way of getting unique VM's? Or are the VM's still cached, not disposed, even when my user control is not used any more?
UPDATE
So it seems that my code works ok, I get unique MyVM instance every time.
Now the question is, what is the correct way to unregistered/dispose view model.
I can do it with SimpleIoc.Default.Unregister(guid) but with my current implementation it is not very straight forward to deliver Guid (which was used in creating VM) to my user control, so I can unregister the VM.
Overall if I just create my VM runtime im ViewModelLocator with out SimpleIoc, is there any other drawbacks than losing dependency injection?
public class ViewModelLocator
{
public MyViewModel MyVM => new MyViewModel();
}
I assume your UserControl must be used by some View (window).
The ViewModel controlling that View could spawn the required ViewModels for the UserControl. You could bind the UserControl to this 'SubViewModel' property and also dispose of them however you then wished.
You can add an additional property to your view model and call a custom removal method in the view disposing your view model.
The modified getter for the view model looks like this:
public MyViewModel MyVM
{
get
{
String id = Guid.NewGuid().ToString();
var instance = SimpleIoc.Default.GetInstance<MyViewModel>(id);
instance.ID = id;
return instance;
}
}
The method for disposing the view model locator looks like this:
public static void UnregisterMyVM(String id)
{
SimpleIoc.Default.Unregister<MyViewModel>(id);
}
In your view you got to listen for closing events and call the unregistration method there:
public MyView()
{
InitializeComponent();
this.Closed += ((sender, arguments) =>
{
var viewModel = ((MyViewModel)this.DataContext);
viewModel.Dispose();
ViewModelLocator.UnregisterSourceCodeViewer(viewModel.ID);
this.DataContext = null;
});
}

Adding INotifyPropertyChanged to Model?

I'm facing some design questions in my wpf MVVM (Prism based) application, would be happy to get your advice.
My model is very simple:
public class Customer
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
As you can see, I don't have any INotifyPropertyChnaged support for my Model class.
I also have ViewModel for the CustomerDetails screen, that support INotifyPropertyChanged.
public class CustomerDetailsViewModel:INotifyPropertyChanged /*Or NotificationObject*/
{
/*INotifyPropertyChanged event */
private Customer item;
public Customer Item
{
get{return item;}
set
{
item=value;
//Raise PropertyChanged
//Set IsDirty to true
}
}
}
In my view, i'm using binding to the Item.FirstName and my ViewModel being updated.
My problem is - since only the FirstName property is being updated via the View, and the Model itself does not support INotifyPropertyChanged, hence the Item setter not being called, and the IsDirty remains equal to false (and therefore does not update the IsDirty notification on the UI).
I know I can support INotifyPropertyChanged in the model, and then register to the Item.PropertyChanged event in the view model, and actually set the IsDirty to true, But -
Since I'm also using CodeFirst, and my Model class shared between my ServerSide and my client side (Not using Add Service Reference), I don't want to add the INotifyPreoprtyChanged stuff to my server side.
I'm considaring creating new project, that will use T4 templates to copy one by one all my Entities (as Customer) and adding INotifyPropertyChanged support to each and every model.
Is that something that seems reasonable or not? any other suggestions?
Thanks!
Option1.
Separate entities, which being transferred between client and server (DTO), from entities, which are models on the client side. Implement INPC in models. Use mapping between these entities.
Option2.
Bind view to view model properties only. Make view model properties, which wrap corresponding model properties.
Option 3.
Is a mix of first two options. Do not aggregate model in view model. Use mapping between model and view model. Make view model properties, which correspond to model properties.
Well your approach is simply not the best. Much better would be to use a VM like this
public class CustomerDetailsViewModel : INotifyPropertyChanged
{
public CustomerDetailsViewModel(Customer customer)
{
_item = customer;
}
private Customer _item;
public string FirstName
{
get { return _item != null ? _item.FirstName : null; }
set
{
if (_item == null)
_item = new Customer(); // just an example, probably it's not the desired behavior
_item.FirstName = value;
RaisePropertyChanged(...);
}
}
...
}
This would stick to the spirit of MVVM.
If you want your UI to notice when your model property changed, your model class MUST implement INotifyPropertyChanged and similar MVVM interfaces (IDataErrorInfo, etc...) in order to Notify to the UI that the property changed.
That's because you are not always updating your model from the the viewmodel, where you must implement INotifyProperyChanged and notify for changes.
Wrapping corresponding model properties in the viewmodel used when you cannot implement INotifyPropertyChanged in the model class, which makes the viewmodel to grow VERY fast and creates unnecessary code duplication.
Scenario for example:
public class Customer
{
public string FirstName {get;set;}
public string LastName {get;set;}
// Changes the first name.
public void ChangeFirstName(string newName)
{
FirstName = newName;
//The UI will never know that the property changed, and it won't update.
}
}
Solution:
Implement INotifyPropertyChanged in you model class, create backing fields to your properties, and for each property setter, AFTER the set operation, raise OnPropertyChanged invoked method with the property name.
If you don't like to clutter your model with INotifyPropertyChanged code you could try using a NUGet package called PropertyChanged.Fody
You can use it like this;
using PropertyChanged;
[ImplementPropertyChanged]
public class Customer
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
Any public property in this class will now support INotifyPropertyChanged
I think you are on the right track. In the server side, you do not need INotifyPropertyChanged, thus do not add it to the domain classes in the server side.
You may just add some build symbols such as "WPF" to your client projects; and in the code first definitions implement INotifyPropertyChanged only if there is "WPF" build symbol. Then just add your server side domain classes as links to your presentation application. Something like;
#if WPF
public class MyEntity : INotifyPropertyChanged
#else
public class MyEntity
....

Pass a parameter to a ViewModel and show its data

I have created an application which uses WPF and MVVM following this article from CodeProject.
I have a view, TVSeriesView, which has a TVSeriesViewModel. These two are connected using a DataTemplate which is done following the article.
<DataTemplate DataType="{x:Type Implementation:TVSeriesViewModel}">
<TVSeriesLibrary:TVSeriesView />
</DataTemplate>
The idea is to pass my model, the TVSeries, to this ViewModel as I have a property named TVSeries in the ViewModel. When this property is set, I will populate other properties such as Title, Cover and so on. These properties are meant to be binded to controls in the view.
public class TVSeriesViewModel : ViewModelBase, ITVSeriesViewModel
{
private TVSeries _tvSeries;
private string _title;
private ImageSource _cover;
public TVSeries TVSeries
{
get
{
return this._tvSeries;
}
set
{
this._tvSeries = value;
}
}
public string Title
{
get
{
return this._title;
}
set
{
this._title = value;
OnPropertyChanged("Title");
}
}
public ImageSource Cover
{
get
{
return this._cover;
}
set
{
this._cover = value;
OnPropertyChanged("Cover");
}
}
}
First and foremost, does this sound like the right way to do it?
Next, does anyone know how to pass a parameter (a TVSeries object) to the ViewModel when the TVSeriesView is shown?
And lastly, does anyone know how I can directly access resources in the view? For example if I don't want to use data binding but instead want to set the image directly like this:
myImage.ImageSource = myImageSource
The View and ViewModel together are one of the possible representations of the Model.
You can pass a repository handle which would be eventually responsible for data access or
Concrete/abstract object of Model through Dependency Injection via Constructor or
Dependency Injection, via property/method or
In more crude way you can write a DB access code in your VM (obviously it's not suggested.)
I would prefer as the order given here. Your code is doing the third option.

Categories