MvvmCross MvxWindowsPage<TViewModel> compilation error - c#

I am writing a Xamarin.iOS, Xamarin.Android and UWP cross-platform application with the MvvmCross framework.
I am making a LoginPage which has a LoginViewModel. In the Xamarin.iOS, Xamarin.Android projects, the binding of the ViewModel and the View with below works just fine
public class LoginActivity : MvxAppCompatActivity<LoginViewModel>
public partial class LoginViewController : MvxViewController<LoginViewModel>
Trying to do the same as above on UWP project, I get some error.
In XAML:
<views:MvxWindowsPage
x:TypeArguments="viewModels:LoginViewModel" x:Class="MyApp.UWP.Views.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:MvvmCross.WindowsUWP.Views"
xmlns:viewModels="using:MyApp.PresentationCore.ViewModels"
mc:Ignorable="d">
And my C# code is
public sealed partial class LoginView : MvxWindowsPage<LoginViewModel>
But I get compilation errors. How can I resolve them?
`Unknown member 'TypeArguments' on element 'MvxWindowsPage'
The name "LoginViewModel" does not exist in the namespace "using:MyApp.PresentationCore.ViewModels".
GenericArguments[0], 'System.Object', on 'MvvmCross.WindowsUWP.Views.MvxWindowsPage`1[TViewModel]' violates the
constraint of type 'TViewModel'.
I think the errors are a little ambiguous because at the first error there is no templated version, but the third error is about a template constraint violation.
I know there is an option binding the ViewModel and the View with naming convention or attributes, but I would like to use this strongly typed solution.

Unfortunately I believe UWP does not support TypeArguments and subsequently generic types parameters for a base pages. You can check out the Prism GitHub thread where they discuss it. So you will have to go with one of the other registration options.
Mvvmcross offers various alternative approaches for registering your View to a ViewModel. During initialisation of Mvvmcross it will attempt to register your ViewModel to your View using MvxViewModelViewTypeFinder in the following order:
Attribute based registration:
You can add MvxViewFor attribute to your page class.
[MvxViewFor(typeof(FirstViewModel))]
public sealed partial class FirstView : MvxWindowsPage
{
public FirstView()
{
this.InitializeComponent();
}
}
Concrete type based registration:
You can register your View to your ViewModel by specifying the concrete type of the ViewModel.
public sealed partial class FirstView : MvxWindowsPage
{
public new FirstViewModel ViewModel => base.ViewModel as FirstViewModel;
public FirstView()
{
this.InitializeComponent();
}
}
Or in the case of Android and iOS where generic base class can be used:
// Android
public class FirstActivity : MvxAppCompatActivity<FirstViewModel>
// iOS
public class FirstViewController : MvxViewController<FirstViewModel>
Convention based registration:
You can have your view and ViewModel follow the same naming convention and Mvvmcross will map them for you (xxxView and xxxViewModel)
View
public sealed partial class FirstView : MvxWindowsPage
{
public FirstView()
{
this.InitializeComponent();
}
}
ViewModel
public class FirstViewModel : MvxViewModel

Related

AvaloniaUI - What is the proper way to inject ViewModels into Views using composition-root based DI system?

