-1

I would like to ask for help with some small C# project. I have a windows forms app, whose purpose is to write text entered by user. It basicaly consist of one text box and one button. When your press the button, the program takes the text and jumps into loop, where it always takes just one symbol and send it using SendKeys.Send(). This works without any problem, but recently I wanted to add a feature, which would allow me to stop the program with a keypress. I’m stuck at this point, because the programm is running in the "writing" loop. My only idea was to check for the keypress inside the loop, but I wasn’t able to find any other way of registering keypress, than KeyPress event. Does anyone have any idea??

my code:

public partial class Form1 : Form
{
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
    String textToWrite;
    int delay;
    bool continueWriting = true;

    enum KeyModifier
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
        WinKey = 8
    }

    public Form1()
    {
        InitializeComponent();
        this.KeyPreview = true;
        int id = 0;
        RegisterHotKey(this.Handle, id, (int)KeyModifier.Shift, Keys.A.GetHashCode());
    }

    private void pisBut_Click(object sender, EventArgs e)
    {
        textToWrite= textToWriteTextBox.Text;
        Console.WriteLine(zadaniText);
        continueWriting = true;
        Thread.Sleep(5000);

        try
        {
            delay = Convert.ToInt32(delayBetweenSymbolsTextBox.Text);
        }
        catch
        {
            Console.WriteLine("conversion failed!");
        }

        for (int i = 0; i < textToWrite.Length; i++) 
        {
            // loop intended to take one char and send it 
            Random nahoda = new Random();
            SendKeys.Send(zadaniText[i].ToString());
            int finalDelay = nahoda.Next(delay - 40, delay);
            Console.WriteLine(finalDelay);
            Thread.Sleep(finalDelay);
            if (continueWriting == false) // stop after setting this bool false 
            {
                break;
            }
        }
    }

    private void zadani_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
        if (e.KeyCode == Keys.Enter)
        {
            // interrupt writing
        }
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == 0x0312)
        {
            Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
            KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF);
            int id = m.WParam.ToInt32();                                        .
            MessageBox.Show("Hotkey has been pressed!");
            continueWriting = false;
        }
    }

    private void ExampleForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        UnregisterHotKey(this.Handle, 0);
    }
}

Thanks!!

Papouc
  • 25
  • 7
  • IMHO using non-English words as variables, identifiers and comments, makes your code harder to understand by anyone who don't know the language. To increase the chances of someone answering your question, I would suggest translating your code to English. – Theodor Zoulias Oct 11 '20 at 10:29
  • Thanks for your advice. It should be allright now. – Papouc Oct 11 '20 at 16:04
  • Yeap, it's better now. What does "zadaniText" means? – Theodor Zoulias Oct 11 '20 at 18:42
  • The main issue is `Thread.Sleep(finalDelay);`. You can replace with `await Task.Delay(finalDelay);` See duplicate. Once that's addressed, you will also want to read [this question and its answers carefully](https://stackoverflow.com/questions/767999/random-number-generator-only-generating-one-random-number). As far as the task stuff goes, see also https://stackoverflow.com/questions/1216791/winform-application-ui-hangs-during-long-running-operation and https://stackoverflow.com/questions/47699621/how-does-async-await-allow-for-responsiveness-in-an-application-that-uses-an-eve. – Peter Duniho Oct 12 '20 at 00:17

2 Answers2

0

Your problem is probably related with the UI thread being blocked by repeated calls to Thread.Sleep, making the UI non-responsive. An easy way to solve this problem is be making the handler of the Click event asynchronous (adding the async modifier), and replacing the Thread.Sleep with await Task.Delay. This way the UI will remain responsive during the whole operation of sending the keys.

private async void Button_Click(object sender, EventArgs e)
{
    //...
    await Task.Delay(5000); // Instead of Thread.Sleep(5000);
    //...
}

This may create another problem, that the user will be able to click the button again while a send-keys operation is in progress, but I am sure that you'll find a way to solve this problem.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    Thank you!! This is perfect solution. It really saved me :-). I would also like to apologise for posting the code in my language. I’ll keep this in mind from now on. – Papouc Oct 12 '20 at 11:22
0

I would consider using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Forms and add using System.Reactive.Linq; - then you can do this:

private IDisposable _subscription = null;
private void pisBut_Click(object sender, EventArgs e)
{
    if (int.TryParse(delayBetweenSymbolsTextBox.Text, out delay))
    {
        Random random = new Random();
        _subscription =
            Observable
                .Generate(
                    0, x => x < zadaniText.Length, x => x + 1, x => x,
                    x => TimeSpan.FromMilliseconds(x == 0 ? 5000.0 : random.Next(delay - 40, delay)))
                .ObserveOn(this)
                .Subscribe(i => SendKeys.Send(zadaniText[i].ToString()));
    }
}

Now to stop the send keys midway you can do this: _subscription?.Dispose();.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172