键绑定困境

时间:2012-06-09 03:57:40

标签: wpf mvvm

我有一个如下所示的用户控件,用于显示主要详细信息。具有基本视图模型的典型MVVM体系结构,其中包含CloseCommand。

我正在尝试对将在TabItem上执行close命令的KeyBinding进行范围调整,但是无法使其工作。

有趣的是,如果我将绑定放在PersonDetailView(TabControl可能显示的两个可能的USerControl之一,如下所示)中,我可以让它工作,但它应该在TabControl上或包含它的边框。

有什么建议吗?

干杯,
Berryl

用户控件

<Grid>

    <ListBox Style="{StaticResource ListBoxStyle}" />

    <GridSplitter 
        HorizontalAlignment="Right" VerticalAlignment="Stretch" Grid.Column="1" 
        ResizeBehavior="PreviousAndNext" Width="5" Background="#FFBCBCBC" KeyboardNavigation.IsTabStop="False"
                  />

    <Border Grid.Column="2" Background="{StaticResource headerBrush}">

        // ** THIS is the scope I want, but it doesn't work
        <Border.InputBindings>
            <KeyBinding Key="F4" Modifiers="Control" Command="{Binding CloseCommand}"/>
        </Border.InputBindings>

        <TabControl Style="{StaticResource TabControlStyle}" >

            <TabControl.Resources>                   
                <DataTemplate DataType="{x:Type personVm:PersonDetailVm}">
                    <local:PersonDetailView />
                </DataTemplate>
                <DataTemplate DataType="{x:Type orgVm:OrganizationDetailVm}">
                    <local:OrganizationDetailView />
                </DataTemplate>
            </TabControl.Resources>

        </TabControl>
    </Border>

</Grid>

TabItem样式

<Style x:Key="OrangeTabItemStyle" TargetType="{x:Type TabItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border AllowDrop="true" ToolTip="{Binding DisplayName}">
                    <Border Name="Border" Background="Transparent" BorderBrush="Transparent" BorderThickness="1,1,1,0" CornerRadius="2,2,0,0">
                        <DockPanel x:Name="TitlePanel" TextElement.Foreground="{StaticResource FileTabTextBrush}">
                            <ctrl:GlyphButton 

                                // ** This works as expected
                                Command="{Binding CloseCommand}" CommandParameter="{Binding}"
                                >
                            </ctrl:GlyphButton>

                        </DockPanel>
                    </Border>

                    // ** Can't get it to work from here either **
                    <Border.InputBindings>
                        <KeyBinding Command="{Binding CloseCommand}" Key="F4" Modifiers="Control" />
                    </Border.InputBindings>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

更新

我无法以我的风格设置RoutedCommand

<Style x:Key="OrangeTabItemStyle" TargetType="{x:Type TabItem}">
    <Setter Property="beh:RoutedCommandWire.RoutedCommand" Value="F4"/> **** ?? ****
    <Setter Property="beh:RoutedCommandWire.ICommand" Value="{Binding CloseCommand}"/>
</Style>

以下是我认为答案代码在C#

中的样子
public class RoutedCommandWire
{

    public static readonly DependencyProperty RoutedCommandProperty =
        DependencyProperty.RegisterAttached("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandWire), new PropertyMetadata(OnCommandChanged));

    public static RoutedCommand GetRoutedCommand(DependencyObject d) { return (RoutedCommand) d.GetValue(RoutedCommandProperty); }
    public static void SetRoutedCommand(DependencyObject d, RoutedCommand value) { d.SetValue(RoutedCommandProperty, value); }

    public static readonly DependencyProperty ICommandProperty = 
        DependencyProperty.RegisterAttached("Iommand", typeof(ICommand), typeof(RoutedCommandWire));

    public static ICommand GetICommand(DependencyObject d) { return (ICommand) d.GetValue(ICommandProperty); }
    public static void SetICommand(DependencyObject d, ICommand value) { d.SetValue(ICommandProperty, value); }

    private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var fe = d as FrameworkElement;
        if(fe==null) return;

        if (e.OldValue != null) {
            Detach(fe, (RoutedCommand) e.OldValue);
        }
        if (e.NewValue != null) {
            Attach(fe, (RoutedCommand) e.NewValue, Execute, CanExecute);
        }
    }

    private static void CanExecute(object sender, CanExecuteRoutedEventArgs e) {
        var depObj = sender as DependencyObject;
        if (depObj == null) return;

        var command = GetICommand(depObj);
        if (command == null) return;

        e.CanExecute = command.CanExecute(e.Parameter);
        e.Handled = true;
    }

    private static void Execute(object sender, ExecutedRoutedEventArgs e)
    {
        var depObj = sender as DependencyObject;
        if (depObj == null) return;

        var command = GetICommand(depObj);
        if (command == null) return;

        command.Execute(e.Parameter);
        e.Handled = true;
    }

    public static void Detach(FrameworkElement fe, RoutedCommand command) {
        var bindingCollection = fe.CommandBindings;
        if (bindingCollection.Count == 0) return;

        var matches = bindingCollection.Cast<CommandBinding>().Where(binding => binding.Equals(command));
        foreach (var binding in matches) {
            bindingCollection.Remove(binding);
        }
    }

    public static void Attach(FrameworkElement fe, RoutedCommand command, 
        ExecutedRoutedEventHandler executedHandler, CanExecuteRoutedEventHandler canExecuteHandler, bool preview = false)
    {
        if (command == null || executedHandler == null) return;

        var binding = new CommandBinding(command);
        if (preview)
        {
            binding.PreviewExecuted += executedHandler;
            if (canExecuteHandler != null)
            {
                binding.PreviewCanExecute += canExecuteHandler;
            }
        }
        else
        {
            binding.Executed += executedHandler;
            if (canExecuteHandler != null)
            {
                binding.CanExecute += canExecuteHandler;
            }
        }
        fe.CommandBindings.Add(binding);
    }
}

1 个答案:

答案 0 :(得分:2)

KeyBindings仅适用于接受键盘输入的控件。边界不是。通常,InputBindings与CommandBindings的不同之处在于您可以在父元素上定义CommandBinding,以便在子元素具有焦点时处理命令,但是您不能在父元素上定义InputBinding以使它们在父元素上有效。儿童元素。

您可以做的是在命令的InputGestures集合中添加默认的InputGesture。这似乎可以使用来自每个接受键盘输入的控件的键盘快捷键使命令可用(这比在任何地方指定InputBindings要好得多,不是吗?)。为了利用这一点,您必须使用RoutedCommand来调用MVVM-ICommand。您可以使用附加属性组合这两个属性,我称之为&#34;粘性命令&#34;这与附加行为非常相似。

此代码定义附加属性:

    Public Class Close

    Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(RoutedCommand), GetType(Close), New PropertyMetadata(AddressOf OnCommandChanged))
    Public Shared Function GetCommand(ByVal d As DependencyObject) As RoutedCommand
        Return d.GetValue(CommandProperty)
    End Function
    Public Shared Sub SetCommand(ByVal d As DependencyObject, ByVal value As RoutedCommand)
        d.SetValue(CommandProperty, value)
    End Sub

    Public Shared ReadOnly MVVMCommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("MVVMCommand", GetType(ICommand), GetType(Close))
    Public Shared Function GetMVVMCommand(ByVal d As DependencyObject) As ICommand
        Return d.GetValue(MVVMCommandProperty)
    End Function
    Public Shared Sub SetMVVMCommand(ByVal d As DependencyObject, ByVal value As ICommand)
        d.SetValue(MVVMCommandProperty, value)
    End Sub


    Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        If e.OldValue IsNot Nothing Then
            Detach(d, DirectCast(e.OldValue, RoutedCommand))
        End If
        If e.NewValue IsNot Nothing Then
             Attach(d, DirectCast(e.NewValue, RoutedCommand), AddressOf DoCloseCommand, AddressOf CanDoCloseCommand)
        End If
    End Sub

    Private Shared Sub CanDoCloseCommand(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
        If sender IsNot Nothing Then
            Dim com As ICommand = GetMVVMCommand(sender)
            If com IsNot Nothing Then
                e.CanExecute = com.CanExecute(e.Parameter)
                e.Handled = True
            End If
        End If
    End Sub

    Private Shared Sub DoCloseCommand(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
        If sender IsNot Nothing Then
            Dim com As ICommand = GetMVVMCommand(sender)
            If com IsNot Nothing Then
                com.Execute(e.Parameter)
                e.Handled = True
            End If
        End If
    End Sub

    Public Shared Sub Detach(ByVal base As FrameworkElement, ByVal command As RoutedCommand)
        Dim commandBindings As CommandBindingCollection = base.CommandBindings
        If commandBindings IsNot Nothing Then
            Dim bindings = From c As CommandBinding In commandBindings
                           Where c.Command Is command
                           Select c
            Dim bindingList As New List(Of CommandBinding)(bindings)
            For Each c As CommandBinding In bindingList
                commandBindings.Remove(c)
            Next
        End If
    End Sub

    Public Shared Sub Attach(ByVal base As FrameworkElement, ByVal command As RoutedCommand, ByVal executedHandler As ExecutedRoutedEventHandler, ByVal canExecuteHandler As CanExecuteRoutedEventHandler, Optional ByVal preview As Boolean = False)
        If command IsNot Nothing And executedHandler IsNot Nothing Then
            Dim b As CommandBinding = New CommandBinding(command)
            If preview Then
                AddHandler b.PreviewExecuted, executedHandler
                If canExecuteHandler IsNot Nothing Then
                    AddHandler b.PreviewCanExecute, canExecuteHandler
                End If
            Else
                AddHandler b.Executed, executedHandler
                If canExecuteHandler IsNot Nothing Then
                    AddHandler b.CanExecute, canExecuteHandler
                End If
            End If
            base.CommandBindings.Add(b)
            'For Each i As InputGesture In command.InputGestures
            '    GetInputBindings(base).Add(New InputBinding(command, i))
            'Next
        End If
    End Sub

我猜你在TabItem上使用它们,因为那是你想要处理close命令的地方,你可以将Close.Command设置为RoutedCommand,它的InputGestures中有一个键盘快捷键,和Close.MVVMCommand =&#34; {Binding CloseCommand}&#34;。

更新

您可以在ViewModel中定义类似这样的RoutedCommand:

Public Shared ReadOnly TestCommand As New RoutedUICommand("Test", "TestCommand", GetType(ViewModel))
Shared Sub New()
    TestCommand.InputGestures.Add(New KeyGesture(Key.T, ModifierKeys.Control))
End Sub

静态构造函数设置命令的默认keygesture。如果要在XAML中执行此操作,也可以使用自定义附加属性执行此操作。无论如何,你在XAML中引用了RoutedCommand:

Close.Command="{x:Static my:ViewModel.TestCommand}"