zoukankan      html  css  js  c++  java
  • C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点

    本文介绍在 MVVM 中,如何用 ViewModel 控制焦点。

    这焦点设置个东西嘛,有些争论。就是到底要不要用 ViewModel 来控制视图的键盘输入焦点。这里不讨论,假设你就是要通过 VM,设置输入焦点在哪里。

    MSDN 有解释关于 Focus 的,还有 FocusManager,点击这里打开。不知道的话建议你先看看,只求结果的可以直接看下面代码,抄就是了。这次,初级的解释全部略过,直接说做法,看不懂的请留言。做法很多,大概两种比较符合 MVVM 模式:

    1. DataTrigger 设置 FocusedElement

    Style 中写 Trigger,DataTrigger,值变化触发设置 FocusManager 的 FocusedElement。

    XAML:

    <Grid><Grid.Style><Style><Style.Triggers><DataTrigger Binding="{Binding Path=ScanFtBarCodeNow}" Value="True"><Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=FtNoTextBox}"/></DataTrigger></Style.Triggers></Style></Grid.Style><TextBox name="FtNoTextBox"/></Grid>

    注意:放置 Style 的位置要注意。一层又一层 Grid 的话,FocusManager 单单设置 FocusedElement 为 TextBox 不足够。原因在 MSDN 有说明,不重复。

    2. Attached Property

    使用 Attached Property 为控件加属性,值变化触发 CallBack,然后 UIElement.Focus()。所谓值变化,比如,bool 变为 True 。

    CODE:

    publicstaticclass FocusBehavior {
        publicstaticbool GetIsFocused(DependencyObject obj) {
            return (bool)obj.GetValue(IsFocusedProperty);
        }
        publicstaticvoid SetIsFocused(DependencyObject obj, bool value) {
            obj.SetValue(IsFocusedProperty, value);
        }
        publicstaticreadonly DependencyProperty IsFocusedProperty =
                DependencyProperty.RegisterAttached(
                "IsFocused", 
                typeof(bool),
                typeof(FocusBehavior),
                new UIPropertyMetadata(false, (s, e) => {
                    var sender = (UIElement)s;
                    if ((bool)e.NewValue) {
                        sender.Focus();
                        Keyboard.Focus(sender);
                    }
                    })
                );
    }

    XAML:

    <TextBox local:FocusBehavior.IsFocused="{Binding Path=ScanBarCodeNow}"/>

    这做法虽然简单,但实际绑定的并非 UIElement.IsFocused 这只读属性,看CallBack 代码就知道,两个Focus() 都只是方法。界面操作焦点离开此控件时候,DependencyProperty 的值不会变化。后果是,设置一次 true 后,属性值 true,焦点离开它依然是 true,你再设置它 true,会被认定为值无变化,CallBack 没有执行,Focus() 不会运行。Workaround:先设 false 再设 true。

    好傻吧。改一下,虽然 IsFocus 是只读无法直接绑定去改它,但可以把 GotFocus 和 LostFocus 事件接上此 Attached Property,触发变更 DependencyProperty 的值,使它变成双向绑定。

    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Input;
    
    namespace Lepton_Practical_MVVM_5 {
        publicstaticclass FocusBehavior {
            privatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();
            publicstaticbool? GetIsFocused(DependencyObject obj) {
                return (bool?)obj.GetValue(IsFocusedProperty);
            }
            publicstaticvoid SetIsFocused(DependencyObject obj, bool? value) {
                obj.SetValue(IsFocusedProperty, value);
            }
            publicstaticreadonly DependencyProperty IsFocusedProperty =
                    DependencyProperty.RegisterAttached(
                    "IsFocused",
                    typeof(bool?),
                    typeof(FocusBehavior),
                    new UIPropertyMetadata() {
                        DefaultValue =null,
                        PropertyChangedCallback =
                            (s, e) => {
                                UIElement sender = (UIElement)s;
                                RoutedEventHandler x;
                                if (!handlers.TryGetValue(sender, out x)) {
                                    Attach(sender);
                                }
                                if ((bool)e.NewValue) {
                                    sender.Focus();
                                    Keyboard.Focus(sender);
                                }
                            }
                    });
            privatestaticvoid Attach(UIElement sender) {
                RoutedEventHandler handler = (s, e) => {
                    UIElement ui = (UIElement)s;
                    if (e.RoutedEvent == UIElement.GotFocusEvent) {
                        ui.SetValue(IsFocusedProperty, true);
                    }
                    if (e.RoutedEvent == UIElement.LostFocusEvent) {
                        ui.SetValue(IsFocusedProperty, false);
                    }
                };
                sender.GotFocus += handler;
                sender.LostFocus += handler;
                handlers.Add(sender, handler);
            }
        }
    }
    

    Dictionary 为了记下已连接的 Handler ,以免重复加入。

    XAML:

    <Window x:Class="Lepton_Practical_MVVM_5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:Lepton_Practical_MVVM_5"
            Title="MainWindow" Height="350" Width="525"><Grid><TextBox local:FocusBehavior.IsFocused="{Binding Path=IsTextBox1Focus,Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="49,54,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"/></Grid></Window>

    点击这里下载Attached Property 版本的代码

    这做法是只有加句柄没有移除,你有情况需要移除的话,自己改吧。

    我在这群里,欢迎加入交流:
    开发板玩家群 578649319开发板玩家群 578649319
    硬件创客 (10105555)硬件创客 (10105555)

  • 相关阅读:
    使用ExpandableListView——当有Group选项展开时,如何正确获取长按的Group选项。
    Android 如何全局获取Context
    Android记录11-控制ExpandableListView展开和关闭
    setOnKeyListener响应两次问题
    ExpandableListView方法详解
    Leetcode 75 Sort Colors
    Leetcode 48 Rotate Image
    Leetcode 64 Minimum Path Sum
    Leetcode 268 Missing Number
    Leetcode 39 40 216 Combination Sum I II III
  • 原文地址:https://www.cnblogs.com/leptonation/p/3292961.html
Copyright © 2011-2022 走看看