I am new to Avalonia/ WPF, Xaml and desktop development in general so please forgive and clarify any related misunderstandings I demonstrate. I will continue to study available documentation but I am having a difficult time finding material which addresses the point I am getting stuck on.
I am trying to implement a composition-root, constructor-injection based dependency-injection system in my Avalonia application, using the recommended MVVM pattern and associated Avalonia project template. I have some familiarity with the Microsoft.Extensions.DependencyInjection package so have been trying to work with this system.
Between tutorials for WPF and Avalonia based on this DI framework as well as other frameworks, I have tried to piece together a working solution. I think I have things figured out conceptually as far as registering Services and ViewModels and setting up constructors for these classes appropriately such that the framework will inject dependencies into these classes on instantiation. However, where I am getting stuck is with how to implement constructor injection for View classes.
I attempted to register both MainWindow and MainWindowViewModel as services:
// App.axaml.cs
public partial class App : Application
{
private IServiceProvider _services;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
ConfigureServiceProvider();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = _services.GetService<MainWindow>();
}
base.OnFrameworkInitializationCompleted();
}
private void ConfigureServiceProvider()
{
var services = ConfigureServices();
_services = services.BuildServiceProvider();
}
private static IServiceCollection ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<MainWindow>();
services.AddTransient<MainWindowViewModel>();
return services;
}
}
The goal is then to be able to inject the MainWindowViewModel class into the MainWindow class via constructor and then assign that argument to the DataContext property of the MainWindow view-class:
// MainWindow.axaml.cs
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
However, this causes the following error to be raised:
MainWindow.axaml(1, 2): [XAMLIL] Unable to find public constructor for type MyApp.Client:MyApp.Client.Views.MainWindow() Line 1, position 2.
It seems the View cannot be instantiated without the existence of a parameter-less constructor, however, this would seem to prevent constructor injection.
It is very possible I have some fundamental misunderstanding about the intended relationship between ViewModels and Views. I have come across a number of examples where ViewModels are not registered with the service-container, and instead are instantiated directly in the View constructor and assigned to the DataContext property. I would prefer to avoid this approach.
Meanwhile, every tutorial I have come across which demonstrates injecting ViewModels into corresponding View classes, does so using the Service Locator pattern, where the DI service container is passed explicitly (or invoked as a global object) and the ViewModel is resolved explicitly from the container.
Can anybody direct me to any example source code or tutorial which demonstrates how to properly inject ViewModels into Views via constructor? Is this possible to achieve? Is there something I can modify in the MainWindow.axaml file to enable the desired behavior? Thank you for your time and again, I would greatly appreciate clarification of any misunderstandings I may have.
Just for reference, here is the MainWindow markup:
// MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.Client.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Client.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
x:CompileBindings="True"
Icon="/Assets/avalonia-logo.ico"
Title="MyApp">
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
View models are associated with views via DataContext instead of constructor injection. Note that a singe view can be reusable (especially if you are dealing with virtualized lists).
In general your DI should not know about most of the view part at all, it should be only concerned with ViewModel and lower layers.
Instead of being created via DI views are usually located via view locator by other views that bind particular properties to ContentControl, e. g.
<ContentControl Content="{Binding MySubViewModel} />
(you can find a simple view locator in avalonia.mvvm template, you can tune it for your needs).
When one needs to show a new top-level view from your view model code, they usually implement some kind of a window manager that manages top-level views and is accessible from the view model via DI, e. g.
public class ViewManager : IViewManager
{
private Window CreateWindowForModel(object model)
{
foreach (var template in Application.Current.DataTemplates)
{
if (template.Match(model))
{
var control = template.Build(model);
if (control is Window w)
return w;
return new Window { Content = control };
}
}
throw new KeyNotFoundException("Unable to find view for model: " + model);
}
public void ShowWindow(object model) => CreateWindowForModel(model).Show();
}
Then you add IViewManager implementation to your DI.
Note that this approach is reusable for all XAML frameworks and makes it possible to completely reuse the view model between various platforms (e. g. if you want to implement mobile UI with Xamarin and desktop with Avalonia) with only a few UI-toolkit specific services.

creating a class like App.xaml.cs in Class Library Project

