乐趣区

关于WPF:WPF-点击空白处隐藏View

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

问题形容

思考如下场景,在白板类软件中,点击按钮弹出一个 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 或开源库或者有更好的解决方案)。

// 派生 UserControl
public 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>

// ViewModel
public 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;
退出移动版