Back to Devexpress

ViewModel Management

windowsforms-119492-cross-platform-app-development-winforms-mvvm-concepts-viewmodel-management.md

latest8.2 KB
Original Source

ViewModel Management

  • Dec 09, 2024
  • 4 minutes to read

This article describes how to retrieve ViewModel instances at runtime. Note that if you utilize the MvvmContext component to build your MVVM-applications at design-time, the component manages ViewModels automatically.

If a ViewModel follows the POCO concept, the MVVM Framework dynamically transforms this ViewModel to a new class containing the necessary infrastructure (to support, for instance, simplified data bindings). The Framework works with dynamically created class instances, which means you cannot initially access these instances at runtime because their types have not been determined.

Use the following options to retrieve a working ViewMode:

The ViewModelSource.Create method

In this approach, you create a ViewModel instance first, then call the SetViewModel method to associate this instance with the specific ViewModel type.

csharp
var mainViewModel = ViewModelSource.Create<MainViewModel>();
mvvmContext1.SetViewModel(typeof(MainViewModel), mainViewModel);
vb
Dim mainViewModel = ViewModelSource.Create(Of MainViewModel)()
mvvmContext1.SetViewModel(GetType(MainViewModel), mainViewModel)

The ViewModelBase class

You can inherit ViewModels from the ViewModelBase class that implements MVVM Framework features. In this case, you can create ViewModel instances directly. Note that you also need to call the SetViewModel method to specify that the Framework should use this instance when the ViewModel is required.

csharp
public class ViewModel : ViewModelBase {
    //. . .
}
var myViewModel = new ViewModel();
mvvmContext1.SetViewModel(typeof(ViewModel), myViewModel);
vb
Public Class ViewModel
  Inherits ViewModelBase

    '. . .
End Class
Private myViewModel = New ViewModel()
mvvmContext1.SetViewModel(GetType(ViewModel), myViewModel)

We do not recommend this approach because you lose all the features POCO models provide.

ViewModelCreate events

This approach is designed to work with dependency injection frameworks (such as Ninject). The example below illustrates how this injection works with the Ninject framework.

csharp
public class SamuraiViewModel {
    public IWeapon Weapon { get; private set; }
    public SamuraiViewModel(IWeapon weapon) {
        this.Weapon = weapon;
    }
    public void Attack() {
        Weapon.Hit();
    }
}

// Bind a dependency for IWeapon
kernel.Bind<IWeapon>().To<Sword>();

// Set up MVVMContext
var fluent = mvvmContext1.OfType<SamuraiViewModel>();
fluent.BindCommand(simpleButton1, x => x.Attack());
vb
Public Class SamuraiViewModel
    Private privateWeapon As IWeapon
    Public Property Weapon() As IWeapon
      Get
        Return privateWeapon
      End Get
      Private Set(ByVal value As IWeapon)
        privateWeapon = value
      End Set
    End Property
    Public Sub New(ByVal weapon As IWeapon)
        Me.Weapon = weapon
    End Sub
    Public Sub Attack()
        Weapon.Hit()
    End Sub
End Class

' Bind a dependency for IWeapon
kernel.Bind(Of IWeapon)().To(Of Sword)()

' Set up MVVMContext
Dim fluent = mvvmContext1.OfType(Of SamuraiViewModel)()
fluent.BindCommand(simpleButton1, Function(x) x.Attack())

In this scenario, you need dynamically generated ViewModels and bind them to interfaces (MVVM framework features are lost when binding interfaces to POCO directly). To obtain the required instances, handle the regular (local) or static (global) ViewModelCreate event as follows:

csharp
// Retrieve the live POCO ViewModel instance with the Ninject kernel
//regular event
mvvmContext1.ViewModelCreate += MVVMContext_ViewModelCreate;
void MVVMContext_ViewModelCreate(object sender, DevExpress.Utils.MVVM.ViewModelCreateEventArgs e) {
    // kernel.Bind<SamuraiViewModel>().To(e.RuntimeViewModelType);
    // e.ViewModel = kernel.Get<SamuraiViewModel>();
    e.ViewModel = kernel.Get(e.RuntimeViewModelType);
}
//static event
MVVMContextCompositionRoot.ViewModelCreate += (s,e)=> {
    e.ViewModel = kernel.Get(e.RuntimeViewModelType);
};
vb
' Retrieve the live POCO ViewModel instance with the Ninject kernel
'regular event
AddHandler mvvmContext1.ViewModelCreate, AddressOf MVVMContext_ViewModelCreate
void MVVMContext_ViewModelCreate(Object sender, DevExpress.Utils.MVVM.ViewModelCreateEventArgs e)
    ' kernel.Bind<SamuraiViewModel>().To(e.RuntimeViewModelType);
    ' e.ViewModel = kernel.Get<SamuraiViewModel>();
    e.ViewModel = kernel.Get(e.RuntimeViewModelType)
'static event
AddHandler MVVMContextCompositionRoot.ViewModelCreate, Sub(s,e) e.ViewModel = kernel.Get(e.RuntimeViewModelType)

The static ViewModelCreate event is a weak event. If it contains a closure to a variable (“kernel” in the example above or the “rootContainer” scope in the Autofac sample below) whose lifetime is shorter than the parent container’s lifetime, the VS Garbage Collector can prematurely collect this variable and the event handler may never be invoked.

csharp
[STAThread]
static void Main() {
    //rootContainer is declared at the Main method level
    IContainer rootContainer;
    var builder = new ContainerBuilder();
    builder.RegisterType<TestViewModel>();
    builder.RegisterType<MyClass>().As<IService>();
    rootContainer = builder.Build();
    MVVMContextCompositionRoot.ViewModelCreate += (s, e) => {
        using (var scope = rootContainer.BeginLifetimeScope(
            b => b.RegisterType(e.RuntimeViewModelType).As(e.ViewModelType))) {
            e.ViewModel = scope.Resolve(e.ViewModelType);
        }
    };
}
vb
<STAThread>
Shared Sub Main()
    'rootContainer is declared at the Main method level
    Dim rootContainer As IContainer
    Dim builder = New ContainerBuilder()
    builder.RegisterType(Of TestViewModel)()
    builder.RegisterType(Of [MyClass])().As(Of IService)()
    rootContainer = builder.Build()
    AddHandler MVVMContextCompositionRoot.ViewModelCreate, Sub(s, e)
        Using scope = rootContainer.BeginLifetimeScope(Function(b) b.RegisterType(e.RuntimeViewModelType).As(e.ViewModelType))
            e.ViewModel = scope.Resolve(e.ViewModelType)
        End Using
    End Sub
End Sub

To fix the potential issue, declare variables as properties/fields at the level of the object from which a subscription is performed.

csharp
//rootContainer is declared at the root level
private static IContainer rootContainer { get; set; }
    [STAThread]
    static void Main()
    {  
        var builder = new ContainerBuilder();
        builder.RegisterType<TestViewModel>();
        builder.RegisterType<MyClass>().As<IService>();
        rootContainer = builder.Build();
        MVVMContextCompositionRoot.ViewModelCreate += (s, e) =>
        {
            using (var scope = rootContainer.BeginLifetimeScope(
                b => b.RegisterType(e.RuntimeViewModelType).As(e.ViewModelType))) {
                    e.ViewModel = scope.Resolve(e.ViewModelType);
                }
        };
}
vb
'rootContainer is declared at the root level
Private Shared Property rootContainer() As IContainer
<STAThread>
Shared Sub Main()
    Dim builder = New ContainerBuilder()
    builder.RegisterType(Of TestViewModel)()
    builder.RegisterType(Of [MyClass])().As(Of IService)()
    rootContainer = builder.Build()
    AddHandler MVVMContextCompositionRoot.ViewModelCreate, Sub(s, e)
        Using scope = rootContainer.BeginLifetimeScope(Function(b) b.RegisterType(e.RuntimeViewModelType).As(e.ViewModelType))
            e.ViewModel = scope.Resolve(e.ViewModelType)
        End Using
    End Sub
End Sub