How do I avoid coupling XAML Views to start up cod

2019-08-05 04:16发布

I really like the flexibility of declaring my ViewModel on the DataContext of a View through XAML, but I am having a hard time figuring out how I can tie this ViewModel into the rest of the system.

Ex.

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ViewModels">

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext>    

The problem here, is that my ViewModels will often have and share dependencies with other parts of the system, and I am not sure how I can inject a reference of another object into the MainViewModel declared above.

If I were doing a manual sort of Dependency Injection, then I would create a bunch of factories at the start of the application responsible for wiring everything up.

How can I take this same approach in a MVVM WPF application? And what is the most effective way to get a handle on all of my ViewModels?

5条回答
闹够了就滚
2楼-- · 2019-08-05 04:23

I guess you have a dependency that you want to constructor inject like this:

class MainViewModel
{
    public interface IDependency { };
    readonly IDependency _dep;
    public MainViewModel (IDependency dep)
    {
        if (dep == null)
            throw new ArgumentNullException("IDependency dep cannot be null");
        _dep = dep;
    }
}
  1. Is that what you are trying to do??

Secondly, to my understanding to be able to use your MainViewModel in XAML, you need to provide a default constructor, which in combination with the injected dependency is a "Bastard Injection": What is the real difference between "Bastard Injection" and "Poor Man's Injection"

So far I have not seen a way to avoid "Bastard Injection" and still be able to this:

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext> 

or that:

<UserControl.Resources>
    <DataTemplate DataType="{x:Type vm:ProgrammingViewModel}">
        <local:ProgrammingView />
    </DataTemplate>
</UserControl.Resources>
查看更多
相关推荐>>
3楼-- · 2019-08-05 04:31

You can use a view model first approach, where you instantiate your view models (injecting any dependencies through constructor injection), and then instantiate your view, and set its DataContext to your view model instance programmatically.

I use the Caliburn.Micro framework which does view location and binding through conventions automatically. You can find it at http://caliburnmicro.codeplex.com/

Rob Eisenburg's original Build Your Own MVVM Framework talk gives an excellent overview of what became Caliburn.Micro - http://live.visitmix.com/MIX10/Sessions/EX15

查看更多
Anthone
4楼-- · 2019-08-05 04:32

I create a view model for the entire application and either make it the data context of my main window or (if I'm not going to have just one main window) stick it in the application's resource dictionary. All of the startup code goes inside of the application view model's constructor, and the AVM exposes other view models as properties so that they can be accessed via binding.

It's kind of a blunt instrument, but it's got the virtue of simplicity.

查看更多
Anthone
5楼-- · 2019-08-05 04:41

I like to use a DataTemplate, defined in a resource dictionary within App.xaml (or elsewhere), to map a ViewModel to a View like this:

<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
    <vw:CustomerView />
</DataTemplate>

This has the effect of automatically assigning the ViewModel to be the view's DataContext at runtime. In this case I would instantiate an object called,say, myCustomerViewModel, in the ViewModel for the window or other user control that would host the CustomerView, and then in the View for that window, use a ContentControl like this:

<ContentControl Content="{Binding Path=myCustomerViewModel}" />
查看更多
地球回转人心会变
6楼-- · 2019-08-05 04:46

To achieve complete decoupling, set ViewModels on the ResourceDictionary found on the main App class. There are two ways to do this, and for the most part it doesn't matter which method is used. There are trade-offs however.

Method 1

If it is done progamatically, you must ensure Dictionary keys match. This causes a weak coupling between the strings defined in the XAML and those defined programmatically. Not ideal, but not the end of the world either. The advantage here is that ability to use constructor injection.

//App.xaml.cs
//This line invoked somewhere after App OnStartUp function is called. 
MainViewModel mainViewModel = new MainViewModel( new Dependency() );
Current.ResourceDictionary["MainViewModel"] = mainViewModel; 

//MainView.xaml
<Window DataContext="{StaticResource MainViewModel}">

The Business logic doesn't care what a View is, and the application can be Bootstrapped in any way... using factory/builder object or any IOC container. (As long as it all starts in the OnStartUp function).

Method 2

Define ViewModels in App.xaml using Application.Resource. Using this method all key names will be located in XAML, which feels pretty nice. The only negative result is that .NET automatically builds the ViewModels, forcing the to provide default constructors. Sometimes it is desirable for the IOC container to build your objects, or use Constructor Injection in custom factories/builders.

//App.xaml
<Application 
    xmlns:local="clr-namespace:ViewModels" />

<Application.Resources>
     <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <local:MainViewModel x:key="MainViewModel" />
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

//App.xaml.cs
MainViewModel mainViewModel = Current.ResourceDictionary["MainViewModel"];
mainViewModel.propertyInjection = new Dependency();

//MainView.xaml
<Window DataContext="{StaticResource MainViewModel}">

Both ways are valid options, and with a little key management, a mix and match can be used to suit requirements.

查看更多
登录 后发表回答