Schleife per Tastendruck unterbrechen

  • VB.NET

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Peter329.

    Schleife per Tastendruck unterbrechen

    Hi,

    ich habe einige Routinen, die sehr langlaufende Schleifen erzeugen.

    Manchmal bereue ich den Start der Routine und würde sie gern mit einem Tastendruck, etwa ESCAPE oder LCTRL unterbrechen.

    Zum Testen verwende ich folgende Routine:

    VB.NET-Quellcode

    1. Private Sub PerformLoop()
    2. Debug.Print("")
    3. DebugPrint("LOOP started")
    4. blnIntercepted = False
    5. For i = 1 To 10
    6. DebugPrint("LOOP i=" & i.ToString)
    7. If blnIntercepted Then
    8. DebugPrint("LOOP intercepted")
    9. Exit For
    10. End If
    11. Thread.Sleep(200)
    12. Next
    13. DebugPrint("LOOP Ended")
    14. End Sub
    15. Private Sub DebugPrint(_message As String)
    16. Debug.Print(Date.Now.ToString("HH:mm:ss") & ": " &
    17. " Intercepted=" & blnIntercepted.ToString.PadRight(6, " "c) &
    18. _message)
    19. End Sub


    Die Routine läuft ziemlich genau 2 Sekunden. Ich habe versucht, sie über das KeyDown Event (natürlich mit KeyPreview=True) versucht zu unterbrechen:

    VB.NET-Quellcode

    1. Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    2. DebugPrint("KEY DOWN")
    3. If e.KeyCode = Keys.Escape Then
    4. blnIntercepted = True
    5. DebugPrint("KEY DOWN Escape depressed")
    6. End If
    7. End Sub


    Leider funktioniert das nicht ... hier sind die Debug Messages:

    Quellcode

    1. 11:28:37: Intercepted=False LOOP started
    2. 11:28:37: Intercepted=False LOOP i=1
    3. 11:28:37: Intercepted=False LOOP LCTRL1=False
    4. 11:28:37: Intercepted=False LOOP i=2
    5. 11:28:37: Intercepted=False LOOP LCTRL1=False
    6. 11:28:37: Intercepted=False LOOP i=3
    7. 11:28:37: Intercepted=False LOOP LCTRL1=False
    8. 11:28:38: Intercepted=False LOOP i=4
    9. 11:28:38: Intercepted=False LOOP LCTRL1=False
    10. 11:28:38: Intercepted=False LOOP i=5
    11. 11:28:38: Intercepted=False LOOP LCTRL1=False
    12. 11:28:38: Intercepted=False LOOP i=6
    13. 11:28:38: Intercepted=False LOOP LCTRL1=False
    14. 11:28:38: Intercepted=False LOOP i=7
    15. 11:28:38: Intercepted=False LOOP LCTRL1=False
    16. 11:28:38: Intercepted=False LOOP i=8
    17. 11:28:38: Intercepted=False LOOP LCTRL1=False
    18. 11:28:39: Intercepted=False LOOP i=9
    19. 11:28:39: Intercepted=False LOOP LCTRL1=False
    20. 11:28:39: Intercepted=False LOOP i=10
    21. 11:28:39: Intercepted=False LOOP LCTRL1=False
    22. 11:28:39: Intercepted=False LOOP Ended
    23. 11:28:39: Intercepted=False KEY DOWN
    24. 11:28:39: Intercepted=True KEY DOWN Escape depressed


    Offensichtlich wird das KeyDown Event "pending" gehalten ... und feuert erst NACHDEM die Schleife beendet wurde.

    Ich habe versucht das alternativ über "GetState" abzuwickeln:

    VB.NET-Quellcode

    1. Dim LCTRL1 As Boolean = False
    2. Dim myKeyState1 As Short = GetKeyState(Keys.LControlKey)
    3. If myKeyState1 = -127 OrElse myKeyState1 = -128 Then LCTRL1 = True
    4. If LCTRL1 Then blnIntercepted = True


    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Module Module1
    3. 'Get state is used to check CTRL
    4. <DllImport("user32.dll")>
    5. Public Function GetKeyState(ByVal vKey As Keys) As Int16
    6. End Function
    7. End Module


    Und auch über einen Timer hab ich das versucht:

    VB.NET-Quellcode

    1. Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    2. DebugPrint("TIMER1 TICK")
    3. Dim myKeyState2 As Short = GetKeyState(Keys.LControlKey)
    4. Dim LCTRL2 As Boolean = False
    5. If myKeyState2 = -127 OrElse myKeyState2 = -128 Then LCTRL2 = True
    6. If LCTRL2 Then
    7. blnIntercepted = True
    8. DebugPrint("TIMER1 TICK LCTRL2=" & LCTRL2.ToString)
    9. End If
    10. End Sub


    Bei allen Versuchen ist das Ergebnis das Gleiche: Der Tastendruck wird erst NACH ENDE der Schleife erkannt und ist damit zu spät wirksam ! Und das ist natürlich blöde, weil die Schleife eben nicht abgebrochen wird !

    Ich nehme an, dass den Spezialisten im Forum dieses Phänomen bekannt ist. Und vielleicht gibt es dafür dann ja auch eine ganz hausbackene Lösung. Nur ich komme halt trotz alle Mühe nicht drauf.

    Kann mir jemand (gewohnt freundlich und nachsichtig) auf die Sprünge helfen ? :)

    LG
    Peter

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Peter329“ ()

    Ist es eine Option, die Loop mit Async/Await nebenläufig zu machen? Dann wird auch der Tastendruck "in Echtzeit" erkannt und verarbeitet.
    Stichwort Async/Await
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Jau ... sowas hab ich mir schon gedacht ...

    Aber mein Code ist eben nur ein TEST Code ... in meiner realen Anwendung ist das sehr viel tiefer verwoben ... und da stelle ich mir das Umstellen auf eine asynchrone Task nicht ganz so einfach vor. Das Dingens darf eben nicht so einfach vor sich hin laufen, während meine Basis Anwendung munter irgendwelche anderen Dinge treiben könnte!
    @Peter329 Ich mach das mit GetAsyncKeyState(), da kannst Du z.B. einer Button_Click Tastendrücke mitgeben.
    pinvoke.net/default.aspx/user32.getasynckeystate
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Was hindert Dich daran, vor Start der Schleife wesentliche/kritische GUI-Teile zu deaktivieren:

    VB.NET-Quellcode

    1. Private Async Sub PerformLoopAndHandleGuiAccess()
    2. DeactivateCriticalGuiParts()
    3. Threading.Tasks.Task.Run(Sub() PerformLoop)
    4. ReactivateCriticalGuiParts()
    5. End Sub
    6. Private Sub DeactivateCriticalGuiParts()
    7. TextBox1.Enabled = False
    8. '…
    9. End Sub
    10. Private Sub ReactivateCriticalGuiParts()
    11. TextBox1.Enabled = True
    12. '…
    13. End Sub

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

    Peter329 schrieb:

    Manchmal bereue ich den Start der Routine und würde sie gern mit einem Tastendruck, etwa ESCAPE oder LCTRL unterbrechen.

    Zum Testen...
    Jo, da hast du doch zwei schöne Ansätze.
    Das mit GetKeyAsync ist deutlich weniger aufwändig zu coden - aber ob man so einen "Geheim-TastenDruck" einem User anbieten möchte...

    Nebenläufigkeit + Cancellation ( + Fehlerhandling) ist dank Async zwar erheblich einfacher geworden - aber viel Arbeit ist es immer noch.
    Weil man muss sich überlegen, was man alles deaktiviert, und was und wie man dem User als "Rest-Interaktivität" anbietet, und das dann implementieren.

    RodFromGermany schrieb:

    Ich mach das mit GetAsyncKeyState(), da kannst Du z.B. einer Button_Click Tastendrücke mitgeben.


    Das klingt doch nach genau dem, was ich benötige.

    VB.NET-Quellcode

    1. 'Get async state is used to check CTRL in a loop
    2. <DllImport("user32.dll")>
    3. Public Function GetAsyncKeyState(ByVal vKey As Keys) As Int16
    4. End Function


    Allein ... ich kriege es nicht zum Laufen.

    Hier mein Test Code:

    VB.NET-Quellcode

    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2. Debug.Print("Hit LCTRL ...")
    3. Thread.Sleep(2000)
    4. Debug.Print("... continue")
    5. Dim LCTRL1 As Boolean = False
    6. Dim myKeyState1 As Short = GetKeyState(Keys.LControlKey)
    7. If myKeyState1 = -127 OrElse myKeyState1 = -128 Then LCTRL1 = True
    8. DebugPrint("LCTRL1=" & LCTRL1.ToString)
    9. Dim myKeyState2 As Short = GetAsyncKeyState(Keys.LControlKey)
    10. Dim LCTRL2 As Boolean = False
    11. If myKeyState2 = -127 OrElse myKeyState2 = -128 Then LCTRL2 = True
    12. DebugPrint("LCTRL2=" & LCTRL2.ToString)
    13. End Sub


    Wenn ich "LCTRL" drücke, während die Button2.Click Routine läuft, dann erhalte ich folgende Ausgabe:

    Quellcode

    1. Hit LCTRL ...
    2. ... continue
    3. 13:21:14: LCTRL1=False
    4. 13:21:14: LCTRL2=False


    Mit anderen Worten: weder GetKeyState noch GetAsyncKeyState erkennt die Taste.

    Wenn ich LCTRL VOR dem Aufruf des Button2 gedrückt halte, dann erhalte ich folgende Ausgabe:

    Quellcode

    1. Hit LCTRL ...
    2. ... continue
    3. 13:24:30: LCTRL1=True
    4. 13:24:30: LCTRL2=False


    Mit anderen Worten, GetKeyState erkennt die LCTRL Taste vor dem Button2.Click (was nicht weiterhilft) ... aber GetAsyncKeyState erkennt die Taste in keinem Fall ... d.h. ich kann gar keine Wirkung feststellen.

    Jetzt habe ich sicher irgend etwas Entscheidendes in deinem Ratschlag überlesen oder falsch verstanden (oder ich bin einfach zu blöd :) ). Kannst du mir sagen wie das aussehen muss ?

    LG
    Peter

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Peter329“ ()

    @Peter329 probier mal so dies, das reagiert auf gleichzeitig Shift und Ctrl:

    VB.NET-Quellcode

    1. Dim b1 = GetAsyncKeyState(Keys.LShiftKey) = &H8001
    2. Dim b2 = GetAsyncKeyState(Keys.LControlKey) = &H8001
    3. If b1 AndAlso b2 Then
    4. MessageBox.Show("gedrückt")
    5. End If
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Irgendwie funzt das bei mir nicht ...

    VB.NET-Quellcode

    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2. Debug.Print("Hit LCTRL ...")
    3. Thread.Sleep(2000)
    4. Debug.Print("... continue")
    5. Dim b1 = GetAsyncKeyState(Keys.LShiftKey) = &H8001
    6. Dim b2 = GetAsyncKeyState(Keys.LControlKey) = &H8001
    7. If b1 AndAlso b2 Then
    8. MessageBox.Show("gedrückt")
    9. End If
    10. Debug.Print("After Hit LCTRL ...")
    11. Thread.Sleep(2000)
    12. Debug.Print("ended")
    13. End Sub


    Und egal was ich drücke ... oder gedrückt halte ... das ist die Ausgabe:

    Quellcode

    1. Hit LCTRL ...
    2. ... continue
    3. After Hit LCTRL ...
    4. ended


    Was zum Teufel mache ich denn falsch ?

    LG
    Peter
    @Peter329 Deine Deklaration stimmt nicht:

    VB.NET-Quellcode

    1. <DllImport("user32.dll")>
    2. Shared Function GetAsyncKeyState(ByVal vKey As Keys) As Integer
    3. End Function
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RFG

    Jau ... mit Integer funktioniert das jetzt ... und zwar hervorragend ! Danke !

    Die Funktion ist genau das was ich gesucht hatte ! Jetzt kann ich endlich langlaufende Prozeduren einfangen, ohne gleich das Dingens mit dem Task Manager platt zu machen. Ich bin restlos begeistert !

    Also herzlichen Dank an alle Ratgeber, Daumen hoch, Problem gelöst ! :)

    LG
    Peter

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Peter329“ ()