Catel InterestedIn OnViewModelCommandExecuted check which command executed - c#

I use the Catel 4.3.0 framework.
I've decorated one of my ViewModels with
[InterestedIn(typeof(AddSupplierWindowViewModel))]
and added
protected override void OnViewModelCommandExecuted(IViewModel viewModel,
ICatelCommand command, object commandParameter)
This event fires correctly.
In the AddSupplierWindowViewModel I've multiple commands, but only the CmdAddSupplier is of interest to this viewmodel.
So I want to check if this command is fired, but I can't figure out how to test for it.
I expect something like
command.Name == "CmdAddSupplier"
but I can't find something like this.
Could someone provide an example of how to test for this.
Kind regards
Jeroen

We recommend to use CommandContainers instead of view models to host commands that span more than 1 view model. The InterestedIn communication will probably (most likely) be removed from Catel in v5.
Some good examples can be found in the Orchestra repository.
If you really want to keep using the InterestedIn (which is actually a shortcut for setting up services to take care of the communication between the view models), you'll need to figure out how to retrieve the instance (you only get an instance of a command) to a command name. For example, you could use the Tag property on the command.

Related

Reactive UI Routing and UWP

I am struggling with ReactiveUI routing for my UWP Navigation View. Since Navigation View Item does not implement command I use ItemInvoked event and execute my command in my view model. Unfortunately, I am unable to show another page in the view. I was using the official tutorial and also Reactive UI UWP Example. When using breakpoint I can see that my command is executed but nothing happens. I have no clue how to debug this more. Did anyone implement Navigation View wit ReactiveUI Routing?
My code: My repo
#Edit
POCOObservableForProperty: The class InwentarzRzeczowy.UWP.Views.MainView property ViewModel is a POCO type and won't send change notifications, WhenAny will only return a single value!
POCOObservableForProperty: The class InwentarzRzeczowy.UWP.Views.MainView property RoutedViewHost is a POCO type and won't send change notifications, WhenAny will only return a single value!
I believe you need a .Subscribe() after the AddPage.Execute() command in your event handler. I'm doing this from memory though and I remember something like that tripping me up.
Based on the conversation we had in Slack, posting the solution here as well. With ReactiveUI routing, you have to either register the views into Splat.Locator.CurrentMutable before using the router (see View Location) or write a custom view locator that matches your view models and returns the views (see Routing). The latter option expects you to implement the IViewLocator interface and assign it to RoutedViewHost.ViewLocator property. So, in C# code we generally have this:
public class YourViewLocator : IViewLocator
{
public IViewFor ResolveView<T>(T viewModel, string contract = null) => viewModel switch
{
NewEntryViewModel _ => new NewEntryView(),
// Also match other routable view models...
// The RoutedViewHost will initialize the ViewModel
// properties of your views automatically.
_ => throw new System.NotImplementedException()
};
}
And in XAML markup we have this:
<reactiveUi:RoutedViewHost Router="{Binding Router}">
<reactiveUi:RoutedViewHost.ViewLocator>
<yourAssembly:YourViewLocator />
</reactiveUi:RoutedViewHost.ViewLocator>
</reactiveUi:RoutedViewHost>
Also, it is important to initialize the IViewFor.ViewModel property for the root view. This could be done in the constructor of your view right after a call to this.InitializeComponent(), or in the composition root of your application.

Use same viewmodel different windows

