【问题标题】:Wpf property binding vs commandWpf 属性绑定 vs 命令
【发布时间】:2014-05-10 21:49:29
【问题描述】:

我创建了两个用户控件,一个在 Canvas 上显示我的数据/视图模型集合的二维笛卡尔地图。另一个是 1 列的可滚动图片库,它为每个数据模型显示一张图片。 (sn-pized)地图基于:

<ItemsControl 
        ItemsSource="{Binding Defects}"
        ItemTemplateSelector="{StaticResource IconSelector}">            
        <!-- ItemsPanelTemplate -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas x:Name="MapCanvas" Height="10000"/>                       
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <!-- ItemContainerStyle -->
        <ItemsControl.ItemContainerStyle>
            <Style>
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
                <Setter Property="Canvas.Left" Value="{Binding X}" />                     
            </Style>               
        </ItemsControl.ItemContainerStyle>
</ItemsControl>

(同样,sn-pized)图片库基于:

<ScrollViewer Name="ScrollContainer"
        Grid.Column="1" PanningMode="VerticalOnly" 
        VerticalScrollBarVisibility="Visible"
        HorizontalScrollBarVisibility="Visible"                  
        Background="White">
    <ItemsControl Name="ItemsContainer" ItemsSource="{Binding Defects}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border BorderThickness="5" BorderBrush="Gray" Margin="2">
                    <Image Source="{Binding ImageUrl}" Margin="2"></Image>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

现在我想为我的数据的这两个视图添加一些同步。即,当单击地图上的一个图标时,我希望图库滚动到相应的图像。 我试图向两个控件添加一个依赖属性以指向所选索引。但这并不是我真正想要的,因为我想要一些额外的灵活性。有时我希望通过动画完成滚动,有时我希望立即同步选定的索引。 因此,我尝试将自定义命令 BringIntoView 添加到视图模型。此命令采用布尔输入来指定是否必须使用动画执行操作。我可以将数据模板绑定到我的自定义命令,并且只要单击地图上的图标,地图控件就会正确调用自定义命令上的执行。

但是我被困在画廊方面,我怎样才能将此命令绑定到一些代码隐藏方法,或者可能绑定到控件上的一些其他方法?命令绑定不起作用,因为它不允许动态绑定到命令。以下会引发编译时错误,因为 CommandBinding 上的 Command 属性不是依赖属性:

<UserControl.CommandBindings>
    <CommandBinding Command="{Binding BringIntoView}" Executed="OnBringIntoViewExecuted"/>
</UserControl.CommandBindings> 

我可以添加一个静态命令并直接引用该命令,但这至少会将视图绑定到 viewmodel 命令。那么如何实现这种级别的命令链接,同时保持与视图的解耦呢?

换句话说,WPF 依赖属性系统允许绑定两个 UI 控件的两个 UI 特定属性。我怎样才能为命令/事件实现相同的目标?也就是说,来自用户控件的 UI 事件/命令连接到另一个 UI 控件的方法/命令?

【问题讨论】:

    标签: wpf binding command


    【解决方案1】:

    我找到了解决这个设计问题的方法。与依赖属性发生的情况类似,自定义控件也可以公开自定义命令。由于控件直接公开的命令(与视图模型公开的命令相反)是特定于 UI 的,因此最适合使用 RoutedUiCommand 而不是自定义 ICommand 实现。 RoutedUiCommands 可以作为静态属性公开,就像 DependencyProperty 发生的一样。在我的例子中,图库控件公开了一个静态的 BringIntoView 命令,如下所示:

    public partial class DefectGallery {
      // the control exposes the following commands:
      public static readonly RoutedUiCommand BringIntoView = new RoutedUiCommand;
      // the control exposes the following depdendency properties:
      public static readonly DependencyProperty ScrollSpeedProperty = DependencyProperty.Register(...);   
      ... 
    }  
    

    该命令绑定到 DefectGallery 控件的 xaml 文件中的代码隐藏:

    <UserControl.CommandBindings>
       <CommandBinding Command="{x:static local:DefectGallery.BringIntoView}" Executed="OnBringIntoViewExecuted"/>
    </UserControl.CommandBindings> 
    

    最后,一些其他控件需要触发 BringIntoViewCommand。它可以例如WPF 提供的命令源,如按钮。在我的例子中,我已经在地图控件上实现了 ICommandSource 接口,以便允许指定一个自定义命令,该命令在单击地图上的图标时执行。我现在可以像这样使用来自 xaml 的地图控件(上面的第一个 sn-p):

    <MapControl 
        Command="{x:static local:DefectGallery.BringIntoView}"
        CommandTarget="{Binding ElementName=defectGalleryInstance}"
     />
    

    因此,我可以仅使用声明性 xaml 从另一个控件“调用”控件。

    现在,这似乎与添加依赖属性一样基本模式,所以我很惊讶我还没有找到这方面的指导方针。对我来说,控件公开属性和命令似乎很自然。我认为这两个是 UI 控件对其他 UI 组件的“接口”,而 View 模型绑定是对应用程序层的接口。哦,好吧,WPF 适合不同的使用方法,您只需要找到适合您/您的应用程序的方法。

    【讨论】: