【问题标题】:WPF - Set Focus when a button is clicked - No Code BehindWPF - 单击按钮时设置焦点 - 没有代码
【发布时间】:2019-08-20 03:13:50
【问题描述】:

有没有办法使用 WPF Triggers 将 Focus 从一个控件设置到另一个控件?

像下面的例子:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>  
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>

    <TextBox Name="txtName"></TextBox>    
    <TextBox Grid.Row="1" Name="txtAddress"></TextBox>
    <Button Grid.Row="2" Content="Finish">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">

                <!-- Insert cool code here-->  

            </EventTrigger>
        </Button.Triggers>
    </Button>
  </Grid>
</Page>

有没有办法让这个EventTrigger 专注于文本框“txtName”?

我正在尝试找到使用严格 MVVM 执行此类操作的方法。如果这是不应该通过 XAML(在 MVVM 中)完成的事情,那很好。但我希望看到一些关于它如何适应 MVVM 模式的文档,在 XAML 之外执行它。

【问题讨论】:

    标签: c# wpf mvvm focus eventtrigger


    【解决方案1】:

    您是否考虑过使用附加行为。它们很容易实现和使用 AttachedProperty。虽然它仍然需要代码,但这些代码在一个类中被抽象出来并被重用。它们可以消除“代码隐藏”的需要,并且经常与 MVVM 模式一起使用。

    试试这个,看看它是否适合你。

    public class EventFocusAttachment
    {
        public static Control GetElementToFocus(Button button)
        {
            return (Control)button.GetValue(ElementToFocusProperty);
        }
    
        public static void SetElementToFocus(Button button, Control value)
        {
            button.SetValue(ElementToFocusProperty, value);
        }
    
        public static readonly DependencyProperty ElementToFocusProperty =
            DependencyProperty.RegisterAttached("ElementToFocus", typeof(Control), 
            typeof(EventFocusAttachment), new UIPropertyMetadata(null, ElementToFocusPropertyChanged));
    
        public static void ElementToFocusPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var button = sender as Button;
            if (button != null)
            {
                button.Click += (s, args) =>
                    {
                        Control control = GetElementToFocus(button);
                        if (control != null)
                        {
                            control.Focus();
                        }
                    };
            }
        }
    }
    

    然后在您的 XAML 中执行类似...

    <Button 
        Content="Click Me!" 
        local:EventFocusAttachment.ElementToFocus="{Binding ElementName=textBox}" 
        />
    <TextBox x:Name="textBox" />
    

    【讨论】:

    • 我喜欢这个。我要试试看。
    • 效果很好。虽然它使用代码,但它不是窗口后面的代码,所以我可以。谢谢!
    • 这是我能够与 DataTemplate 一起使用并从 ViewModel 聚焦文本框的唯一有效解决方案。谢谢。
    • 如果我的 TextBox 最初是隐藏的并且我的 DataTemplate 通过触发器使其可见,然后尝试将焦点设置为它,这似乎不起作用。
    【解决方案2】:

    我不在视觉工作室附近,所以我现在实际上无法尝试这个,但在我的脑海中,你应该能够做这样的事情:

    FocusManager.FocusedElement="{Binding ElementName=txtName}">
    

    编辑:

    这里有一个关于这个的后续问题(最近被问到):How to set autofocus only in xaml? 其中包含这个方法,以及关于如何使用它的一些不同想法。

    【讨论】:

    • 我喜欢这个,但我需要更多的代码来看看它是如何工作的。我找不到使用 EventTrigger 进行设置的方法。 (据我所知,故事板无法做到这一点。)
    • @Vaccano - 我的谈话已经很晚了,但是如果你在点击事件之后像你最初发布的那样非常想改变焦点,你不需要 EventTrigger总而言之,您可以设置 caesay 指出的焦点元素属性...一旦单击按钮,它将焦点更改为绑定元素。
    • 它在这里完全按照 OP 的要求工作。干得好,凯赛!
    • 这显然在 DataTemplate 中有效,我首先将 TextBox 的 Visibility 设置为 True,然后尝试使用上述方法将焦点设置为它。
    【解决方案3】:

    您也可以使用 WPF 行为...

        public class FocusElementAfterClickBehavior : Behavior<ButtonBase>
    {
        private ButtonBase _AssociatedButton;
    
        protected override void OnAttached()
        {
            _AssociatedButton = AssociatedObject;
    
            _AssociatedButton.Click += AssociatedButtonClick;
        }
    
        protected override void OnDetaching()
        {
            _AssociatedButton.Click -= AssociatedButtonClick;
        }
    
        void AssociatedButtonClick(object sender, RoutedEventArgs e)
        {
            Keyboard.Focus(FocusElement);
        }
    
        public Control FocusElement
        {
            get { return (Control)GetValue(FocusElementProperty); }
            set { SetValue(FocusElementProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for FocusElement.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FocusElementProperty =
            DependencyProperty.Register("FocusElement", typeof(Control), typeof(FocusElementAfterClickBehavior), new UIPropertyMetadata());
    }
    

    这是使用该行为的 XAML。

    包括命名空间:

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:local="clr-namespace:WpfApplication1"
    

    将 WPF 行为附加到按钮并绑定要设置焦点的元素:

    <Button Content="Focus" Width="75">
        <i:Interaction.Behaviors>
            <local:FocusElementAfterClickBehavior FocusElement="{Binding ElementName=CheckBoxComboBox, Mode=OneWay}"/>
        </i:Interaction.Behaviors>
    </Button>
    <ComboBox x:Name="CheckBoxComboBox" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Grid.Row="1"/>
    

    因此,这样您就没有任何代码,并且可以在任何继承自 ButtonBase 的控件上重复使用。

    希望这对某人有所帮助。

    【讨论】:

    • 这比附加属性更好,因为它具有更好的 Blend 支持。但是,如果您不使用混合,那么这只需要一个 helluva 更多的 xaml 代码。
    • 不要忘记添加对 System.Windows.Interactivity 的引用。
    【解决方案4】:

    您需要一个TriggerAction 来调用所需控件上的 Focus() 方法。

    public class SetFocusTrigger : TargetedTriggerAction<Control>
    {
     protected override void Invoke(object parameter)
     {
        if (Target == null) return;
    
        Target.Focus();
     }
    }
    

    要将焦点设置为 Control ,您可以在 LayoutRoot(或任何控件)之后放置一个 Triggers 集合,选择事件作为触发器,然后选择 SetFocusTrigger 作为要运行的类。在 SetFocusTrigger 声明中,使用 TargetName 属性输入要接收焦点的控件的名称。

    <Button x:Name="LayoutRoot" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Clicked">
            <local:SetFocusTrigger TargetName="StartHere"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <TextBox x:Name="StartHere"/>
    </Button>
    

    【讨论】:

    • 这是一个很好的解决方案 - 谢谢! XAML 中只有一个小错误...EventName 应该是“Click”而不是“Clicked”
    • 现在可以使用空传播运算符将 Invoke 方法变成单线
    【解决方案5】:

    这是你想要的吗?

        <TextBox Name="txtName"></TextBox>
        <TextBox Grid.Row="1" Name="txtAddress"></TextBox>
        <Button Grid.Row="2" Content="Finish">
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <EventSetter Event="Click" Handler="MoveFocusOnClick" />
                </Style>
            </Button.Style>
            <!--<Button.Triggers>
                <EventTrigger RoutedEvent="Button.Click">
                </EventTrigger>
            </Button.Triggers>-->
        </Button>
    

    c#:

        public void MoveFocusOnClick(object sender, RoutedEventArgs e)
        {
            Keyboard.Focus(txtName); // Or your own logic
        }
    

    【讨论】:

    • 是的,如果我愿意使用后面的代码,那么有几种方法可以实现这一点。我想知道是否有办法在没有代码的情况下做到这一点。
    【解决方案6】:

    这与 Ian Oakes 的解决方案相同,但我做了一些小改动。

    1. 按钮类型可以更通用,即ButtonBase,以处理更多情况,如ToggleButton
    2. 目标类型也可以更通用,即UIElement。从技术上讲,我想这可能是IInputElement
    3. 将事件处理程序设为静态,这样它就不会在每次使用它时生成运行时闭包。
    4. [编辑:2019] 更新为使用空条件和 C#7 表达式正文语法。

    非常感谢伊恩。


    public sealed class EventFocusAttachment
    {
        public static UIElement GetTarget(ButtonBase b) => (UIElement)b.GetValue(TargetProperty);
    
        public static void SetTarget(ButtonBase b, UIElement tgt) => b.SetValue(TargetProperty, tgt);
    
        public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(
                "Target",
                typeof(UIElement),
                typeof(EventFocusAttachment),
                new UIPropertyMetadata(null, (b, _) => (b as ButtonBase)?.AddHandler(
                    ButtonBase.ClickEvent,
                    new RoutedEventHandler((bb, __) => GetTarget((ButtonBase)bb)?.Focus()))));
    };
    

    用法基本同上:

    <ToggleButton z:EventFocusAttachment.Target="{Binding RelativeSource={RelativeSource Self}}" />
    

    请注意,该事件可以定位/聚焦原始按钮本身。

    【讨论】:

      【解决方案7】:

      看看你是否使用了任何 Dispatcher 会很有帮助,但这是我在代码中使用的一个小技巧。只需在 XAML 中使用 Loaded 事件并创建一个新的处理程序。在该处理程序中粘贴此代码和宾果游戏!你准备好了

      您加载的事件在此处带有一些参数...

      {
          Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new System.Action(() => {
              The element to be focused goes here......
          }));
      }
      

      PS:它需要 Windows。线程如果你不知道;)

      【讨论】:

        猜你喜欢
        • 2011-01-04
        • 1970-01-01
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多