My project is build on MVVM. Currently I have a list where i can select an object and add them to another list. What I want to make is a new window where this list is shown (the list with objects that are added) and edit that list in the new window (delte an item from that list).
How should I pass the data (selected object) to another window and be able to update them there?
I currently have it working in one view. In some related questions they advice MVVM light so I tried looking for that, from what I red mvvm light is mostly used to replace the notify property change. Should I use mvvm light or are there some specific patterns I could use?
Both windows will be open at the same time.
If you want to share your ViewModel between windows, you can use a ViewModelLocator. It is not specific to MvvmLight, it just creates one for you with its project template. You can implement it yourself, it is basically a container for your ViewModels. You can look here for the implementation details.
I've got to say that I'm not sure that these are the best approaches and if they are common, it's just what me and my colleagues were using in a WinRT application, so I'll be really glad if someone comes up with something better (both of these are not that clean).
I can think of two ways to pass data (without persisting it)
Pass parameters on page navigation
Have common shared class (Static or singleton class with some common data accessible from all ViewModels)
For passing on navigation:
I have this method in my Navigation service class:
public virtual void NavigateTo(Type sourcePageType, object parameter)
{
((Frame)Window.Current.Content).Navigate(sourcePageType, parameter);
}
and I use it like this in navigation commands:
Navigation.NavigateTo(typeof(PageType), someParameters);
Then you could get the values in the code behind of the navigated page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var receivedParameter = e.Parameter as TheTypeOfThePassedParameter;
}
And from there to pass them to the ViewModel, maybe there is an option to pass this without code in the code behind but I've not tried this.
Having shared class:
This is pretty much straightforward just have static class or a singleton with the desired fields.

Proper way to communicate/pass values between viewmodels?

I know there's a lot of questions on the topic and I understand how to do it but I need some help on the design of my architecture. I'm using the Simple MVVM Toolkit.
Architecture
I have a ShellVM which is the main VM for my app. It dishes out navigation and props that my main view binds to.
Then I have a ManageVM that does all the grit work for managing the client, stores, imports, exports etc. It also handles navigation of all my management views.
Then I have an ImportVM that fleshes out the importing of data.
I also have a static PageValues dictionary that stores pages and specific properties and values that should be retained when switching views. It also stores any 'global' properties that is used throughout certain VMs.
I'm using Messaging to pass data between the VMs. The validation and prompts (using dialogs) of the PageValues data is controlled in my ManageVM. I placed it here as I feel my ManageVM should handle all 'management' like setting the client and store. Setting the actual values is done by sending a message to the ShellVM that handles this.
The ShellVM handles the CRUD of the PageValues. So in other words, if any VM gets or sets a global/shell-wide property, it does so by means of messaging to the ShellVM. The ShellVM then sends the message/result back to whichever VM requested it.
Question
This feels very spaghetti-like. I've got a ManageVM that does the loading and validations on PageValues that are actually CRUD'ed in the ShellVM.
Am I on the right track or is there any other suggestion I can try to make this feel a bit cleaner?
Thanks for reading.
Edit
What I'm trying to achieve is to have a container that holds values (ie client and store) that could be accessible from multiple VMs. A bonus is to have each page's/view's values in this container too. Then on showing of the view, it will grab its values from the container and populate the view.
You said
if any VM gets or sets a global/shell-wide property, it does so by
means of messaging to the ShellVM
I propose an interface based approach instead of message passing for this purpose. ViewModels passing messages is for view models to communicate,not for setting a global state. If there is a global state of the application,it is better handled through a dedicated service, IMO.
public interface IApplicationService
{
//your applcation methods here
}
public class ApplicationService:IApplicationService
{
}
public class ManageVM
{
public ManageVM(IApplicationService){}
}
public class ShellVM
{
public ShellVM(IApplicationService){}
}
public class SomeOtherVM
{
public SomeOtherVM(IApplicationService){}
}
Yes, this does sound rather messy. You need to try and isolate areas of functionality into their own VMs so they are not dependent on one another.
One of the tricks I use to do this is to try and imagine that I suddenly need to copy a blob of functionality (say one of your pageviews) into another application. How easy would it be? Would it be a case of just copying one VM and injecting a few dependencies? Or is the VM impossibly coupled to the rest of the app?
It's a bit difficult to give advice without knowing exactly what your app is doing, but really you want each PageVM to be in charge of it's own validation, and CRUD. Or, if the data is shared between many pages, then you need to pass in some kind of repository than the PageVMs can query for data. If validation logic is specific to some data, then put it on the model itself and just leave the presentation of that validation to the VM.
For global settings, I tend to pass around a settings object rather than using messaging.
Have a read up on inversion of control, and dependency injection. These can help you to keep objects loosely coupled because you can see exactly what other things your object is depending upon by looking at the constructor. If you are passing in half the application then it can serve as a warning alarm to try and reduce the coupling.

Should I put this function in View (code-behind) or in ViewModel?

