将参数传递给ViewModel中的构造函数

本文关键字:构造函数 ViewModel 参数传递 | 更新日期: 2023-09-27 18:21:33

我正在构建一个具有MVVM模式的WPF浏览器应用程序。

我有一个带有数据网格的第一页(咨询发票)。当我双击其中一行时,我想导航到另一个页面(EditInvoice),将参数中选定的行传递给我的构造函数。

我知道如果我想正确地做事,我应该使用依赖注入,但我真的不知道如何在这里使用它。

如何简单地传递此构造函数?

咨询发票查看模型

private Invoice _selected;
public Invoice Selected
{
    get
    {
        return _selected;
    }
    set
    {
        _selected = value;
        OnPropertyChanged("Selected");
    }
}
private void Edit()
{
    EditInvoiceViewModel editInvoice = new EditInvoiceViewModel(Selected); 
   /* doing something here*/
}
public ICommand EditCommand
{
    get
    {
        return editCommand ?? (editCommand = new RelayCommand(p => this.Edit(), p => this.CanEdit()));
    }
}

编辑InvoiceViewModel

public class EditInvoiceViewModel : ViewModelBase
{
    public Context ctx = new Context();
    Invoice invoice;
    PreInvoice preInvoice;
    #region properties
    private ObservableCollection<PreInvoice> collection;
    public ObservableCollection<PreInvoice> Collection
    {
        get
        {
            return collection;
        }
        set
        {
            collection = value;
            OnPropertyChanged("Collection");
        }
    }
    #endregion
    public EditInvoiceViewModel(Invoice inv)
    {
        /* do stuff*/
    }
}

将参数传递给ViewModel中的构造函数

基本上,您应该避免将此类参数传递到ViewModels构造函数中,因为将其与反向控制/依赖注入连接起来会很痛苦。虽然您可以使用抽象工厂模式来解析具有运行时参数的对象,但它不适用于ViewModels。

相反,我总是建议使用一种导航模式,类似于微软的Patterns&实践团队已经完成了棱镜。在那里,您有一个INavigationAware接口,您的ViewModels可以实现该接口。它有两种方法,NavigateToNavigateFrom

还有导航服务。导航服务将切换视图,并在切换之前调用当前ViewModel中的NavigateFrom(如果它实现了它。可以使用它来检查数据是否保存,必要时取消导航。在加载新视图并将ViewModel分配给它之后,在新导航的ViewModel中调用NavigateTo

在这里,您将传递ViewModel所需的参数,在您的案例中为invoiceId。尽量避免传递整个模型或复杂的对象。使用invoiceid获取发票数据并填充正在编辑的ViewModel。

我以前的答案中的一个基本实现(可以在这里找到):

public interface INavigationService 
{
    // T is whatever your base ViewModel class is called
    void NavigateTo<T>() where T ViewModel;
    void NavigateToNewWindow<T>();
    void NavigateToNewWindow<T>(object parameter);
    void NavigateTo<T>(object parameter);
}
public class NavigationService : INavigationService
{
    private IUnityContainer container;
    public NavigationService(IUnityContainer container) 
    {
        this.container = container;
    }
    public void NavigateToWindow<T>(object parameter) where T : IView
    {
        // configure your IoC container to resolve a View for a given ViewModel
        // i.e. container.Register<IPlotView, PlotWindow>(); in your
        // composition root
        IView view = container.Resolve<T>();
        Window window = view as Window;
        if(window!=null)
            window.Show();
        INavigationAware nav = view as INavigationAware;
        if(nav!= null)
            nav.NavigatedTo(parameter);
    }
}
// IPlotView is an empty interface, only used to be able to resolve
// the PlotWindow w/o needing to reference to it's concrete implementation as
// calling navigationService.NavigateToWindow<PlotWindow>(userId); would violate 
// MVVM pattern, where navigationService.NavigateToWindow<IPlotWindow>(userId); doesn't. There are also other ways involving strings or naming
// convention, but this is out of scope for this answer. IView would 
// just implement "object DataContext { get; set; }" property, which is already
// implemented Control objects
public class PlotWindow : Window, IView, IPlotView
{
}
public class PlotViewModel : ViewModel, INotifyPropertyChanged, INavigationAware
{
    private int plotId;
    public void NavigatedTo(object parameter) where T : IView
    {
        if(!parameter is int)
            return; // Wrong parameter type passed
        this.plotId = (int)parameter;
        Task.Start( () => {
            // load the data
            PlotData = LoadPlot(plotId);
        });
    }
    private Plot plotData;
    public Plot PlotData {
        get { return plotData; }
        set 
        {
            if(plotData != value) 
            {
                plotData = value;
                OnPropertyChanged("PlotData");
            }
        }
    }
}

Prism中使用的INavigationAware接口示例可以在项目github存储库中找到。

这使得传递参数和async加载数据变得很容易(没有任何干净的方法可以通过构造函数来做到这一点,因为在没有锁定的情况下,无法在构造函数内awaitasync操作,并且在构造函数中做这种事情是非常不鼓励的)。