使用 MouseUp 和 MouseDown 事件或其他方式模拟单击的最佳方法是啥?

Posted

技术标签:

【中文标题】使用 MouseUp 和 MouseDown 事件或其他方式模拟单击的最佳方法是啥?【英文标题】:What is the best way to simulate a Click with MouseUp & MouseDown events or otherwise?使用 MouseUp 和 MouseDown 事件或其他方式模拟单击的最佳方法是什么? 【发布时间】:2011-06-17 08:10:44 【问题描述】:

在 WPF 中,大多数控件都有 MouseUpMouseDown 事件(以及特定于鼠标按钮的变体),但不是可以立即使用的简单 Click 事件。如果您想使用这些事件进行类似点击的行为,您需要同时处理这两个事件,我认为这有点痛苦。

明显的问题是你不能简单地忽略MouseDown 事件,因为如果你的点击是在另一个控件上开始的,并且它在只处理MouseUp 的控件上被释放,你假设的点击将会触发,而实际上它不应该触发: MouseUpMouseDown 都应该出现在同一个控件上。

所以如果有的话,我会对这个一般问题的更优雅的解决方案感兴趣。


注意事项: 如下所示,有几个很好的解决方案,我选择接受 Rachel 的回答,因为它似乎很受欢迎,但另外我想添加以下注释:

Rachel's button answer 非常简洁明了,但是您需要将您的实际控件包装在一个按钮中,并且在某些情况下,您可能不会仅仅因为它可以被点击而认为您的控件是一个按钮(例如,如果它更多像超链接一样),而且您每次都需要引用一个模板。

Rick Sladkey's behaviour answer 更直接地回答了如何仅模拟单击/使控件可单击的原始问题,缺点是您需要引用 System.Windows.Interactivity 并且像 Rachel 的解决方案一样,它使 Xaml 代码膨胀了很多。

My attached event answer 的优点是就 Xaml-Markup 而言非常接近正常的点击事件,这可以通过一个属性来完成,我看到的唯一问题是代码中的事件附件不干净封装(如果有人知道解决方法,请在答案中添加评论)。

【问题讨论】:

WPF 控件与 WinForms 的不同之处在于它们是无外观的。它不是由 UI 和行为组成的控件,而是行为。按钮的默认 UI 取决于所使用的主题,但它会被任何表现得像按钮但不一定看起来像按钮的东西覆盖。 我知道这已经很老了,但我注意到 Rachel 的回答的一个问题是,当控件位于 ControlTemplate 中时,它们不能很容易地被引用。解决方法是在每个控件上创建一个Loaded 事件,然后在代码中实例化它们。为简单起见,我将坚持使用 A.R. 的解决方案。 @killianmcc:无论如何,您都不应该在控件模板中引用控件,因此您可能做错了什么。您的实际内容不应该在 ControlTemplate 中,它只是您的内容的框架,然后将放置在 ContentPresenter 所在的位置。此外,您可以将模板内的属性绑定到 Button,通常会为各种画笔和布局属性这样做。 放置在 ContentPresenter 所在的位置,如在 ControlTemplate?但我无法绑定所有属性(例如,这是一个子窗口,我正在向其传递必须在 TextBlock 中显示的文本,该文本必须在 ControlTemplate 内)。 @killianmcc:我编辑了 Rachel 的答案,在示例中,Label 将放置在模板中ContentPresenter 的位置。由于Label 不是inside 模板,因此引用它应该没有问题。不要将TextBlock 放在模板中,除非您只想设置Text,然后您可以使用Text="TemplateBinding Content",然后您设置为按钮内容的所有内容都以TextBlock 结尾(这可能会导致非文本例外;只需使用ContentPresenter,它会自动绑定到Content,并为文本内容创建TextBlock 【参考方案1】:

我会使用Button 控件并覆盖Button.Template 以直接显示内容。

<ControlTemplate x:Key="ContentOnlyTemplate" TargetType="x:Type Button">
    <ContentPresenter />
</ControlTemplate>

<Button Template="StaticResource ContentOnlyTemplate">
    <Label Content="Test"/>
</Button>

【讨论】:

我正在寻找非按钮控件的点击行为(例如TextBlocks 我会将可点击项目设为按钮并覆盖 ControlTemplate 以显示标签...我将更改我的帖子以显示示例 我知道这种方法,我什至在我的问题草稿中对其进行了概述,但又将其删除(无论如何最好将其作为答案)。这还不错,因为至少您不再需要使用这些事件,也许这是最好的解决方案,但也许其他人已经想到了更好的方法。 @H.B.:这是使用 WPF 的预期解决方案。控件是“无外观”的,因为它们可以以您选择的任何方式进行模板化 - 因此任何需要“可点击”的控件都可以是带有自定义模板的按钮。 @Rachel:我知道这只是一个例子,但我会删除Label 并在那里只添加一个ContentPresenter,然后这是创建可点击控件的一般方法。模板名称当然是ClickableControl,或者可能是ContentOnlyTemplate。 @Dan Puzey:听起来很合理。【参考方案2】:

这是您可以添加到任何元素的行为,以便使用普通按钮逻辑引发ButtonBase.Click 事件:

public class ClickBehavior : Behavior<FrameworkElement>

    protected override void OnAttached()
    
        AssociatedObject.MouseLeftButtonDown += (s, e) =>
        
            e.Handled = true;
            AssociatedObject.CaptureMouse();
        ;
        AssociatedObject.MouseLeftButtonUp += (s, e) =>
        
            if (!AssociatedObject.IsMouseCaptured) return;
            e.Handled = true;
            AssociatedObject.ReleaseMouseCapture();
            if (AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)) != null)
                AssociatedObject.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
        ;
    