I am creating a simple WPF Application. I've a function OpenFile:
private void OpenFile(string fileName)
{
if(!File.Exists(Helper.GetPath(fileName)))
{
MessageBox.Show("Error opening file");
}
else
{
//Code to handle file opening
}
}
Ideally where should this function be present? I feel it should be in .xaml.cs because it accesses a MessageBox which comes in the View part. But it also calls my Helper, which is in the model. So I also think it can be in the ViewModel. What is the advantage of having this in the View or in the ViewModel? Can someone help me with some pointers?
Thanks.
One of the advantages of placing it in the view model would be testability. You could write a unit test that checks that the message box is only displayed if the file exists for example (more accurately it would be an integration test if you are hitting the file system).
However, because you are using a message box directly, your test would never complete on a build server because the machine would be waiting for input from the user whilst the message box is displayed.
Therefore, I would work against an abstraction in your view model, so that you can mock the message box during tests.
This function must be in the ViewModel. You need to create an operation in your view for showing the error message and call this method instead of MessageBox.Show. Showing the message box needs to be done in the View.
Generally you should avoid implementing any business logic inside the View such as validating or handling a file.
If you're using Microsoft Prism you can use the IInteractionRequest interface to have the view create the MessageBox, but actually pass back the necessary response to the view-model.
If you are not using Microsoft Prism, then look at how this part works and either simulate it or use a framework that does something similar.
Basically, that code should go on your view-model for testability, but replace the line where you explicitly call the MessageBox and use the IInteractionRequest mentioned instead.
Here is the documentation pertinent to the scenario you're looking to implement: Chapter 6: Advanced MVVM Scenarios. Look at the section stated User Interaction Patterns.

Databinding to Classmember of Classmember

