本文介绍一种点击空白处使控件暗藏的实现办法。

问题形容

思考如下场景,在白板类软件中,点击按钮弹出一个View,心愿在点击空白处间接暗藏掉View,同时能够间接书写,如下图:

实现该需要,能够通过View间通信解决,但这样会减少代码耦合且使逻辑显得简单。

本文通过派生UserControl,将解决逻辑封装在View外部,从而升高代码耦合度。

解决方案

通过剖析需要能够想到,点击空白处时,该View会失去焦点,因而能够通过监听LostFocus事件来解决。

首先,须要设置Focusable属性为true,其默认值为false。而后监听LostFocus事件,当View失去焦点时,Visibility属性置为Collapsed。

此处有个问题,如果点击View外部的子控件,View会先LostFocus,而后立马GotFocus,通过测试距离在20ms内。因而还要响应下GotFocus事件,获取到焦点时,Visibility属性置为Visible。

另外,当点击按钮显示View时,此View并未获取焦点,因而须要监听IsVisibleChanged事件,当NewValue为true时,通过调用Focus使View获取焦点。

还须要解决一个问题。如上文动图所示,需点击按钮显示,再次点击按钮暗藏。但再次点击按钮时,View曾经失去了焦点,此时已暗藏,所以再次点击会导致View暗藏后立马显示。通过测试统计,点击按钮执行命令,到View响应命令执行显示/暗藏,工夫在(50,200)ms范畴内。因而如果在该范畴内View先暗藏后显示,需将其Visibility置为Collapsed。

至此,逻辑根本解决完了,然而还有一个坑。如果应用bool值绑定Visibility(Mode需设置为TwoWay),点击按钮批改bool时,PropertyChanged事件会告诉监听者属性扭转,此时由上个步骤中的逻辑晓得,咱们须要批改Visibility的值,这实践上又会导致bool值的扭转,但bool值并未批改(属性未修改完再次批改),这就导致Visibility与bool值不统一,再次点击按钮不会显示View。咱们只须要异步执行上个步骤,就能够解决。

通过上述解决,点击空白处暗藏View的逻辑就封装到View外面了,外围代码如下所示,感兴趣的能够下载残缺demo试试。如果有其它好的办法,欢送交换(WPF或开源库或者有更好的解决方案)。

// 派生UserControlpublic class MyAutoHideControl : UserControl{    public MyAutoHideControl()        : base()    {        Focusable = true;        _lastTimeCollapsed = DateTime.Now.Ticks / 10000;        IsVisibleChanged += AutoHideControl_IsVisibleChanged;        GotFocus += AutoHideControl_GotFocus;        LostFocus += AutoHideControl_LostFocus;    }    private void AutoHideControl_GotFocus(object sender, RoutedEventArgs e)    {        if (Visibility != Visibility.Visible)            Visibility = Visibility.Visible;    }    private void AutoHideControl_LostFocus(object sender, RoutedEventArgs e)    {        if (Visibility == Visibility.Visible)            Visibility = Visibility.Collapsed;    }    private void AutoHideControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)    {        if ((bool)e.NewValue == (bool)e.OldValue)            return;        if ((bool)e.NewValue)        {            long interval = DateTime.Now.Ticks / 10000 - _lastTimeCollapsed;            if (interval > MinInterval && interval < MaxInterval)            {                if (Visibility == Visibility.Visible)                {                    Dispatcher.BeginInvoke(new Action(() =>                    {                        Visibility = Visibility.Collapsed;                    }));                }            }            else                Focus();        }        else            _lastTimeCollapsed = DateTime.Now.Ticks / 10000;    }    private long _lastTimeCollapsed;    // 需解决再次点击按钮暗藏的状况    private const long MinInterval = 50;    private const long MaxInterval = 200;}
// View<Window ...        xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"        xmlns:local="clr-namespace:AutoHideControl"        Title="AutoHideControl" Height="200" Width="350">    <Window.Resources>        <BooleanToVisibilityConverter x:Key="BooleanToVisibility"/>    </Window.Resources>    <Grid>        <InkCanvas Background="LightCyan"/>        <DockPanel VerticalAlignment="Bottom" Margin="10" Height="Auto">            <local:MyAutoHideView DockPanel.Dock="Top" Width="150" Height="50" Margin="10"                              Visibility="{Binding ShowView,Converter={StaticResource BooleanToVisibility},Mode=TwoWay}"/>            <Button Width="80" Height="30" Command="{Binding ButtonClickedCommand}"                    Content="{c:Binding ShowView ? \'Hide\' : \'Show\'}"/>        </DockPanel>    </Grid></Window>// ViewModelpublic class MainWindowViewModel : INotifyPropertyChanged{    public bool ShowView    {        get => _showView;        set        {            _showView = value;            OnPropertyChanged();        }    }    public DelegateCommand ButtonClickedCommand =>        _buttonClickedCommand ?? (_buttonClickedCommand = new DelegateCommand        {            ExecuteAction = (_)=> ShowView = !_showView        });    public void OnPropertyChanged([CallerMemberName] string name = "")=>        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));    public event PropertyChangedEventHandler PropertyChanged;    private bool _showView;