注意使用鼠标捕获/释放和输入命中测试检查。有了这种行为,我们可以像这样编写点击处理程序:

<Grid>
    <Rectangle Width="100" Height="100" Fill="LightGreen" ButtonBase.Click="Rectangle_Click">
        <i:Interaction.Behaviors>
            <utils:ClickBehavior/>
        </i:Interaction.Behaviors>
    </Rectangle>
</Grid>

以及背后的代码:

    private void Rectangle_Click(object sender, RoutedEventArgs e)
    
        Debug.WriteLine("Code-behind: Click");
    

将其转换为所有代码隐藏很容易;重要的部分是捕获和点击逻辑。

如果您不熟悉行为,请安装 Expression Blend 4 SDK 并添加此命名空间:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

并将System.Windows.Interactivity 添加到您的项目中。

编辑:

以下是如何将点击行为附加到代码隐藏中的元素并为点击事件添加处理程序:

void AttachClickBehaviors()

    AttachClickBehavior(rectangle1, new RoutedEventHandler(OnAttachedClick));


void OnAttachedClick(object sender, RoutedEventArgs e)

    Debug.WriteLine("Attached: Click");


// Reusable: doesn't need to be in the code-behind.
static void AttachClickBehavior(FrameworkElement element, RoutedEventHandler clickHandler)

    Interaction.GetBehaviors(element).Add(new ClickBehavior());
    element.AddHandler(ButtonBase.ClickEvent, clickHandler);

【讨论】:

现在测试了一下,问题和我的解决方法一样:Popups好像不行,这很烦人... 事件处理程序对于弹出窗口通常不能按预期工作,因为它们不是可视化树的一部分。解决方法是在弹出窗口的子元素上安装处理程序(或行为)。您可以使用VisualTreeHelper.GetChild(popup, 0) 来获取该元素,或者您可以通过名称访问它。 我找不到 WPF 文档,但此 Silverlight 文档描述了该问题:视觉树外的路由事件:msdn.microsoft.com/en-us/library/cc189018(v=vs.95).aspx【参考方案3】:

好的,只是想我会玩一些附加的事件,看看我会在哪里得到它,以下似乎在大多数情况下都有效,可以使用一些改进/调试:

public static class Extensions

    public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
        "Click",
        RoutingStrategy.Bubble,
        typeof(RoutedEventHandler),
        typeof(UIElement)
        );

    public static void AddClickHandler(DependencyObject d, RoutedEventHandler handler)
    
        UIElement element = d as UIElement;
        if (element != null)
        
            element.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown);
            element.MouseLeftButtonUp += new MouseButtonEventHandler(element_MouseLeftButtonUp);
            element.AddHandler(Extensions.ClickEvent, handler);
        
    

    public static void RemoveClickHandler(DependencyObject d, RoutedEventHandler handler)
    
        UIElement element = d as UIElement;
        if (element != null)
        
            element.MouseLeftButtonDown -= new MouseButtonEventHandler(element_MouseLeftButtonDown);
            element.MouseLeftButtonUp -= new MouseButtonEventHandler(element_MouseLeftButtonUp);
            element.RemoveHandler(Extensions.ClickEvent, handler);
        
    

    static void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    
        UIElement uie = sender as UIElement;
        if (uie != null)
        
            uie.CaptureMouse();
        
    
    static void element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    
        UIElement uie = sender as UIElement;
        if (uie != null && uie.IsMouseCaptured)
        
            uie.ReleaseMouseCapture();
            UIElement element = e.OriginalSource as UIElement;
            if (element != null && element.InputHitTest(e.GetPosition(element)) != null) element.RaiseEvent(new RoutedEventArgs(Extensions.ClickEvent));
        
    

