简体   繁体   中英

Intercepting and cancelling the Click event of a WinForms Button

Is there a way to capture the Click event of a Button from the parent Control and prevent it occuring if a variable within the parent Control is true?

For example:

private void AssignEventOverrides()
{
    foreach (Button button in Buttons) 
    {
        // Get the event assigned at design time
        var someUnknownEventHandler = GetTheClickHandlerSomehow(button);

        // Unsubscribe the unknown event
        button.Click -= SomeUnknownEventHandler;

        // Assign the intercepting event
        button.Click += button_Click;
    }
}

private void button_Click(object sender, EventArgs e)
{
    if (!preventClick)
    {
        // Fire the intercepted event that was previously unsubscribed
    }
}

Hopefully there's a nicer way to do this than the above example. Just to note, I don't have any control over the events assigned to the afforementioned buttons. They're subscribed elsewhere and I just need to prevent them happening if a variable is true on the consuming parent Control.

My real scenario:

I'm using Windows Mobile 6.5 (legacy application rewrite) I've created a panel control that can contain N number of child controls. The panel control allows for iPhone style vertical scrolling. I subscribe to the MouseMove event of the child Control and set a variable _isScrolling to true . In the MouseUp event I set _isScrolling to false .

As Click occurs before MouseUp I can check within the Click event to ensure a scroll hadn't occured when the button was pressed. So basically, if a scroll of my panel occurs, I need to prevent the Click event (subscribed at design time) from firing.

This might provide some useful insight

How to remove all event handlers from a control

Specifically, look at

private void RemoveClickEvent(Button b)
{
    FieldInfo f1 = typeof(Control).GetField("EventClick", 
        BindingFlags.Static | BindingFlags.NonPublic);
    object obj = f1.GetValue(b);
    PropertyInfo pi = b.GetType().GetProperty("Events",  
        BindingFlags.NonPublic | BindingFlags.Instance);
    EventHandlerList list = (EventHandlerList)pi.GetValue(b, null);
    list.RemoveHandler(obj, list[obj]);
}

As an alternate way to look at the problem, why not just Subclass the hosting form and look for clicks on your targets. If your preventClick is true, then mark the message as handled to prevent it from getting passed to the child.

What about disabling buttons instead of setting variable within the parent Control to true?

UPDATE: Setting button.Enabled = false definitely will prevent from Click event raising :)

UPDATE 2 (for real scenario): consider creating custom button

public class ScrollableButton : Button
{
    private bool _isScrolling;

    protected override void OnMouseMove(MouseEventArgs mevent)
    {
        _isScrolling = true;
        base.OnMouseMove(mevent);
    }

    protected override void OnMouseUp(MouseEventArgs mevent)
    {
        base.OnMouseUp(mevent);
        _isScrolling = false;
    }

    public bool IsScrolling { get; set; }

    protected override void OnClick(EventArgs e)
    {
        if (!_isScrolling)
            base.OnClick(e);
    }
}

Just replace default buttons with scrollable buttons.

Thanks to Hehewaffles answer , here's the solution I came up with:

/// <summary>
/// Dictionary for holding the controls design time Click EventHandlers.
/// </summary>
private Dictionary<Control, EventHandler> _interceptedClickEventHandlers;

/// <summary>
/// Recursively moves through the child controls assigning events for scrolling and replacing Click events 
/// with an interception EventHandler that prevents the Click event occuring if the Panel was scrolled.
/// </summary>
/// <param name="control">The control to handle the events for and whose children to recurse through.</param>
private void RecursivelySubscribeChildEvents(Control control)
{
    if (!(control is IPanelItem))
    {
        return;
    }

    // Subscribe the events for the scroll handling
    control.MouseDown += new MouseEventHandler(UIPanel_MouseDown);
    control.MouseMove += new MouseEventHandler(UIPanel_MouseMove);
    control.MouseUp += new MouseEventHandler(UIPanel_MouseUp);

    // Intercept the click event
    FieldInfo eventClickField = typeof(Control).GetField("Click", BindingFlags.NonPublic | BindingFlags.Instance);
    if (eventClickField != null)
    {
        if (_interceptedClickEventHandlers == null)
        {
            _interceptedClickEventHandlers = new Dictionary<Control, EventHandler>();
        }

        // Get the original event and store it against the control for actioning later
        EventHandler eventClick = (EventHandler)eventClickField.GetValue(control);
        _interceptedClickEventHandlers.Add(control, eventClick);

        // Unsubscribe the old event and assign the interception event
        control.Click -= (EventHandler)eventClick;
        control.Click += new EventHandler(UIPanel_ChildClick);
    }

    // Recurse the event subscription operation
    foreach (Control child in control.Controls)
    {
        RecursivelySubscribeChildEvents(child);
    }
}

private void UIPanel_ChildClick(object sender, EventArgs e)
{
    EventHandler interceptedHandler = _interceptedClickEventHandlers[(Control)sender];
    if (interceptedHandler != null)
    {
        // Fire the originally subscribed event if the panel is not scrolling
        if (!_isScrolling)
        {
            interceptedHandler(sender, e);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM