MvvmCross navigation on screen - mvvm

Our designer created a layout something like the screen above. The main idea was to create an application with only one screen, just the red part of the screen is changing (i.e. 2 textbox instead of 1 textbox) when you tap on a button. This application will be a multiplatform application and I'm using MvvmCross to create it. My question is that how can i achieve this behavior in Mvvm? My first thought was sg. like the code below, but I'm not satisfied with this solution. Do you have any better solution to this problem? Should i somehow overwrite default navigation on ShowViewModel()?
public class MainViewModel : MvxViewModel
{
private MvxViewModel _currentViewModel;
public MvxViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; RaisePropertyChanged(() => CurrentViewModel); }
}
public MainViewModel()
{
CurrentViewModel = new DefaultViewModel();
}
public void OnButtonClick()
{
CurrentViewModel = new SecondViewModel();
}
}
public partial class MainViewModel : MvxViewController
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
FirstViewModel.WeakSubscribe(ViewModelPropertyChanged);
}
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "CurrentViewModel")
{
if (Model.CurrentViewModel != null)
{
if (Model.CurrentViewModel is SecondViewModel)
{
//remove bindings
//change View
//bind new viewmodel
}
}
}
}

The alternatives for this kind of 'non-page navigation' are similar to those in MvvmCross Dialog:
You can:
Customize the MvxPresenter to allow ShowViewModel to be used
Put a special interface in the Core project and use Inversion of Control to inject the implementation from the UI project to the Core project
Use the MvxMessenger plugin and share messages between the Core and UI project which trigger this type of navigation.
Use a property with a special interface (like IInteractionRequest) on the ViewModel - that property will fire an event when the UI needs to change.
Personally, for your situation, I quite like the first of these options - intercepting ShowViewModel using a presenter.
One other alternative which I might consider is to use some kind of 'Adapter-driven' control which could very easily update it's child contents based on the CurrentViewModel property. On Android, this would be as easy as using an MvxLinearLayout with an adapter. On iOS, however, I think you'd have to write something new to do this - just because iOS doesn't really have a LinearLayout/StackPanel control.

Related

Where is good place to register Messenger responsible for showing Windows to ensure MVVM pattern Separation of Concerns and Testability not violated?

Scenario:
MainWindow has a Menu About which relates to AboutWindow.
About Meny is triggered by command:
<MenuItem Header="_About" Command="{Binding OpenAbout}"/>
OpenAbout is property like that:
private RelayCommand _openAbout;
public RelayCommand OpenAbout
{
get
{
return _openAbout ?? (_openAbout = new RelayCommand(() => Messenger.Default.Send(new NotificationMessage("ShowAboutView"))));
}
}
Notification message is registered in App.cs class as follows:
static App()
{
DispatcherHelper.Initialize();
}
public App()
{
RegisterMessenger();
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, ProcessShowAboutView);
}
private void ProcessShowAboutView(NotificationMessage message)
{
AboutWindow view = new AboutWindow();
view.Show();
}
I analysed another questions like that:
How to open a new window using MVVM Light Toolkit
WPF MVVM - How to Show a view from MainWindowViewModel upon Clicking on button
I like Messenger functionality but however I am not sure If above solution is a good one.
I would be thankful for any advise!
As depicted above, Registering messages is done in App Config.
I consider it not be a good place therefore I need to know what place would be better.
Another place to consider would be Locator
I personaly would register the messages in App.xaml.cs in the OnStartup method (WPF) and in the set up method of the unit test (dont forget to unregister everything in the tear down method).

Show AlertDialog from ViewModel using MvvmCross

I am using MvvmCross for creation my Android-app and I faced with the following problem:
When I'm trying to show AlertDialog, that was created in ViewModel, the
"Unhandled Exception: Android.Views.WindowManagerBadTokenException" appears.
public class MyViewModel : MvxViewModel
{
public ICommand ShowAlertCommand { get; private set; }
public AuthorizationViewModel()
{
ShowAlertCommand = new MvxCommand(() =>
{
var adb = new AlertDialog.Builder(Application.Context);
adb.SetTitle("Title here");
adb.SetMessage("Message here");
adb.SetIcon(Resource.Drawable.Icon);
adb.SetPositiveButton("OK", (sender, args) => { /* some logic */});
adb.SetNegativeButton("Cancel", (sender, args) => { /* close alertDialog */});
adb.Create().Show();
});
}
}
When I was researching I have found that it happens because of transmission of the reference to the Context but not on the Activity in the AlertDialog.Builder.
In this topic I found the following decision:
Receive references to the current Activity through the use of GetService(), but I didn't found mvvmcross plugins for work with IMvxServiceConsumer, IMvxAndroidCurrentTopActivity interfaces.
My question is can I show AlertDialog from ViewModel? And how can I get the reference to Activity, but not to the Application.Context?
And what is the correct way to close AlertDialog that the user would stay on the current View?
In general, you should try not to put this type of code into ViewModels
because ViewModels should stay platform independent
because ViewModels should be unit testable - and it's hard to unit test when the code shows a dialog
I'd also recommend you don't put code like this inside a ViewModel Constructor - these constructors are generally called during navigations and displaying a Dialog during a transition is likely to be problematic.
With those things said, if you do want to get hold of the current top Activity within any code, then you can do this using the IMvxAndroidCurrentTopActivity
public interface IMvxAndroidCurrentTopActivity
{
Activity Activity { get; }
}
Using this, any code can get the current Activity using:
var top = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
var act = top.Activity;
if (act == null)
{
// this can happen during transitions
// - you need to be sure that this won't happen for your code
throw new MvxException("Cannot get current top activity");
}
var dlg = new AlertDialog.Builder(act);
//...
dlg.Create().Show();
The use of IMvxAndroidCurrentTopActivity is discussed in MvvmCross: How to pass Android context down to MvxCommand?
The approach taken in that question/answer is also one of the ways I would generally approach showing dialogs from a ViewModel:
I would create an IFooDialog interface
Ideally I would probably make this interface asynchronous - e.g. using async or using an Action<DialogResult> callback parameter
on each platform I would implement that in the UI project
the ViewModels can then use IFooDialog when a dialog is needed and each platform can respond with an appropriate UI action
This 'Dialog Service' type of approach is common in Mvvm - e.g. see articles like http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern (although that article is very Windows specific!)
There are also a few other questions on here about MvvmCross and dialogs - although they may contain reference to older v1 or vNext code - e.g. Alerts or Popups in MvvmCross and Unable run ProgressDialog - BadTokenException while showind

MvvmCross: How to navigate to something besides a ViewModel?