So recently; I refactored Views to their own WPF Application project and I moved my ViewModel classes into their own Class Library project. This worked well for keeping my code in order. Then I realised that I didn't have the comfort of the App.xaml.cs class.
This class (for me) meant that I could declare all sorts of objects and access them application wide.
i.e: In the App.xaml.cs
public partial class App : Application
{
public myDatabaseEntities context { get; set; }
// App.xaml.cs Constructor
public App()
{
context = new myDatabaseEntities();
}
}
In some random View Model:
myDatabaseEntities context = ((App)Application.Current).context;
The above allows me to recylce the instance, and comes in particularly handy with Unity's (IoC container) version of lifetime manager.
Thing is, I'm not sure on how to achieve this behaviour within a class Library project. I'm not sure how to create a class that instantiates at runtime. And I have no clue how to pass that App class instance around to relevant classes. Any ideas on how to do this? Any help would be much appreciated.
Personally, I would keep all the "functionally" related Views and ViewModels together (next to each other). You may want to create class libraries (or modules) based for different functional parts of the application. Also, please refer to this MSDN page on building composite application using WPF and Prism.
Coming to your question, have an interface called IApplication defined something like this:
public interface IApplication
{
MyDatabaseEntities Context { get; }
}
and implement that interface on App class:
public partial class App : Application, IApplication
{
public MyDatabaseEntities Context { get; set; }
// App.xaml.cs Constructor
public App()
{
Context = new MyDatabaseEntities();
}
}
In your App.xaml.cs, as part of bootstrapping your application register this App instance with the container by calling RegisterInstance extension method on Unity container:
Container.RegisterInstance(typeof (IApplication), this, new ContainerControlledLifetimeManager());
Now, if your ViewModels take a dependency on IApplication, then they will have access to your App object and to the Context property via this interface. In future you could expose additional properties like: Dispatcher, Resources, etc from your App object through this interface.
Turns out all I needed was a regular class without the xaml front end. Then inherit the Application class. And lastly set it as the base class for app.xaml.cs. The answer is already here

Type not found in cache - UWP Windows 10

I've just recently added a new View and ViewModel to my UWP project which uses MVVMLight and I've spent the last few hours trying to figure out between this and the new files but can't see anything different.
This might be a duplicate post MVVM Light “Type Not Found in cache", but I feel it was never really answered.
Anyway, I've got a dozen or more ViewModels declared in my ViewModelLocator and they all work as expected except for my new one which is defined exactly the same way as the others:
SimpleIoc.Default.Register<UnlockFeaturesPageViewModel>();
My ViewModel is declared as follows:
public class UnlockFeaturesPageViewModel : AbstractPageViewModel
{
public UnlockFeaturesPageViewModel(ITelemetryService telemetryService,
IDataService dataservice, CurrentPageEnum currentPage) :
base(telemetryService, dataservice,
CurrentPageEnum.UnlockFeatures)
}
As you can see it inherits from a AbstractPageViewModel and that abstract class is quite straight forward as well:
public abstract class AbstractPageViewModel : ViewModelBase, IPageViewModel
{
public AbstractPageViewModel(ITelemetryService telemetryService,
IDataService dataservice, CurrentPageEnum currentPage)
{
this._telemetryService = telemetryService;
this._dataService = dataservice;
this._currentPage = currentPage;
}
}
And it contains other properties, methods that can be overwritten if needed. The ViewModelBase is the class from Galasoft.MVVMLight and IPageViewModel is a basic interface.
The error occurs when the InitialComponents(); in the CodeBehind is called and tried to initialized the DataContext which is defined as follows:
<Page x:Class="MyApp.Views.UnlockFeaturesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator},
Path=UnlockFeaturesViewModel}" >
and the following code is then called from the ViewModelLocator and this is where the error occurs:
public UnlockFeaturesPageViewModel UnlockFeaturesPageViewModel
{
get
{
return ServiceLocator.Current.
GetInstance<UnlockFeaturesPageViewModel>();
}
}
If I mouse my mouse over the , it displays the following error:
'UnlockFeaturesPageViewModel' threw an exception of type
'System.Reflection.TargetInvocationException'
and when I execute the code, it throws the following error:
TargetInvocationException was unhandled by user code
An exception of type 'System.Reflection.TargetInvocationException'
occurred in mscorlib.ni.dll but was not handled in user code.
and the InnerException contains the following details:
Message: Type not found in cache: MyApp.Constants.CurrentPageEnum.
Source: GalaSoft.MvvmLight.Extras
StackTrace: at GalaSoft.MvvmLight.Ioc.SimpleIoc.DoGetService(Type
serviceType, String key, Boolean cache) at
GalaSoft.MvvmLight.Ioc.SimpleIoc.MakeInstance[TClass]()
So why am I getting this error regarding MyApp.Constants.CurrentPageEnum not being found in the cache. I've added a new value to my CurrentPageEnum to match the newly added page but according to the error this is cached somewhere and it is not updated. I could be totally wrong but I can't think of anything else as the code is identical to other ViewModels that work.
It's definitely related to my AbstractPageViewModel as if my UnlockFeatureViewModel inherits from ViewModelBase (from Galasoft.MVVMLight), it doesn't throw any errors.
Regarding as to why this could be a duplicate post is because in the other similar post, the developer mentioned that it simply did not execute the following line in debug mode but he doesn't mention how he fixed it.
SimpleIoc.Default.Register<UnlockFeaturesPageViewModel>();
My problem is that it appears to be executed in debug mode as I'm stepping through it and it goes to the next line and as mentioned the error only occurs when it is attempting to set the DataContext of my page to that specific view model.
Strangely no error occur in Release Mode!
Any ideas what could be causing this and how I can resolve this problem?
Thanks.
Your UnlockFeaturesPageViewModel has an enum as dependency.
public class UnlockFeaturesPageViewModel : AbstractPageViewModel
{
public UnlockFeaturesPageViewModel(ITelemetryService telemetryService,
IDataService dataservice, CurrentPageEnum currentPage) : // This one, CurrentPageEnum do not belong here, it's enum and can't be resolved
base(telemetryService, dataservice,
CurrentPageEnum.UnlockFeatures)
}
Since the CurrentPageEnum is not a class that can be instantiated, the resolving fails.
You should remove the enum like this
public class UnlockFeaturesPageViewModel : AbstractPageViewModel
{
public UnlockFeaturesPageViewModel(ITelemetryService telemetryService,
IDataService dataservice) :
base(telemetryService, dataservice,
CurrentPageEnum.UnlockFeatures)
}
I had this error before, this error is produced when the ServiceLocator tries to inject all of your services in the viewmodel.
I'm not sure if it's an error of MVVM light or another thing. I solved this issue always using ServiceLocator in the constructor of my viewmodels for calling all my services without modify the constructor of all my viewmodels.
Example:
Public MyViewModel()
{
var myService= ServiceLocator.Current.GetInstance<IMyService>();
}