用法:

<TextBlock local:Extensions.Click="TextBlock_Click" Text="Test"/>

编辑: 将其更改为使用鼠标捕获,而不是存储未充分清除的发件人/来源。剩下的问题是您无法使用UIElement.AddHandler(Extensions.ClickEvent, handler) 在代码中添加事件,因为这省略了引发单击事件所需的其他鼠标事件的附件(您需要改用Extensions.AddClickHandler(object, handler))。

【讨论】:

【参考方案4】:

这是一个我经常使用的简单解决方案。它很简单,适用于各种场景。

在你的代码中,放置这个。

private object DownOn = null;

private void LeftButtonUp(object sender, MouseButtonEventArgs e)

  if (DownOn == sender)
  
    MessageBox.Show((sender as FrameworkElement).Name);
  
  DownOn = null;


private void LeftButtonDown(object sender, MouseButtonEventArgs e)

  DownOn = sender;

现在您所要做的就是为您关心的所有控件设置处理程序,即

<Border Background="Red"  MouseLeftButtonUp="LeftButtonUp" MouseLeftButtonDown="LeftButtonDown".../>
<Border Background="Blue" MouseLeftButtonUp="LeftButtonUp" MouseLeftButtonDown="LeftButtonDown".../>

如果您有大量控件,并且不想全部在 XAML 中完成,您可以在控件/窗口构造函数中以编程方式完成。

这将涵盖您的大部分场景,并且可以轻松调整以处理特殊情况。

【讨论】:

这是我自己在对 ChrisF 的回答的评论中指出的那样做的方式,还不错,但也不是很优雅...... 也许不是,但经验告诉我,必须为简单的行为制作一堆模板很快就会进入矫枉过正的领域。看看人们还有什么想法会很有趣。 我自己现在想出了一个有趣的解决方案,如果您有兴趣,它扩展了这种方法。【参考方案5】:

先添加一个鼠标事件点击函数:

/// <summary>
/// Returns mouse click.
/// </summary>
/// <returns>mouseeEvent</returns>
public static MouseButtonEventArgs MouseClickEvent()

    MouseDevice md = InputManager.Current.PrimaryMouseDevice;
    MouseButtonEventArgs mouseEvent = new MouseButtonEventArgs(md, 0, MouseButton.Left);
    return mouseEvent;

将点击事件添加到您的 WPF 控件之一:

private void btnDoSomeThing_Click(object sender, RoutedEventArgs e)

    // Do something

最后,从任意函数调用点击事件:

btnDoSomeThing_Click(new object(), MouseClickEvent());

要模拟双击,请添加一个双击事件,如 PreviewMouseDoubleClick 并确保任何代码都在单独的函数中开始:

private void lvFiles_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)

    DoMouseDoubleClick(e);


private void DoMouseDoubleClick(RoutedEventArgs e)

    // Add your logic here

要调用双击事件,只需从另一个函数(如 KeyDown)调用它:

private void someControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)

    if (e.Key == Key.Enter)
        DoMouseDoubleClick(e);

【讨论】:

抱歉,问题的重点不是真正完全模拟代码中的点击,而是为不公开Click 事件的控件创建所需的行为。

以上是关于使用 MouseUp 和 MouseDown 事件或其他方式模拟单击的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在触摸设备上触发 mousedown 和 mouseup

mousedown/mouseup/click执行顺序

vb.net中对mouseUp事件的疑惑

如何在 mousedown 元素之外捕获 mouseup 事件? (即正确的拖放?)

在jQuery中同时拥有mousedown / mouseup和dblclick

在 Tampermonkey 中模拟 mousedown、click、mouseup 序列?