【问题标题】:How can I bind a CommandParameter with a child UserControl's child element?如何将 CommandParameter 与子 UserControl 的子元素绑定?
【发布时间】:2019-09-19 07:48:45
【问题描述】:

我有以下 xaml 视图:

<UserControl x:Class="MyViews.PersonView"
  xmlns:views="clr-namespace:MyViews"
 [...]
>
[...]
<dxb:BarManager x:Name="MainBarManager">
  <dxb:BarManager.Items>
    <dxb:BarButtonItem x:Name="bbiPrint"
                       Content="{Binding Print, Source={StaticResource CommonResources}}" 
                       Command="{Binding PrintPersonsCommand}" 
                       CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
                       />
  </dxb:BarManager.Items>
  <Grid>
    <Grid.RowDefinitions>
    [...]
    </Grid.RowDefinitions>
    <views:CardView x:Name="CardUserControl" Grid.Row="2"/>
  </Grid>
[...]
</UserControl>

CardView 定义如下:

<UserControl x:Class="MyViews.CardView"
             [...]>
[...]

    <dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
        [...]
        <dxg:GridControl.View>
            <dxg:CardView x:Name="PersonsCardView" 
                          [...]
                          CardTemplate="{StaticResource DisplayCardTemplate}" 
                          PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
        </dxg:GridControl.View>
        [...]
    </dxg:GridControl>
</UserControl>

PrintPersonsCommand 在我的 ViewModel 中定义如下:

public class PersonViewModel
{
  public PersonViewModel(...)
  {
    [...]
    PrintPersonsCommand = new Prism.Commands.DelegateCommand<DataViewBase>(PrintPersons, CanPrintPersons);
  }  

  public Prism.Commands.DelegateCommand<DataViewBase> PrintPersonsCommand { get; private set; }

  private void PrintPersons(DataViewBase view)
  {
    _printService.ShowGridViewPrintPreview(view);
  }

  private bool CanPrintPersons(DataViewBase view)
  {
    return true;
  }
}

现在,当我单击“打印”按钮时,上面的PrintPersons 方法总是会输入null。如何在上面的MyViews.PersonView xaml 中传递CardUserControl.PersonsCardView,如何将PersonCardView 传递给我的命令?换句话说,我该如何解决

CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"

让它工作?

目前,我发现解决此问题的唯一方法是将CommandCommandParameter 替换为

ItemClick="OnPrintBtnClick"

然后在PersonView的代码隐藏文件中做:

private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
  var ctxt = DataContext as PersonViewModel;
  ctxt.PrintPersonsCommand.Execute(CardUserControl.PersonsCardView);
}

这行得通,但我不敢相信没有其他方法。我对该解决方案不满意,因为我不再拥有使用Command 的好处,例如命令的CanExecute 方法的自动评估。我也可以将CardView 的xaml 代码放在PersonView.xaml 中,但我喜欢将我的控件放在单独的文件中,因为我觉得它更有条理,而且每个用户控件都有自己的职责,可以很好地拆分为单独的文件文件。此外,该解决方案将我的视图绑定到我的视图模型过于紧密。

有人可以帮帮我吗?

【问题讨论】:

  • 你需要这个参数做什么? IE。 PersonsCardView 中有什么,PersonViewModel 中没有?
  • 我想打印我的卡片视图的卡片。我有其他视图,例如网格视图。 PersonViewModel 没有关于 PersonsCardView 的信息。它只带有打印视图所需的逻辑。我不希望PersonViewModel 有任何关于要打印的视图的信息。它会将视图模型与视图绑定得太紧。
  • 我的PersonView 的子视图(如上面提到的CardView 和上面没有提到的GridView)都共享相同的视图模型。
  • 公平点,但是将视图作为参数传递给视图模型看起来已经是我能想象的最紧密的耦合了。
  • 不,因为我传递了它的抽象。 CardViewGridView 都继承自 DataViewBase。我不希望我的应用中使用的任何视图属于不同类型。

标签: c# wpf xaml prism


【解决方案1】:

在不更改现有视图和视图模型层次结构的情况下,我能够使用 Tag 属性将 GridControl.View 传递给 PersonViewModel 您可以将 CardView 分配给 CardView UserControl 底部的 Tag 属性,然后将该 Tag 作为 CommandParameter 访问。

CardView 用户控件

<UserControl x:Class="MyViews.CardView"
         [...]>
    [...]

<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
    [...]
    <dxg:GridControl.View>
        <dxg:CardView x:Name="PersonsCardView" 
                      [...]
                      CardTemplate="{StaticResource DisplayCardTemplate}" 
                      PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
    </dxg:GridControl.View>
    [...]
</dxg:GridControl>

<UserControl.Tag>
    <Binding ElementName="PersonsCardView"/>
</UserControl.Tag>

</UserControl>

打印按钮 Xaml:

<dxb:BarButtonItem x:Name="bbiPrint"
                   Content="{Binding Print, Source={StaticResource CommonResources}}" 
                   Command="{Binding PrintPersonsCommand}" 
                   CommandParameter="{Binding ElementName=CardUserControl, Path=Tag}"
                   />

【讨论】:

  • 效果很好。这是在 WPF 中进行标准的方式还是我以错误的方式做我的事情?有没有更好的方法来组织我的用户控件?
  • 在 MVVM 中通常不鼓励将 UI 控件传递给视图模型。如果我们只需要打印视图,那么我们可以在后面的代码中完成,因为打印控件与视图模型无关。或者,如果必须在视图模型中启动打印,那么我会将打印命令放在 CardViewModel 中。只需使用 CommandParameter 作为绑定,它就会使 UI 元素的传递更加清晰。
  • 我想我明白你的意思;我会根据您的两个建议发布答案
【解决方案2】:

基于 Insane 的宝贵意见,我提出了以下两个更简洁的修复:

代码隐藏解决方案

PersonView 中,在Print 按钮上使用ItemClick 事件处理程序:

<dxb:BarButtonItem x:Name="bbiPrint"
  Content="{Binding Print, Source={StaticResource CommonResources}}" 
  ItemClick="OnPrintBtnClick"/>

像这样调整相应的代码隐藏文件:

public partial class PersonView : UserControl
{
  readonly IPrintService _printService;

  public PersonView(IPrintService printService)
  {
    _printService = printService;

    InitializeComponent();
  }

  private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
  {
    _printService.ShowGridViewPrintPreview(CardUserControl.PersonsCardView);
  }
}

因为我想在没有选择时使打印按钮变灰,所以我仍然需要添加一些代码来实现这一点。我可以得到它 1.更新按钮代码为

<dxb:BarButtonItem x:Name="bbiPrint"
  Content="{Binding Print, Source={StaticResource CommonResources}}" 
  ItemClick="OnPrintBtnClick" IsEnabled="{Binding CanPrintPersons}"/> 
  1. 在更改人员选择时刷新PersonViewModel 中的CanPrintPersons 属性

就是这样。

CardViewModel 解决方案

在该解决方案中,我们有一个 PersonView 及其底层 PersonViewModel 和一个 CardView 及其底层 CardViewModel。我不会详细描述该解决方案,因为它在我的情况下是多余的,但为了完整起见,我将给出要点。单击PersonView 上的Print 按钮后,将调用PersonViewModelPrintCommand。该命令向CardViewModel 发出Print 事件,而CardViewModel 又调用它自己的PrintCommand。后一个命令调用

_printService.ShowGridViewPrintPreview(View);

其中ViewCardViewModel 的属性,在CardView 加载时设置,例如

<dxmvvm:Interaction.Behaviors>
  <dxmvvm:EventToCommand EventName="Loaded" Command="{Binding ViewLoadedCommand}" CommandParameter="{Binding ElementName=PersonsCardView}" />
</dxmvvm:Interaction.Behaviors>

因为我有两个要打印的子视图,所以我需要为每个子视图添加一个视图模型。此外,这两个视图模型加上PersonViewModel 需要访问要打印的人员列表。特别是,它们需要共享访问相同的数据,以便它们同步。 here 解释了一个简单的方法,并且是完全可行的。但我认为对于我拥有的简单用例来说,不值得麻烦,因为它增加了不必要的复杂性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-11
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多