控件如何处理鼠标点击控件外部的事件?

30

我正在编写一个自定义控件,想让控件在用户点击控件之外的区域时从编辑状态切换到正常状态。我正在处理LostFocus事件,这有助于当用户切换到另一个可聚焦的控件时或者按Tab键离开控件时。但如果他们没有点击其他可聚焦的东西,它不会退出编辑状态。因此我有两个解决方案:

  • 在进入编辑状态时向上遍历树形结构到最顶层元素,并添加MouseDownEvent处理程序(并处理"handled"事件)。在处理程序中,我将把控件从编辑状态中移除,并从最顶层元素中删除处理程序。这似乎有点像hack,但可能效果很好。

示例代码:

private void RegisterTopMostParentMouseClickEvent()
{
   _topMostParent = this.FindLastVisualAncestor<FrameworkElement>();
   if ( _topMostParent == null )
      return;
   _topMostParent.AddHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ), true );
}

private void UnRegisterTopMostParentMouseClickEvent()
{
   if ( _topMostParent == null )
      return;
   _topMostParent.RemoveHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ) );
   _topMostParent = null;
}
  • 使用Mouse.PreviewMouseDownOutsideCapturedElement并在我的控件上添加处理程序。 在处理程序中,我会将控件退出其编辑状态。 但是我似乎无法使事件触发。 Mouse.PreviewMouseDownOutsideCapturedElement何时启动?

示例代码:

AddHandler( Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler( EditableTextBlockPreviewMouseDownOutsideCapturedElementEvent ), true );
4个回答

41

关于鼠标焦点的答案澄清一下——它很有用,但我不得不进一步挖掘和摆弄才能获得实际有效的东西:

我试图实现类似组合框的东西,并需要类似的行为——在单击其他内容时让下拉菜单消失,而控件并不知道其他内容是什么。

我对下拉按钮有以下事件:

    private void ClickButton(object sender, RoutedEventArgs routedEventArgs)
    {
        //do stuff (eg activate drop down)
        Mouse.Capture(this, CaptureMode.SubTree);
        AddHandler();
    }

CaptureMode.SubTree 的意思是只获取控件外部的事件,并且控件内的任何鼠标活动都会像平常一样传递给其他物体。在 UIElement 的 CaptureMouse 中没有提供此枚举选项,这意味着您将收到 HandleClickOutsideOfControl 的调用,而不是子控件或控件内其他处理程序的调用。即使您没有订阅它们正在使用的事件,也是如此 - 完全捕获鼠标有点过头了!

    private void AddHandler()
    {
        AddHandler(Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(HandleClickOutsideOfControl), true);
    }

为了清晰简洁起见,我并没有在此处包括在适当时点保留和移除处理程序的步骤。

最后,在处理程序中,你需要再次释放捕获。

    private void HandleClickOutsideOfControl(object sender, MouseButtonEventArgs e)
    {
        //do stuff (eg close drop down)
        ReleaseMouseCapture();
    }

9
有些控件(如TextBox)在被点击时会释放捕获,因此每次控件触发 "LostMouseCapture" 事件时都需要重新附加处理程序。这只是我的观察。 - Dave_cz
@Dave_cz 不太对。事件订阅仍然处于活动状态,因此您需要重复使用Mouse.Capture(...)。这只是我的观察。 - vitidev

16

捕获鼠标。

当一个对象捕获了鼠标,所有与鼠标相关的事件都被视为由具有鼠标捕获的对象执行,即使鼠标指针在另一个对象上。


2
所以这个方法完美地解决了问题。我只需要在进入编辑模式时捕获鼠标,然后WPF会自动在你点击元素外部时将焦点移开。失去焦点后,我只需要小心地释放鼠标捕获即可。谢谢! - chrislarson
4
我需要注意的另一件事是,当你按下 Windows 键或其他应用程序启动时,我可能会失去鼠标控制权,但这不会停止我进行编辑。因此,我必须处理 IsMouseCapturedChanged 事件。例如:private void CustomControlIsMouseCapturedChanged( object sender, DependencyPropertyChangedEventArgs e ) { if ( (bool)e.NewValue == false) { IsEditing = false; } } - chrislarson
1
由于鼠标标题,所有其他控件将无法被点击或者点击它们不会起作用。即使在鼠标悬停在元素上时,它们也不再突出显示。如何解决这个问题?这种行为超出了不幸的范畴。 - Kaspatoo

3

通常我会获取父窗口并添加一个预览处理程序,即使已经处理过。有时当 MouseCapture 不足够时,这种技术就派上用场了:

Window.GetWindow(this).AddHandler
(
    UIElement.MouseDownEvent,
    (MouseButtonEventHandler)TextBox_PreviewMouseDown,
    true
);

0
我会用不同的方法来处理这个问题 - 当用户点击表单的其他部分时,让包含控件的表单失去焦点。
让控件实际失去焦点比尝试在某些情况下“模拟”失去焦点要干净得多,因为除非控件真正失去焦点,否则它仍然会接受键盘输入等操作。

那么你的意思是让表单可聚焦吗?因为只有在转移到另一个元素时,焦点才不会自动取消,对吧?例如,这不会从TextBox中去除焦点:<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"      x:Class="FocusTest.MainWindow"      x:Name="Window"      Width="640"      Height="480">  <Grid>   <TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Width="75"/>  </Grid> </Window> - chrislarson
@chrislarson 你可以设置另一个控件(例如一个没有文本的标签)来获得焦点 - 参见有没有办法使UI元素失去焦点 - Justin
这对我来说完全没问题,我仍然可以通过控件是否具有焦点来判断控件是否处于编辑状态。但是我仍然需要一种可靠的方法,在用户单击控件外部时使控件失去焦点。 - chrislarson
1
@chrislarson 在表单上处理MouseClick事件 - 如果用户单击控件,则该控件将首先接收事件,因此可以处理它,防止其冒泡到表单处理程序。但在表单的其他位置点击将由此事件处理,这是您可以将焦点移动到虚拟焦点控件的地方。 - Justin

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