WPF/MVVM设计建议

4
我对WPF比较陌生,需要一些指引。
我正在开发一个应用程序,用于打印我们的履行部门的工作订单。
目前有两个窗口:第一个是主屏幕,第二个是一个带有GridView的窗口,将保存要打印的工作订单。
第一个页面上将有几个按钮。每个按钮都会打开第二个窗口;但是,根据单击哪个按钮,传递到加载数据的服务中的参数将不同。
这样做的最佳实践方式是什么?
  • 是否有办法在Button控件上定义这些参数,然后通过ICommand / RelayCommand传递它们?
  • 应该创建一个UserControl/ServerControl,让我构建这些附加属性?
  • 还有其他我没有想到的方法吗?
编辑:
为了给出一个大致的例子(这非常简化),假设我有2组标准: 订单类型:{Rush, Today, Future}和位置{Warehouse 1, Warehouse 2, Warehouse 3}。
主窗口将有一个3x3的按钮网格,每个组合一个按钮。我想能够在单个按钮上指定“加快&仓库1”;然后将这些参数传递回一个方法,该方法将打开第二个窗口。
4个回答

3
假设您有一个MainWindow,并在其中放置了按钮。
  1. 创建一个MainWindowViewModel并将其设置为MainWindow的DataContext

  2. 在您的ViewModel上有一个ICommand,并将按钮命令与此ICommand绑定,以便打开另一个窗口的入口点是单一的。对于ICommand,您可以使用RelayCommandDelegateCommand中最适合您的那个。

  3. 现在,根据按钮类型单击需要打开窗口并传递参数的点。我建议使用枚举表示基于不同按钮执行的操作

枚举

public enum ActionType
{
   Action1,
   Action2,
   Action3 and so on...
}

并且可以通过以下方法将按钮进行绑定:

<Button Command="{Binding CommandInstance}"
        CommandParameter="{x:Type local:ActionType.Action1}"/>

<Button Command="{Binding CommandInstance}"
        CommandParameter="{x:Type local:ActionType.Action2}"/>

local将是声明枚举的命名空间。

在命令执行委托中,将枚举值传递给另一个窗口的构造函数:

private void CommandMethod(ActionType action)
{
    AnotherWindow anotherWindow = new AnotherWindow(action);
    anotherWindow.Show();
}

通过构造函数传递的操作,您可以检查需要传递给负责加载数据的服务的参数。

此外,如果从ViewModel创建窗口不合适,则可以拥有覆盖窗口控件的服务包装器,该服务负责显示/关闭窗口。


更新

由于您想要从视图传递多个参数,因此维护枚举将很麻烦。您可以使用IMultiValueConverter从视图传递多个值。

让我用一个简单的例子来解释:

<Button Command="{Binding TestCommand}">
   <Button.Resources>
      <sys:String x:Key="Rush">Rush</sys:String>
      <sys:String x:Key="Warehouse">Warehouse</sys:String>
    </Button.Resources>
    <Button.CommandParameter>
       <MultiBinding Converter="{StaticResource MultiValuesReturnConverter}">
          <Binding Source="{StaticResource Rush}"/>
          <Binding Source="{StaticResource Warehouse}"/>
        </MultiBinding>
    </Button.CommandParameter>
 </Button>

在XAML中,sys将成为System的命名空间:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

所以,现在你可以在XAML中自由地传递许多对象到你的命令参数。你所要做的就是在按钮资源部分声明资源,并将其作为绑定传递给转换器。

转换器代码将其转换为可以作为单个参数对象传递给命令的参数列表:

public class MultiValuesReturnConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
                          object parameter, CultureInfo culture)
    {
        return values.ToList<object>();   
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
                                object parameter, CultureInfo culture)
    {
        throw new System.NotImplementedException();
    }
}

Command Method:

private void CommandMethod(object parameter)
{
    // Now you have all the parameters here to take decision.
    IList<object> values = parameter as List<object>;

    AnotherWindow anotherWindow = new AnotherWindow(action);
    anotherWindow.Show();
}

我非常喜欢这种方式,因为它只有一个入口点。我能否在那里指定多个参数,还是只能锁定一个? - Jim B
你只需要从视图中传递参数吗?如果可以根据操作类型推断出来,我建议在ViewModel中创建。 - Rohit Vats
我发布了一个快速编辑,其中包含我想要实现的示例。只有一个ActionType可能很快变得难以控制;因为我可能会有很多潜在的类型。 - Jim B
1
@JimB- 请查看答案更新,了解如何使用IMultiValueConverter从View传递多个值到Command。 - Rohit Vats
在ViewModel内部打开另一个窗口?那违背了ICommand的目的。 - jamesSampica
显示剩余3条评论

1
如果你不想使用第三方库,那么简单地通过点击事件将参数传递到另一个窗口的构造函数中是没有问题的。如果你的数据由视图模型表示,你也可以传递该视图模型而不是参数本身。
MVVM 的一个重点不是“无代码后端”。很多时候你会没有代码后端,但是试图以这种方式开发应用程序会导致复杂的反模式,通常比简单的点击事件和“旧方法”更费力、代码更多。
将你的数据视为数据,尝试在可测试的视图模型中完成所有工作,永远不要过于严格地遵循一种模式,否则你可能会得到大量难以阅读的抽象。

0
在详细说明任何事情之前,我建议您使用第三方库MVVMLight,它具有许多有用的功能,例如Messenger、自己的RelayCommands等等...
为了将参数从按钮传递到命令中以便使用,如果您想要传递一个与事件类型无关的参数,则可以使用Tag属性;如果您想要传递一个与特定命令(事件)相关的参数,则需要使用CommandParameter: Tag:获取或设置一个任意对象值,该值可用于存储有关此元素的自定义信息。(从FrameworkElement继承。)
CommandParameter:
<Button Content="Parameterized Command" 
    Command="{Binding ParameterizedCommand}" CommandParameter={Binding SomeObject} /> 

我认为在你的问题层面上,除非涉及更复杂的情况,否则不需要创建一个UserControl。
你可以使用Messenger类将信息从ViewModel传递到另一个(这只是一个辅助功能,与MVVM模式无关)。
MVVMLight提供了代码模板,帮助你轻松创建ViewModels。
MVVMLight具有许多有用的代码片段,你会发现它们很有帮助。
要小心命令,因为它们并不是所有UI元素都原生支持的,它们只能在ButtonBase及其子元素上使用,并且只能替换Click事件。如果要在其他UI元素和其他类型的事件中使用命令和命令参数,你应该使用一种名为EventToCommand行为的方式。MVVMLight已经为你准备好了这个功能。
希望我已经涵盖了你可能需要的最重要的部分。

@BasBrekelmans 嗯,我想我搞错了,我在考虑另一种情况,我会更新的,谢谢提醒。 - AymenDaoudi

0
最简单和直观的方法(使用INotifyPropertyChanged更新UI,而不是使用DependencyProperty):
您可以创建一个属性作为MainWindowViewModelOrderViewModelDataContext
class MainWindowViewModel : ViewModelBase // ViewModelBase should implement INotifyPropertychanged, unless you're using dependency properties
{
  private OrderViewModel _OrderViewModelInstance;
  public OrderViewModel OrderViewModelInstance 
  { 
     get{ return _OrderViewModel;} 
     set { _OrderViewModel = value; 
          OnPropertyChanged("OrderViewModel")} // Method from ViewModelBase
   }

无论您以哪种方式创建订单视图:
  • 在MainWindowViewModel中实例化OrderViewModel(假设当按钮被点击时),并使用所需参数。
  • 在XAML中,将订单视图的DataContext绑定到OrderViewModelInstance。您可能还想创建一个额外的变量来告诉您窗口何时可见。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接