在XAML中处理按钮的点击事件?

7
这似乎是一个非常基础的问题,但我相信有更好的方法来解决这个问题。 我的UI中有一个按钮,它选择特定的选项卡并从ViewModel触发一个命令。

以下是当前代码(可以正常工作):

XAML:

<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />

代码后台:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
     theTab.IsSelected = true;
}

有没有更加干净、仅使用 XAML 的方式来执行该 UI 操作呢?我在考虑这样一种方式:
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Click">
            <Setter TargetName="theTab" Property="IsSelected" Value="True" />
        </EventTrigger>
    </Button.Trigger>
</Button>

但不幸的是,似乎“事件触发器”不支持“点击”事件。为什么呢?在使用WPF工作几年后,我仍然有时会对触发器感到困惑,这基本上就是原因。 当尝试构建代码时,在Setter行上出现错误。
A value of type 'Setter' cannot be added to a collection or dictionary of type 'TriggerActionCollection'.

谢谢!

编辑 自从我被问及窗口的XAML结构,它看起来像这样:

<DockPanel LastChildFill="True">
    <Ribbon DockPanel.Dock="Top">
        <Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />
    </Ribbon>
    <TabControl>
        <TabItem x:Name="theTab" />
    </TabControl>
</DockPanel>

你能发布一下包括你所提到的选项卡的完整XAML吗? - Nitin
我相信这个和/或者这个可能对你有所帮助...(并且使用Button.Click代替Click) - qqbenq
在我看来,这与问题本身无关。你需要它吗? - Damascus
是的。我想看看按钮和选项卡所在的层次结构? - Nitin
1
@Damascus 我同意,这似乎非常复杂,但这就是实现的方法 :) 您可以创建一个故事板资源,并在触发器中使用它,这样看起来会简单一些。 - qqbenq
显示剩余2条评论
3个回答

9

错误简要概述了问题。在EventTrigger中,您不能使用Setter。如果您想在XAML中实现此功能,可以使用Storyboard,它将在按下按钮时选择给定的选项卡。类似这样:

<Button Content="Click">
   <Button.Triggers>
       <EventTrigger RoutedEvent="Button.Click">
           <BeginStoryboard>
               <Storyboard>
                   <BooleanAnimationUsingKeyFrames Storyboard.TargetName="theTab" Storyboard.TargetProperty="IsSelected">
                       <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
                   </BooleanAnimationUsingKeyFrames>
               </Storyboard>
           </BeginStoryboard>
       </EventTrigger>
   </Button.Triggers>
</Button>

有人也将此发布为评论,我的担忧是:为了简单起见,为什么带有“动画”的“Storyboard”会比基于代码的“EventHandler”更简单/干净? 如果这是唯一的方法,我想我可能会坚持使用基于代码的方式。在接受你的答案之前,我会保持问题的开放性,看看是否还有其他的方法。谢谢! - Damascus
你说得对,在这种情况下,代码后台更简单。问题在于普通的Trigger有状态的概念,因此Setter可以被撤销,但是EventTrigger没有这样的概念,所以你只能在发生时开始一些操作。 - dkozl
哦,原来如此。我总是对所有这些“触发器”可能性感到困惑。为什么在“样式”中的那些会不同!我将尝试找到一个更简短的解决方案。也许通过创建一个单独的“Storyboard”派生类。 - Damascus

4
您可以在您的VM中引入一个bool类型的属性IsMyTabSelected,并将其绑定到TabItem.IsSelected属性:
<TabItem x:Name="theTab" IsSelected="{Binding IsMyTabSelected}" >

然后您只需在ButtonWhateverCommand处理程序中设置此标志。 注意: IsMyTabSelected属性必须实现System.ComponentModel.INotifyPropertyChanged


那么它就不是仅限于XAML了!在我的ViewModel中保留一个纯UI属性IsMyTabSelected也没有实际意义,因为这可以在XAML代码后台中切换。有趣的是,对于如此简单的问题,每个解决方案似乎都同样不适用 :/ - Damascus
1
不仅仅是XAML,也没有代码后台和简单。此外,在大型项目中很难(不可能?)将ViewModel与“纯UI”分开。 - amnezjak

4
肯定有更好的方法。通过使用Windows.Interactivity程序集的帮助,您可以将事件源绑定到一个包含相关操作的单个类中。这样,您几乎可以做任何事情。
动作类必须派生自TriggerAction。通过重写Invoke方法,您可以指定操作。
除了这种情况外,还可以将EventTrigger绑定到命令(例如RelayCommand),从而实现干净的MMVM实现。
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<Button x:Name="button">
                        <i:Interaction.Triggers>
                            <i:EventTrigger SourceName="button" EventName="Click">
                                <app:MyAction/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>

Public Class MyAction
    Inherits Interactivity.TriggerAction(Of UIElement)

    Protected Overrides Sub Invoke(parameter As Object)
        MsgBox("Clicked")
    End Sub

End Class

我已根据你的具体要求更新了代码。现在TriggerAction类还包含一个依赖属性,可以将其绑定到你的选项卡控件中:

<TabControl x:Name="tab"/>
                    <Button x:Name="button">
                        <i:Interaction.Triggers>
                            <i:EventTrigger SourceName="button" EventName="Click">
                                <app:MyAction Target="{Binding ElementName=tab}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Button>

Public Class MyAction
    Inherits Interactivity.TriggerAction(Of UIElement)

    Protected Overrides Sub Invoke(parameter As Object)
        DirectCast(Target, TabControl).SelectedIndex = 0
    End Sub

    Shared Sub New()
        _targetProperty = DependencyProperty.Register(
          "Target",
          GetType(UIElement),
          GetType(MyAction),
          New UIPropertyMetadata(Nothing))
    End Sub

    Private Shared _targetProperty As DependencyProperty
    Public Shared ReadOnly Property TargetProperty As DependencyProperty
        Get
            Return _targetProperty
        End Get
    End Property

    Property Target As UIElement
        Get
            Return DirectCast(GetValue(TargetProperty), UIElement)
        End Get
        Set(value As UIElement)
            SetValue(TargetProperty, value)
        End Set
    End Property

End Class

太好了 :-) 请注意,我已更新代码以处理TabControl。 - Dennis Kassel

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