WPF Caliburn.Micro - Best way to navigate in Single Window Application

My history:
I'm developing a WPF application, which will run in full screen on the touch screen. Navigation in my application can be done only by clicking a button on each page ("back" or "logout").
This is not a Universal App, but it looks like.
Assumptions of the project:
Application will run on full screen mode in Windows 7 on the touch screen.
I'm using Caliburn.Micro MVVM framework.
Problem and question:
I've got 1 window and 3 UserControl (and ViewModels) Concept art
Window ShellView
UserControl LoginView
UserControl OrdersView
UserControl OrderDetailView
When application starting, i'm set LoginView as default and load it by using CM Conductor ActivateItem method, but i don't know how to set another View from UserControl like LoginView
I have read: this question but this doesn't cover my case
and this answer but it's to hard to understand for me.
My ideas:
make static method in ShellViewModel like:
ShellViewModel
public static void setOrdersView() {
ActivateItem(new OrdersViewModel());
// Error : An object reference is required for the non-static field, method, or property 'Caliburn.Micro.ConductorBase<object>.ActivateItem(object)
}
ShellViewModel.setOrdersView();
make listener in ShellViewModel and send event from child ViewModel ( but now i don't know how to achieve it)
Question: What is the best way to handle navigation in this case?
Application architecture:
ShellView
<Window>
<ContentControl x:Name="ActiveItem" />
</Window>
ShellViewModel
public class ShellViewModel : Conductor<object>, IShell
{
public ShellViewModel()
{
LoadDefault();
}
public void LoadDefault()
{
ActivateItem(new LoginViewModel());
}
}
LoginView
<UserControl>
<Button x:Name="Login" />
</UserControl>
LoginViewModel
public class LoginViewModel : PropertyChangedBase
{
public void Login() {
if (LoginManager.Login("User", "Password")) {
// How to redirect user to OrdersView?
}
}
}
I have similar applications with one shell window and many activated views inside and some dialog windows.
You should use EventAggregator pattern for these needs and Caliburn already has implementation.
How to Achieve:
Minimum Shell signature
public class ShellViewModel : Conductor<object>,
IHandle<ChangePageMessage>,
IHandle<OpenWindowMessage>
You need two fields inside (second one is for dialogs):
public IEventAggregator EventAggregator { get; private set; }
public IWindowManager WindowManager { get; private set; }
I've set single instances of that objects through IoC. You can also define them as Singletons.
EventAggregator needs subscription on object where IHandles are implemented.
EventAggregator.Subscribe(this); //You should Unsubscribe when message handling is no longer needed
Handlers implementation:
public void Handle(ChangePageMessage message) {
var instance = IoC.GetInstance(message.ViewModelType, null);//Or just create viewModel by type
ActivateItem(instance);
}
public void Handle(OpenWindowMessage message) {
var instance = IoC.GetInstance(message.ViewModelType, null);//Or just create viewModel by type
WindowManager.ShowWindow(instance);
}
Messages for event aggregator can be only marker classes but sometimes it is useful to pass more parameters like ours OpenWindowMessage and ChangePageMessage classes - they are absolutely similar by content, so for example:
public class OpenWindowMessage {
public readonly Type ViewModelType;
public OpenWindowMessage(Type viewModelType) {
ViewModelType = viewModelType;
}
}
All your viewModels can also subscribe to EventAggregator instance and Handle some messages for communication, or even for initial parameters. I have something like MyViewModelInitMessage classes almost for every viewModel, and just use two publish methods together.
EventAggregator.Publish(new ChangePageMessage(typeof(MyViewModel)));
EventAggregator.Publish(new MyViewModelInitMessage("...all needed parameters"));
So when I publish those two - mine ViewModel will be activated, then it subscribes to EventAggregator(don't forget to do that or second message handling never occurs), and will handle its InitMessage right after.
Now with EventAggregator you can send messages between all ViewModels that are currently subscribed to it.
That seems pretty common solution.

Form designer breaks on generic abstract UserControl

I have a generic abstract UserControl class, SensorControl, which I want all my sensor control panels to inherit from.
The problem
When attempting to design the EthernetSensorControl (one of my inherited UserControl forms, from within Visual Studio, the following error is displayed in the form designer:
The designer could not be shown for this file because none of the classes within it can be designed. The designer inspected the following classes in the file: DeviceSensorControl --- The base class 'Engine.Sensors.SensorControl' could not be loaded. Ensure the assembly has been referenced and that all projects have been built.
SensorControl class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Engine.Sensors
{
public abstract class SensorControl<SensorType>
: UserControl where SensorType : class
{
protected SensorType _sensor;
public SensorControl(SensorType sensor)
{
_sensor = sensor;
}
}
}
Example inherited class, EthernetSensorControl:
namespace Engine.Sensors
{
public partial class EthernetSensorControl
: SensorControl<EthernetSensor>
{
public EthernetSensorControl(EthernetSensor sensor)
: base(sensor)
{
}
}
}
And the call stack:
at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.EnsureDocument(IDesignerSerializationManager manager)
at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager manager)
at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager)
at System.ComponentModel.Design.Serialization.BasicDesignerLoader.BeginLoad(IDesignerLoaderHost host)
Everything compiles and I can see the panel displayed, but I can't design it. I think the problem may be related to the partial classes. Any ideas?
You cannot design a control or form that inherits an abstract class.
(The designer needs to instantiate the base class to serve as the design surface)
The base class also needs to have a parameterless constructor for the designer to call.
This constructor can be private.

Categories