使用自定义INavigationService实现将参数传递给ViewModel?

时间:2019-04-19 02:55:34

标签: c# wpf mvvm dependency-injection mvvm-light

我已经使用MVVM Light构建了一个小型应用程序,并且达到了需要在应用程序中的几个不同ViewModel之间传递参数的地步。我已经探索了几种不同的选择,但是我并不是真的很喜欢它们。到目前为止,我遇到的最有前途的就是在ViewModel之间简单地传递消息,但是这在一定程度上是有局限性的,因为该应用程序有可能一次打开多个同一个View,并且我需要将参数隔离到单个实例中View / ViewModel的视图。

我目前未使用MVVM Light提供的内置INavigationService,但我做了一个非常相似的东西(如果我能解决参数注入问题,我可能会切换)。

这是我的导航服务的精简版:

public class NavigationService : INavigationService
{
    /* this implementation will not allow us to have the same window open
       more than once. However, for this application, that should be sufficient.
    */
    public NavigationService()
    {
        _openPages = new Dictionary<string, Window>();
    }

    private readonly Dictionary<string, Window> _openPages;
    public void ClosePage(string pageKey)
    {
        if (!_openPages.ContainsKey(pageKey)) return;

        var window = _openPages[pageKey];

        window.Close();
        _openPages.Remove(pageKey);
    }

    public IEnumerable<string> OpenPages => _openPages.Keys;

    public void NavigateTo(string pageKey)
    {
        if (!AllPages.ContainsKey(pageKey))
            throw new InvalidPageException(pageKey);

        // Don't re-open a window that's already open
        if (_openPages.ContainsKey(pageKey))
        {
            _openPages[pageKey].Activate();
            return;
        }

        var page = (Window) Activator.CreateInstance(AllPages[pageKey]);
        page.Show();
        page.Closed += OnWindowClosedHandler;
        _openPages.Add(pageKey, page);
    }

    // Probably a better way to remove this.
    private void OnWindowClosedHandler(object sender, EventArgs args)
    {
        foreach (var item in _openPages.Where(kvp => kvp.Value == sender).ToList())
        {
            _openPages.Remove(item.Key);
        }
    }

    // Reflection might work for this.
    // Might also consider making this more dynamic so it isn't hard-coded into my service
    private readonly Dictionary<string, Type> AllPages = new Dictionary<string, Type>
    {
        ["AddPatientView"] = typeof(AddPatientView),
        ["CheckInView"] = typeof(CheckInView),
        ["MainView"] = typeof(MainWindow),
        ["PatientLookupView"] = typeof(PatientLookupView),
        ["PatientDetailsView"] = typeof(PatientDetailsView)
    };
}

我的大多数ViewModel使用依赖注入来连接其他注入的服务,就像这样:

public class CheckInViewModel : ViewModelBase
{
    public CheckInViewModel(ILicenseValidationService licenseValidationService,
        IPatientFetchService patientFetchService,
        IPatientCheckInService patientCheckInService)
    {
        if (IsInDesignMode)
        {
            Title = "Find Member (Design)";
        }
        else
        {
            Title = "Find Member";

            CanFetch = true;
            FindMemberCommand = new RelayCommand(async () => await FindMemberHandler(), () => CanFetch);
            CheckInPatientCommand = new RelayCommand<Window>(async (window) => await CheckInPatientHandler(window),
                (window) => Patient?.PatientId != null);

            _licenseValidationService = licenseValidationService;
            _patientFetchService = patientFetchService;
            _patientCheckInService = patientCheckInService;
        }
    }
}

我想实现一些在注入服务旁注入其他参数的方法。这样的事情以相对简单的方式完成了吗?

1 个答案:

答案 0 :(得分:0)

几乎在所有情况下,依赖注入的工作方式都是当您解析或获取一个实例类型时,该类型将在向您提供对象时使用带有最多参数的构造函数。

如果您针对接口(或只是类型)注册一个具体对象,然后稍后解析/获取一个在其ctor中使用这些东西之一的类,则DI将提供您注册的实例。

使用MVVMLight,您可以拥有SimpleIoc和SimpleIoc。Default等同于您正在考虑的静态服务。

simpleioc有一个问题。非常简单。

使用simpleioc,一旦获得给定类型的视图模型,便成为单例。您可以通过传递唯一键来强制使用其他实例,但是它们都已缓存。您可以使用参数获取实例,也许可以替换当前对象。我不确定随手可得。建议使用更复杂的DI容器。

除此之外。

由于您使用的是不同的窗口,因此要实例化一个窗口,并且将需要一个数据上下文,因此您需要以某种方式提供参数,这会带来一些麻烦。

首先可以使用viewmodel。 您会从DI或资源或静态中获取inavigationservice。

您有一个DoWindow(Object vm)方法。

想导航时,大概知道vm的参数。使用参数更新视图模型。新建一个用于所有视图的窗口。将其内容设置为您的视图模型。现在已将其模板化为Windows。除了使它们成为用户控件。使用Datatype =“ vmtype”将视图作为模板与viewmodel关联。将窗口的标题绑定到Content.Title,然后将Title属性添加到基本视图模型。

或者,使用单个窗口应用程序,您可以让contentcontrol填充将显示视图的区域。将该内容的内容绑定到currentviewmodel属性,然后可以在该窗口中使用viewmodel首先导航。