开发者

What is the best way to simulate a Click with MouseUp & MouseDown events or otherwise?

In WPF most controls have MouseUp and MouseDown events (and the mouse-button-specific variations) but not a simple Click event that can be used right away. If you want to have a click-like behaviour using those events you need to handle both which i consider to be a bit of a pain.

The obvious problem is that you cannot simply omit the MouseDown event because if your click is started on another control and it is released over the control that only handles MouseUp your supposed click will fire while it really should not: Both MouseUp and MouseDown should occur over the same control.

So i would be interested in a more elegant solution to this general problem if there is any.


Notes: There are several good solutions to this as can be seen below, i chose to accept Rachel's answer because it seems to be well received, but additionally i'd like to add the following annotations:

Rachel's button answer is quite clean and straightforward, but you need to wrap your actual control in a button and in some cases you might not really consider your control to be a button just because it can be clicked (e.g. if it is more like a hyperlink), further you need to reference a template every time.

Rick Sladkey's behaviour answer more directly answers the original question of how to just simulate a click/make a control clickable, the drawback is that you need to reference System.Windows.Interactivity and like Rachel's solution it inflates the Xaml-code quite a bit.

My attached event answer has the advantage of being quite close to a normal click event in terms of Xaml-Markup which can be done with one attribute,开发者_Go百科 the only problem i see with it is that the event-attachment in code is not cleanly encapsulated (if anyone knows a fix for that, please add a comment to the answer).


I would use a Button control and overwrite the Button.Template to just show the content directly.

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

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


Here is a behavior you can add to any element so that it will raise the ButtonBase.Click event using the normal button logic:

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));
        };
    }
}

Notice the use of mouse capture/release and the input hit test check. With this behavior in place, we can write click handlers like this:

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

and the code behind:

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

It's easy enough to convert this to all code-behind; the important part is the capture and click logic.

If you are not familiar with behaviors, install the Expression Blend 4 SDK and add this namespace:

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

and add System.Windows.Interactivity to your project.

Edit:

Here's how to attach the click behavior to an element in code-behind and add a handler for the click event:

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);
}


Ok, just thought i'd play around with attached events and see where i'll get with it, the following seems to work in most cases, could use some refinement/debugging:

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));
        }
    }
}

Usage:

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

Edit: Changed it to use mouse-capture instead of storing sender/source which did not get cleared out sufficiently. Remaining problem is that you cannot add the event in code using UIElement.AddHandler(Extensions.ClickEvent, handler) because that omits the attachments of the other mouse events which are needed for raising the click event (you need to use Extensions.AddClickHandler(object, handler) instead).


Here is a simple solution that I use on a fairly regular basis. It is easy, and works in all sorts of scenarios.

In your code, place this.

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;
}

Now all you have to do is setup handlers for all of the controls that you care about i.e.

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

If you have a ton of controls, and don't want to do it all in XAML, you can do it in your controls / window constructor programatically.

This will cover most of your scenarios, and it can easily be tweaked to handle special cases.


First add a Mouse event click function:

/// <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;
}

Add a click event to one of your WPF controls:

private void btnDoSomeThing_Click(object sender, RoutedEventArgs e)
{
    // Do something
}

Finally, Call the click event from any function:

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

To simulate double clicking, add a double click event like PreviewMouseDoubleClick and make sure any code starts in a separate function:

private void lvFiles_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    DoMouseDoubleClick(e);
}

private void DoMouseDoubleClick(RoutedEventArgs e)
{
    // Add your logic here
}

To invoke the double click event, just call it from another function (like KeyDown):

private void someControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
    if (e.Key == Key.Enter)
        DoMouseDoubleClick(e);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