What would I put in my MvxCommand to navigate to a simple URL? All mobile platforms have a mechanism to ask the OS for an Activity or ViewController that can display the contents of a URL. How would I do that with MvvmCross? One way that I know of is to put special stuff in the presentationBundle and/or parameterBundle when calling ShowViewModel that the presenter can detect to do the special OpenUrl command. But is that the best way??
There is a plugin which enables this - https://github.com/slodge/MvvmCross/tree/v3/Plugins/Cirrious/WebBrowser
If that plugins is loaded, then a viewmodel can use:
public class MyViewModel : MvxViewModel
{
private readonly IMvxWebBrowserTask _webBrowser;
public MyViewModel(IMvxWebBrowserTask webBrowser)
{
_webBrowser = webBrowse;
}
public ICommand ShowWebPage
{
get { return new MvxCommand(() => _webBrowser.ShowWebPage("https://github.com/slodge/mvvmcross");
}
}
You can see this used in, for example:
https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.Core/ViewModels/BaseViewModel.cs
https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CustomerManagement/CustomerManagement/CustomerManagement/ViewModels/DetailsCustomerViewModel.cs
If you ever need to create your own plugins, see https://speakerdeck.com/cirrious/plugins-in-mvvmcross

MVVM: IView vs. databinding

I am reading about MVVM in order to adopt this across my presentation layers' views. Ideally I would like to use the same approach for WinForms, ASP.NET and SL.
I came across 2 distinct approaches and I would like to gather opinions in these (and possibly others):
The 'View with interface' and where the View is databound to the ViewModel
'View with interface'
In this approach we have an interface IView that contains:
- Set/Get properties for the typical field values
- Events for actions that happen on the View
The way this works is by a concrete implementation of the IView being injected into the ViewModel. The ViewModel then wires up the
events. It also then 'pushes' and pulls field values via the properties. It is also aware of what happens on the View by means of
the events. Controls can be activated and deactivated through the IView properties.
Concrete implementations of the view is easy in WinForms, not sure about the other technologies and the 'Blendability' in SL.
public interface IMyView
{
event EventHandler SomeActionClicked;
Boolean CanEditField1 { get;set; }
string Field1 { get;set; }
}
public class MyConcreteView: Form,IMyView
{
public event EventHandler SomeActionClicked;
public Boolean CanEditField1
{
get { return edtField1.Enabled; }
set { edtField1.Enabled = value; }
}
public string Field1
{
get { return edtField1.Text; }
set { edtField1.Text = value; }
}
private void btnAction_Click(object sender,EventArgs e)
{
SomeActionClicked(sender,e);
}
}
public class ViewModel
{
public ViewModel(IMyView view)
{
this.view = view;
view.SomeActionClicked += SomeActionHandler;
}
private void SomeActionHandler(object sender,EventArgs e)
{
view.CanEditField = !view.CanEditField; // Or whatever 'state' the ViewModel or Model is
view.Field1 = DateTime.Now.ToString(...);
}
private IMyView view;
}
Databound View
The other approach is a ViewModel that has several properties that reflect the Model (data/field values, UI control states, etc.
The View then uses databinding to 'present' the field values (Model) in the UI controls on the View. The ViewModel also controls
UI control states through properties being databound to. Actions in the View is fed to the ViewModel through methods in the
ViewModel that are hooked to.
Any (non-apparent) pros & cons to each of these methods?
Maybe you would actually only need the view as a "presenter" of the properties in winforms, while in SL all the wiring up is done trough the xaml, that would require less boiler plate.
So keep your view models in a common code base, while the view is specific to winforms.
For ASP.NET, those viewmodel events are not so sweet to hook up, unless you go for ugly oldschool webforms controls and postbacks.
I dont really see the difference between the two approaches, both would work with SL.
Keep it simple.

In Prism (CAL), how can I RegisterPresenterWithRegion instead of RegisterViewWithRegion

I have a module in a Prism application and in its initialize method I want to register a presenter instead of a view with a region, i.e. I want to do this:
PSEUDO-CODE:
regionManager.RegisterPresenterWithRegion(
"MainRegion", typeof(Presenters.EditCustomerPresenter));
instead of loading a view like this:
regionManager.RegisterViewWithRegion(
"MainRegion", typeof(Views.EditCustomerView));
The presenter would of course bring along its own view and ultimately register this view in the region, but it would allow me to bind the presenter to the view in the presenter's constructor instead of binding the two together in XAML (which is more of a decoupled MVVM pattern which I want to avoid here).
How can I add a Presenter to a Region instead of a view?
namespace Client.Modules.CustomerModule
{
[Module(ModuleName = "CustomerModule")]
public class CustomerModule : IModule
{
private readonly IRegionManager regionManager;
public CustomerModule(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void Initialize()
{
regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.EditCustomerView));
}
}
}
I think your presenters should be responsible for inserting them into the region when they are activated. I usually create an IViewRegistry for my presenters to use that avoids them knowing about region names and have their presenters use this to show the view.
public class MyViewPresenter : IPresenter
{
IViewRegistry _viewReg;
IUnityContainer _container;
public MyViewPresenter(IViewRegistry viewRegistry, IUnityContainer container)
{
_viewReg = viewRegistry;
_container = container;
}
void IPresenter.Present()
{
MyView view = _container.Resolve<MyView>();
MyViewViewModel vm = _container.Resolve<MyViewViewModel>();
view.DataContext = vm;
_viewReg.ShowInMainRegion(view);
}
}
And then of course, the implementation of ShowInMainRegion would be that region registry code you already have.
public void ShowInMainRegion(object view)
{
regionManager.RegisterViewWithRegion(
"MainRegion", view);
}
There is a possibility you could do something that is more like what you want (a region adapter that detects an IViewFactory, maybe), but it's probably not practical.
Hope this helps,
Anderson
I'm still quite new to Prism but as I understand it, that doesn't make sense: Regions are designed to hold Views, aren't they. That's all they exist for. What are you hoping to get from your Region that your Presenter can use?
Given that your Presenter knows all about your View, can you use your Presenter in your RegisterViewWithRegion call:
regionManager.RegisterViewWithRegion(
"MainRegion", typeof(Presenters.EditCustomerPresenter.View));
You can try using the RegisterViewWithRegion overload that takes a delegate instead of a view type.
For example:
regionManager.RegisterViewWithRegion(RegionNames.APPLICATION_MANAGEMENT_REGION, OnGetManagementView);
public object OnGetManagementView()
{
return m_managementViewModel.View;
}
This will allow you to have your own custom logic for creating the view/viewmodel(aka presenter). The callback will be called when the named region is found.
My approach to this is to register the view with the region by passing it the View property of a resolved presenter
this.regionManager.RegisterViewWithRegion(
FoundationToolkitRegionNames.RIBBON_REGION,
() => this.container.Resolve<SetupRibbonTabPresenter>().View);
My presenters constructor would look like this:
public SetupRibbonTabPresenter(ISetupRibbonTabView view)
{
this.view = view;
}
Both the view and presenter have previously been registered in the container.