I need some help about WPF and Databinding.
Let's say I have a ClassA with a member of ClassB. ClassB has again a member,
maybe an int:
ClassB
{
public int MemberOfB { get; set; }
}
ClassA
{
private ClassB _theB;
public ClassB MemberOfA
{
get {return _theB;}
set
{
_theB = value;
// Need to do something here...
}
}
}
When I have a Databinding in XAML like this:
<TextBox Text="{Binding Path=MemberOfA.MemberOfB}"/>
Where the Datacontext of the Textbox is an object of type ClassA.
As you can see, i need to do some computations in the setter of MemberOfA in ClassA.
But with the databinding above, this setter is of course never called, because it binds
to the member of ClassB.
So, how can i get to be informed if the MemberOfA changes (when I Type something into the Textbox)? Are there any best practices?
(Didn't check the code in Visual Studio, so there may be some syntax errors).
Thanks, Walter
The best way to handle this would probably be to make B implement INotifyPropertyChanged. When A gets a new instance of B, have it hook up to the PropertyChanged event (and unhook from the old B's event if necessary).
public B MemberOfA {
get { return _b; }
set {
if (_b != null) { _b.PropertyChanged -= B_PropertyChanged; }
_b = value;
if (_b != null) { _b.PropertyChanged += B_PropertyChanged; }
DoWhatever(_b);
}
}
private void B_PropertyChanged(object sender, PropertyChangedEventArgs e) {
DoWhatever((B)sender);
}
Fundamental problem
In my opinion, all 3rd party vendors ought to implement at least one of NET Framework's standard notification mechanisms, which are:
The original XyzChanged event pattern
INotifyPropertyChanged
INotifyCollectionChanged
OnDependencyPropertyChanged
All of these are fully supported by WPF, so if your vendor implements any of these you should be able to just drop their objects into your WPF application and go.
Unfortunately you will find many 3rd party libraries that don't implement any of these mechanisms: Instead they implement their own custom change notification, or even none at all!
In the rest of this answer I will explain several possible soutions to this problem.
1. Wrap 3rd party objects in wrapper models
You may create a "wrapper model" object to parallel each 3rd party model object that doesn't implement standard notifications. Expose all the 3rd party's properties and methods as your own (yes, this is a lot of code). Register with the 3rd party object's nonstandard notifications mechanism and send standard notifications using INotifyPropertyChanged. This solution is labor-intensive and can be a maintenance headache, but it works well and is many times a good way to make the best of a bad situation.
Many people call these wrapper models "view models" but this terminology leads to confusion: A "view model" is fundamentally a model that models the current state of the user interface such as what the user has open right now, what objects are selected, the current search filter, etc. WPF data binding typically binds to the view model for these types of properties but directly to the model objects themselves for the actual data.
When model objects don't support standard notification, it is common to overload the use of the view model objects to expose both "real" view model properties and also properties from the underlying models. Thus the view model acts both as a real view model and also a wrapper model: The additional properties have nothing to do with UI state but are merely a convenient way to get proper change notification without using a separate wrapper model around the broken 3rd party object.
There is nothing inherently wrong with merging your wrapper model and your view model up into a single object, but it confuses the terminology. For many people I think "view model" has come to be synonymous with "wrapper model" and I think that's a shame. If the wrapper model is kept separate from the view model you get a cleaner interface in your XAML and can more easily adapt once the vendor fixes their change notification.
You may not have time to generate wrapper models for every 3rd party model object, or you may realize that doing so would be impractical due to maintenance issues, updates, or other considerations. It can be a pain to continually be adding and updating your wrapper model every time the 3rd party object changes. In this case I would consider one of the alternative solutions below.
2. Replace DataContext on notification
You may simply tap into the 3rd party's change notification mechanism, and every time an update is signalled simply clear out the relevant DataContext (set it to null), and set it again. This will cause all WPF data bindings to be re-evaluated.
This solution is a bit like killing a fly with a sledgehammer, but it actually works and gets the job done. If it is only simple objects it works well, but as things get more complex it can be messy. If you have ItemsControls or ContentPresenters that create visual trees for your items, setting DataContext to null and back will cause those visual trees to be regenerated. This means you will lose scroll positions, Expander settings, and other UI-specific state within them. You can solve this by binding those properties to a view model to preserve their state.
This solution may be the only solution if the 3rd party's change notification mechanism is broken or inconsistent. It also tends to be the simplest "quick and dirty" solution if you need to get something usable out the door TODAY.
3. Wrap using TransparentProxy / RealProxy
If your 3rd party library includes a sane and consistent but nonstandard change notification mechanism, you have another alternative: You can implement a generalized mapping between the 3rd party mechanism and the standard INotifyPropertyChanged mechanism using the TransparentProxy / RealProxy mechanism of NET Framework.
To do this:
Expose the INotifyPropertyChanged interface on the TransparentProxy, and on your RealProxy handle add_PropertyChanged and remove_PropertyChanged by registering with the 3rd party's event notification system
When the RealProxy receives a property get on a property, do any 3rd party registration required for change notification on that particular property (if not already done) before returning the property
Automatically convert each object returned by the 3rd party property getters into a TransparentProxy of the same object.
Now you can bind your DataContext to an intially-constructed TransparentProxy and from then on pretend the 3rd party library uses a standard change notification mechanism and is completely compatible with WPF.
The TransparentProxy / RealProxy solution is a lot of work to set up and is specific to the 3rd party library's notification mechanism, but once it is working you don't need to worry about out-of-sync UI or losing UI state during refreshes.
4. Use a timer
If the 3rd party library provides no change notifications at all, you can simply set a timer that periodically checks for changes.
Your timer will probably scan the 3rd party object for relevant changes from the values during the last scan. If any are found, it uses one of the previous techniques to provide a standard notification of the changes. In other words, it will signal the wrapper model or RealProxy of the change or it will clear/set a DataContext.
The simplest possible timer solution just periodically sets DataContext to null and then sets it back again without checking for changes.
5. Encourage your 3rd party to implement one of the standard mechanisms
NET Framework defines four different change notification mechanisms, all of which are fully supported by WPF, so it seems inexcusable for anyone to generate objects that support none of these.
Hopefully the next version of your third party library will properly implement one of these standard change notification mechanisms. Contact your vendor and ask them to do so, or at least to create bridge code.
The sad part is, at the moment Microsoft is the worst culprit of all: Neither LINQ to SQL nor LINQ to Entities implements standard change notification on their objects! I think that's too bad because people tend to follow Microsoft's example.

Categories

Resources