user3037147
user3037147

Reputation: 63

C# Detect detect key down after button click

I would like to perform some action after particular key is pressed after button click. Sth like this: I click button then press End key and then some action is performed.

I tried with this simple code but it doesn't work.

private void buttonStart_Click(object sender, EventArgs e)
    {
        KeyDown += OnKeyDown;         
    }

    private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
    {
        if (keyEventArgs.KeyCode == Keys.Return)
            label2.Text = "siala";
    }
}

Upvotes: 2

Views: 2824

Answers (3)

Enigmativity
Enigmativity

Reputation: 117124

I would look at using Microsoft's Reactive Framework (Rx) for this (NuGet "Rx-Main", "Rx-WinForms", "Rx-WPF") you you can very cleanly describe what you want in a single LINQ query.

Rx allows you to create streams of events that you can query.

To start with make two streams of data - button clicks and key downs.

var clicks = Observable.FromEventPattern(
        h => this.button1.Click += h,
        h => this.button1.Click -= h);

var keydowns = Observable.Merge(
        Observable.FromEventPattern<KeyEventHandler, KeyEventArgs>(
            h => this.button1.KeyDown += h,
            h => this.button1.KeyDown -= h),
        Observable.FromEventPattern<KeyEventHandler, KeyEventArgs>(
            h => this.KeyDown += h,
            h => this.KeyDown -= h));

This is by far the hardest code to comprehend. The nice thing is that you don't really need to think too hard - they are just even handlers done with Rx.

Now here's the extremely nice query that you can now perform:

var query =
    from c in clicks
    from kd in keydowns
        .Take(1)
        .TakeUntil(clicks)
    where kd.EventArgs.KeyCode == Keys.Return
    select kd;

This says "when I get a click, take only 1 key down unless I get a second click, but only take that one key down if it happens to be 'return'."

The only last step you have to do is subscribe to the values being produced.

var subscription = query.Subscribe(kd => this.label1.Text = "siala");

You could even go one step further and do this:

var query =
    from c in clicks
    from kd in keydowns
        .Take(1)
        .TakeUntil(clicks)
        .TakeUntil(Observable.Timer(TimeSpan.FromSeconds(3.0)))
    where kd.EventArgs.KeyCode == Keys.Return
    select kd;

This now says "when I get a click, take only 1 key down unless I get a second click OR IF 3 SECONDS have passed, but only take that one key down if it happens to be 'return'."

And if you want the subscription to stop just call subscription.Dispose();

Rx is very powerful.

Upvotes: 1

Benj
Benj

Reputation: 989

I assume that the button keeps the focus after clicking it, so the event is not propagated to the Form. You can use the code below to check where the event is fired

Are you using WPF or Winforms?

    private void button1_Click(object sender, EventArgs e)
    {
        this.KeyDown += Form1_KeyDown;
        this.button1.KeyDown += button1_KeyDown;
    }

    void button1_KeyDown(object sender, KeyEventArgs e)
    {
        MessageBox.Show("[BUTTON] PRE-KEY-CHECK"); e.Handled = false;

        if (e.KeyCode == Keys.F1)
        {
            MessageBox.Show("[BUTTON] POST-KEY-CHECK");
        }
    }

    void Form1_KeyDown(object sender, KeyEventArgs e)
    {
        MessageBox.Show("[WINDOW] PRE-KEY-CHECK"); e.Handled = false;

        if(e.KeyCode == Keys.F1)
        {
            MessageBox.Show("[WINDOW] POST-KEY-CHECK");
        }
    }

EDIT: A solution for the problem can be found Keyboard event propagation in winforms

Basically you have to set this.KeyPreview=true;

EDIT 2: Allowing the event to be registered only once

    Boolean isEventhandlerRegistered = false;
    private void button1_Click(object sender, EventArgs e)
    {
        if (!this.isEventhandlerRegistered)
        {
            this.PreviewKeyDown += Form1_PreviewKeyDown;
            this.isEventhandlerRegistered = true;
        }
    }

    void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
        if (e.KeyCode == Keys.F1)
        {
            MessageBox.Show("[WINDOW] POST-KEY-CHECK");
        }
    }

Upvotes: -1

TimothyP
TimothyP

Reputation: 21755

You did not specify whether you are using Winforms or WPF,
the following is an example for WPF but should be very similar to Winforms as well:

bool canClick = true;

public MainWindow()
{
    InitializeComponent();
    TheBox.PreviewMouseDown += TheGrid_PreviewMouseDown;          
}

async void TheGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{            
    if (canClick)
    {
        canClick = false;
        TheBox.PreviewKeyDown += TheGrid_PreviewKeyDown;                
        await Task.Delay(5000);
        TheBox.PreviewKeyDown -= TheGrid_PreviewKeyDown;
        canClick = true;
    }
}

void TheGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
    Console.WriteLine("Button {0}", e.Key);
}

The idea here is that you register an event handler for PreviewKeyDown when
the mouse is clicked and then remove that handler after 5 seconds.

This way the user would have to enter the key within 5 seconds of clicking the mouse.
The boolean is used to make sure the event handler isn't registered more than once per time.

You should also look into reactive extensions which might provide a cleaner
way of solving this problem.

Upvotes: 1

Related Questions