ESC pour interrompre un calcul dans une application WinForm en C#

eguignier Messages postés 8 Date d'inscription lundi 6 novembre 2000 Statut Membre Dernière intervention 11 juin 2020 - 8 juin 2020 à 17:00
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 - 11 juin 2020 à 11:36
Bonjour à tous,

J'ai construit une application Winform en C#.
En cliquant sur un bouton, une procédure avec de nombreux calculs est lancée.
J'aimerais pouvoir arrêter correctement la procédure en appuyant sur la touche d'échappement (ESC).

Mais il y a plusieurs problèmes :

L'événement KeyDown mis en place sur le formulaire principal ne peut pas être remontée (comme une interruption) pendant que la procédure est en cours.
L'événement KeyDown ne peut pas être détecté tant que l'appelant (fonction button_Click()) n'a pas rendu le focus sur le formulaire principal.
L'événement KeyDown semble n'être détectable que lorsque l'application est disponible, en attente.

J'ai essayé différentes solutions :

J'ai placé la procédure de calcul dans un thread séparé, lancé par un bouton dont la fonction button_Click() se termine immédiatement après.
De cette façon, la détection de l'événement KeyDown devient possible au niveau du Thread principal (Form1). Ainsi, à l'aide d'un Flag, il est possible d'arrêter la procédure placée dans le fil.
Le problème est que les autres tâches qui devaient suivre la fin des calculs doivent également être placées dans le thread, sinon il n'est pas possible de continuer les opérations dans le thread principal après la fin des calculs.

Autre test : Utiliser un "Low level Keyboard hook" (https://youtu.be/f5gLsA7wX5U).
Il fonctionne bien du moment qu'il utilisé dans le thread principal (Form1) et que l'application est en attente d'entrées de l'utilisateur, mais si le fil principal est en train d'exécuter la procédure de calcul (ce qui est le but), l'événement n'est détecté qu'à la fin du calcul, ce que l'on ne veut évidemment pas.
J'ai donc pensé à placer cette capture de clavier de bas niveau dans un autre thread séparé. Mais je ne sais pas comment faire.

Je cherche donc une chose très simple (cliquer sur ESC pour arrêter un calcul), mais il me semble difficile d'y arriver !
Y a-t-il quelqu'un pour m'aider, s'il vous plaît ?
Merci beaucoup.

Emmanuel

5 réponses

Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
8 juin 2020 à 17:24
Bonjour

L’utilisation de threads est la solution.
Pour
Le problème est que les autres tâches qui devaient suivre la fin des calculs doivent également être placées dans le thread, sinon il n'est pas possible de continuer les opérations dans le thread principal après la fin des calculs.

En te servant du pool de thread, tu peux attendre la fin d’un thread avant d’en lancer un autre.

https://fdorin.developpez.com/tutoriels/csharp/threadpool/part1/
0
eguignier Messages postés 8 Date d'inscription lundi 6 novembre 2000 Statut Membre Dernière intervention 11 juin 2020
9 juin 2020 à 18:01
Bonjour et un grand merci pour votre retour !!

Tout d'abord, j'imaginais qu'il y avait une solution simple à mon problème ... quelque chose qui m'échappait ... mais bon.

Besoin : Fonctionnellement, l'utilisateur clique sur un bouton pour lancer un calcul long (quelques minutes). En cliquant sur ESC, il peut interrompre le calcul et continuer à utiliser l'application, voire relancer les calculs.

Problématique : Comme je ne peux faire ces deux choses (calculs et détection des touches) dans le même thread (celui du formulaire de base), je déporte les calculs dans un thread séparé lancé par l'appui sur le fameux bouton. Mais pour détecter la touche ESC, il faut qu'après avoir lancé le Thread de calculs, la fonction de lancement se termine et rende la main à l'application (thread principal). Or en faisant cela, c'est toute l'application qui est rendue à l'utilisateur qui peut alors cliquer ailleurs, alors que le thread de calcul est toujours en cours ... ce que je ne souhaite pas. Je préférerai que le clic sur le bouton soit "bloquant" pour l'utilisateur, qui doit attendre la fin des calculs (quelques minutes) avant toute autre opération sur l'application, sauf à cliquer sur ESC qui viendrait arrêter les calculs.

Voilà pourquoi je me tourne vers vous :)

Je crains que le pool de thread empêche la détection d'une frappe clavier ?!
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
9 juin 2020 à 18:30
Bonsoir

la détection de frappe c'est forcément dans le thread principal.

Mais si ton calcul est long, et qu'il s'exécute dans ce même thread, le calcul est bloquant, l'appli fige et la détection ne marche pas.

L'idée c'est donc bien, comme tu l'as écrit dans ton premier message à la détection de la frappe de basculer un flag qui va stopper ton calcul.

Maintenant, je comprends que tant que le calcul tourne, tu ne veux pas que l'utilisateur ait la main sur l'appli. Ce qui est parfaitement compréhensible.
Il te suffit d'un panel, un groupebox ou d'un autre conteneur, qui est invisible au départ et qui devient visible au lancement du calcul.
Tu peux même mettre une barre de progression sur ce panel, pour que ce soit user friendly.
A la fin tu le rend à nouveau invisible.
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
9 juin 2020 à 19:51
Sur le formulaire, il y a un bouton, et un panel contenant une progressbar.
Le panel fait la taille du formulaire.

Le code
namespace Test_Winform
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void butGo_Click(object sender, EventArgs e)
        {
            panel1.Visible = true;
            stop = false;
            Task.Run(LanceThread);
        }

        /// <summary>
        /// Ce premier thread rend la main à l'application.
        /// Il exécute le calcul long et attend son résultat, au pire un bool comme dans l'exemple
        /// </summary>
        private void LanceThread()
        {
            leThread = Task.Run(CalculLong);

            if (!leThread.Result)//attend le résultat
                MessageBox.Show("Calcul arrété avant la fin");

            this.BeginInvoke((Action)(() => panel1.Visible = false));
        }

        bool stop = false;
        Task<bool> leThread;

        private bool CalculLong()
        {
            for (int i = 0; i < 101; i++)
            {
                this.BeginInvoke((Action)(() => progressBar1.Value = i));
                Thread.Sleep(500);
                if (stop)
                    return false;
            }

            return true;
        }

        private void Form2_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.KeyCode == Keys.Escape && !leThread.IsCompleted)
            {
                stop = true;
            }
        }
    }
}





0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
eguignier Messages postés 8 Date d'inscription lundi 6 novembre 2000 Statut Membre Dernière intervention 11 juin 2020
11 juin 2020 à 11:22
Bonjour Whismeril,

Un grand merci pour ton retour éclairé.
C'est une bonne idée que celle d'afficher un panel qui viendrait masquer le reste de l'IHM.
J'avais déjà mis dans le bandeau supérieur une zone avec double ProgressBar.

Je vais de ce pas étudier le code que tu as écris (merci :)).
Je ne connaissais pas Task.Run. De mon coté, j'utilise la création d'un thread puis Thread.Start();

Bonne journée.
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
11 juin 2020 à 11:36
Bonjour

Task.Run
Je pense que si tu avais connu, tu n’aurais pas posé cette question.
C’est d’ailleurs pour ça que je t’ai mis en lien le premier tuto d’une série de 4 que tu devrais lire avant d’étudier mon code.
0
Rejoignez-nous