Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Software-Projekte der Mitglieder (https://www.delphipraxis.net/26-software-projekte-der-mitglieder/)
-   -   SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht (https://www.delphipraxis.net/154931-sendinputhelper-ein-wrapper-der-den-umgang-mit-sendinput-vereinfacht.html)

WladiD 1. Okt 2010 08:11

SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hallo DP-Mitglieder,

vor einigen Tagen bestand bei mir Bedarf, Tastatureingaben zu simulieren, wofür die Windows-Funktion MSDN-Library durchsuchenSendInput ja auch implementiert wurde. Doch das Array aufzubauen, welches die Funktion erwartet, ist ein Graus. So muss z.B. für jede Taste einmal der gedrückt und dann wieder der losgelassen Status einzeln angegeben werden. Es ist schon klar, dass es so sein soll, damit man die Kontrolle hat, aber es ist sehr mühsam und schreit geradezu nach einer Klasse die das ganze etwas vereinfacht.

Ich lege am einfachsten mit einem Beispiel los. Notepad starten:

Delphi-Quellcode:
uses SendInputHelper;

with TSendInputHelper.Create do
begin
  AddShortCut([ssWin], 'r'); // [Win] + [R]
  AddDelay(100);            // Verzögerung in ms
  AddText('cmd', TRUE);     // TRUE = AppendReturn
 
  Flush; // Erst hier werden die zuvor hinzugefügten
         // Eingaben gebündelt abgesetzt.
  Free;
end;
Jeder der mit SendInput schon mal gearbeitet hat, wird wissen, was das für eine Vereinfachung mit sich bringt. Desweiteren wird der Caps-Lock-Zustand in der Methode Flush neutralisiert, soll heißen, dass im obigen Beispiel auch wirklich 'cmd' und nicht 'CMD' gesendet wird, wenn der Benutzer die Feststelltaste (wohl aus versehen) aktiviert hat.

Ich weiß, es ist eigentlich kein "richtiges" Projekt, doch ich sehe es einfach mal als eines an und habe es auch bei sourceforge veröffentlicht:

SendInputHelper auf GitHub

Dort ist auch ein umfangreicheres Beispiel (auch compiliert) dabei.

mfg

mleyen 1. Okt 2010 08:14

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Vielen Dank, Wladimir....

WladiD 1. Okt 2010 09:57

Ein weiteres Beispiel
 
Liste der Anhänge anzeigen (Anzahl: 1)
Habe soeben ein minimalistisches Beispiel erstellt, mit dem das Windows Flip 3D aktiviert und für 2 Sekunden durch die geöffneten Fenster geflippt wird:

Delphi-Quellcode:
program WindowsFlip;

{$APPTYPE CONSOLE}

uses
    SysUtils, Windows, SendInputHelper;

var
    cc:Integer;
begin
    with TSendInputHelper.Create do
    begin
        AddShift([ssWin], TRUE, FALSE);
        for cc:=1 to 20 do
        begin
            AddVirtualKey(VK_TAB, TRUE, FALSE);
            AddDelay(100);
        end;
        AddVirtualKey(VK_TAB, FALSE, TRUE);
        AddVirtualKey(VK_ESCAPE);
        AddShift([ssWin], FALSE, TRUE);
        Flush;
        Free;
    end;
end.
mfg

MarcoWarm 23. Nov 2010 14:12

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Gute Arbeit 8-)

Als Dankeschön noch eine kleine Erweiterung.

zum Hintergrund:
Die aktuelle Implementation kann nur Zeichen schicken, die es auch im aktuell eingestellten Keyboard Layout gibt. D.h. auf einem deutschen Keyboard kannst du kein الله أكبر ausgeben.
Das ist uns aufgefallen, als ein TerminalDiensteClient mit englischem Tastaturlayout ein Ö schicken wollte. Mit der Erweiterung ist das kein Problem mehr.
Ein nicht unicodefähiges Control erhält dann die Ansi Entsprechung des Zeichens.

wie man die Klasse erweitern kann:
Delphi-Quellcode:
class function TSendInputHelper.GetUnicode(SendChar: WideChar; Press,
  Release: Boolean): TInputArray;
const
  KEYEVENTF_UNICODE = 4;
var
   ScanCode:Word absolute SendChar;
   KeyDown, KeyUp:TInput;
begin
  KeyDown.Itype:=INPUT_KEYBOARD;
  KeyDown.ki.wVk:=0;
  KeyDown.ki.wScan:=ScanCode;
  KeyDown.ki.dwFlags:=KEYEVENTF_UNICODE;
  KeyDown.ki.time:=0;
  KeyDown.ki.dwExtraInfo:=GetMessageExtraInfo;

  KeyUp:=KeyDown;
  KeyUp.ki.dwFlags:=KeyUp.ki.dwFlags or KEYEVENTF_KEYUP;

  SetLength(Result, 0);
  if Press and Release then
  begin
    SetLength(Result, 2);
    Result[0]:=KeyDown;
    Result[1]:=KeyUp;
  end
  else
  if Press then
  begin
    SetLength(Result, 1);
    Result[0]:=KeyDown;
  end
  else
  if Release then
  begin
    SetLength(Result, 1);
    Result[0]:=KeyUp;
  end;
end;
       
procedure TSendInputHelper.AddUnicodeChar(SendChar: WideChar; Press,
  Release: Boolean);
var
   Inputs:TInputArray;
begin
   Inputs:=GetUnicode(SendChar, Press, Release);
   if Assigned(Inputs) then
      AddRange(Inputs);
end;
P.S. ich hab hier WideChar genommen, damit es auch vor D2009 funzt.

WladiD 14. Dez 2010 14:48

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hallo MarcoWarm,

sorry, dass ich erst jetzt antworte. Das Forum hat mir keine Benachrichtigung geschickt, dass ich ein Antwort erhalten hab. Hab es nur per Zufall entdeckt.

Zitat:

Zitat von MarcoWarm (Beitrag 1063517)
Gute Arbeit 8-)

Als Dankeschön noch eine kleine Erweiterung...

Wow, Super, so wie es sich anhört kam meine Unit zum produktiven Einsatz. Freut mich!

Die Erweiterung werde ich gerne demnächst übernehmen. Vielen Dank dafür.

WladiD 14. Dez 2010 22:15

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von MarcoWarm (Beitrag 1063517)
Als Dankeschön noch eine kleine Erweiterung...

Hallo nochmals,

deinen Code habe ich als Basis genommen und den Support für Unicode eingebunden.

Es läuft jetzt transparent ab, die Klassenmethode TSendInputHelper.GetChar überprüft nun ob das Zeichen ein Unicode-Char ist und leitet den Aufruf ggf. auf TSendInputHelper.GetUnicodeChar um. Dadurch werden alle Methoden, die auf GetChar basieren, automatisch Unicode kompatibel und eine separate AddUnicode Methode wird überflüssig. Ich weiß, dadurch bin ich nicht Abwärtskompatibel zu < Delphi 2009, aber das interessiert mich ehrlich gesagt nicht ;) (think only forward)

Die Änderungen sind auf der Projektseite eingespielt.

MarcoWarm 31. Jan 2011 06:34

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hi Wladi,

danke für's einbauen (wir haben es erstmal ungetestet in unsere produktive Umgebung übernommen ;-)
ein kleiner Hinweis noch. Für alle D2009 Nutzer wäre es schön, wenn du
Delphi-Quellcode:
KEYEVENTF_UNICODE = 4;
als konstante definieren würdest ... das bringt unsere alte Windows.pas nicht mit :-(

Gruß
Marco

WladiD 31. Jan 2011 08:16

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hi Marco,

die Konstante ist drin. Die Änderung wurde ins SVN-Repository gecommitted. Leider kann ich im Moment keine Dateien releasen, da sourceforge vor ein paar Tagen gehackt wurde und der File-Release-Dienst deaktiviert wurde. Hole ich aber in ein paar Tagen nach.

mfg

MarcoWarm 1. Feb 2011 12:50

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
vielen Dank :-D

PeterPanino 25. Okt 2012 11:44

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hallo Waldemar,

vielen Dank für TSendInputHelper!

Ich habe eine Frage:

Man kann zwar alle möglichen Schriftzeichen senden, die nicht auf der Tastatur vorhanden sind, z.B.:
Delphi-Quellcode:
IH.AddText('Das ist ein Test üöäÜÖÄß!"§$%&/()=?\|~', True);
IH.AddText('&#1055;&#1088;&#1080;&#1074;&#1077;&#1090;! Cze&#347;&#263;! &#20320;&#22909;! !&#1513;&#1500;&#1493;&#1501;', TRUE);
Wenn ich jedoch ein "é" senden möchte, so funktioniert es nicht:

Delphi-Quellcode:
IH.AddText('é', True); // Kleiner lateinischer Buchstabe E mit Akut


Wie kann ich dieses Zeichen senden?

mkinzler 25. Okt 2012 12:11

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Wird ein Fehler gemeldet? Wenn ja welcher?

PeterPanino 25. Okt 2012 13:17

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von mkinzler (Beitrag 1188327)
Wird ein Fehler gemeldet? Wenn ja welcher?

Nein, es wird kein Fehler gemeldet, das Zeichen wird einfach nicht gesendet. Übrigens auch nicht die übrigen Zeichen mit "Akzent": éèáà.

Ich habe eine Tastatur mit deutschem Layout. Windows 7 x64 SP1 Deutsch

PeterPanino 26. Okt 2012 13:46

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von PeterPanino (Beitrag 1188351)
Zitat:

Zitat von mkinzler (Beitrag 1188327)
Wird ein Fehler gemeldet? Wenn ja welcher?

Nein, es wird kein Fehler gemeldet, das Zeichen wird einfach nicht gesendet. Übrigens auch nicht die übrigen Zeichen mit "Akzent": éèáà.

Ich habe eine Tastatur mit deutschem Layout. Windows 7 x64 SP1 Deutsch

Ich kann TSendInputHelper zur Zeit leider nicht debuggen, weil die Delphi IDE im Debug-Modus immer abstürzt (unabhängig davon welches Projekt).

PeterPanino 26. Okt 2012 21:06

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
SendInputHelper hat ein Problem: Es verwendet TShiftState als Klassenmitglied. Das führt zu Problemen mit anderen Klassen, welche System.Classes.TShiftState verwenden.

Wie kann man dieses Problem lösen?

PeterPanino 26. Okt 2012 21:15

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Eine Möglichkeit wäre, dass man SendInputHelper in der uses-Klausel VOR System.Classes einfügt. Aber dann funktionieren alle SendInputHelper Methoden nicht mehr, welche einen TShiftState Parameter verwenden, z.B.:

Delphi-Quellcode:
procedure TSendInputHelper.AddShortCut(ShiftState:TShiftState; ShortChar:Char);


Ein verzwicktes Problem! Wie könnte man es lösen?

PeterPanino 26. Okt 2012 23:42

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Ein möglicher Workaround: Eine eigene Bridge- oder Worker-Unit erzeugen, in der die SendInputHelper-Aufrufe gekapselt werden, z.B.:
Delphi-Quellcode:
unit SIHWorker;

interface

uses
  SendInputHelper;

procedure SendShortCut(DummyShiftState: Integer; ShortChar: Char);

implementation

var
  SIH: TSendInputHelper;

procedure SendShortCut(DummyShiftState: Integer; ShortChar: Char);
begin
  case DummyShiftState of
    1: SIH.AddShortCut([ssShift], ShortChar);
    2: SIH.AddShortCut([ssCtrl], ShortChar);
    3: SIH.AddShortCut([ssAlt],  ShortChar);
  end;
  SIH.Flush;
end;

{ Todo: weitere SendInputHelper-Aufrufe }

initialization
  SIH := TSendInputHelper.Create;

finalization
  SIH.Free;

end.
Zusätzlich sollte noch in SendInputHelper.pas TShiftState durch TSIHShiftState ersetzt werden.

Jetzt kann man die Methoden von SendInputHelper indirekt über SIHWorker aufrufen und braucht so SendInputHelper nicht mehr in anderen Units zu deklarieren (was zu den besagten Fehlern führen würde).

PeterPanino 28. Okt 2012 00:57

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Nachdem ich eine Lösung für das TShiftState-Problem aufgezeigt habe, habe ich jetzt endlich den Fehler gefunden, bei dem keine Akzentzeichen (z.B. éèáà usw.) ausgegeben wurden. Ich habe jetzt die Delphi XE3 IDE zum Debuggen verwendet, und die stürzt im Debug-Modus nicht ab, obwohl AQtime installiert ist! ;-)
Delphi-Quellcode:
if not((Ord(SendChar) > 0) and (Ord(SendChar) < 127)) then // war: 255
begin
  Result := GetUnicodeChar(SendChar, Press, Release);
  Exit;
end;
Der Fehler liegt darin, dass Waldemar die Unicode-Zeichenbehandlung erst ab Ord(SendChar) = 255 angesetzt hat. Akzentzeichen wie é sind jedoch auf einer deutschen Tastatur mit den Umschalttasten Shift/Ctrl/Alt(/AltGr) nicht erreichbar (und nur diese sind im Rückgabewert von VkKeyScan kodiert), deshalb hat das nachfolgende VkKeyScan(SendChar) immer einen falschen ScanCode von 65535 (!) ergeben, wodurch natürlich kein Zeichen gesendet wurde. Die deutschen Umlaute sind zwar auch 8-bittig kodiert (haben also einen ASCII-Wert im Bereich 128-255), sind jedoch mit den Umschalttasten auf einer deutschen Tastatur erreichbar und liefern deshalb bei VkKeyScan einen korrekten Wert zurück. Bei Verwendung einer US-Tastatur würde VkKeyScan aber auch bei den deutschen Umlauten einen falschen Wert zurückgeben. Es ist also richtig, bei VkKeyScan vom kleinsten gemeinsamen Nenner - nämlich 7-bit ASCII - auszugehen!

PeterPanino 28. Okt 2012 20:31

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Verbesserung: SendInputHelper-Methoden, die kein TShiftState als Parameter übergeben, können von außen direkt über die Objekt-Variable SIH aufgerufen werden. Deshalb muss die Objekt-Variable SIH im Interface-Teil deklariert werden!
Delphi-Quellcode:
unit SIHWorker;

interface

uses
  SendInputHelper;

procedure SendShortCutChar(DummyShiftState: Integer; ShortChar: Char);

var
  SIH: TSendInputHelper;

implementation

procedure SendShortCutChar(DummyShiftState: Integer; ShortChar: Char);
begin
  case DummyShiftState of
    1:
      SIH.AddShortCut([ssShift], ShortChar);
    2:
      SIH.AddShortCut([ssCtrl], ShortChar);
    3:
      SIH.AddShortCut([ssAlt], ShortChar);
  end;
  SIH.Flush;
end;

(*
SendInputHelper-Methoden, die kein TShiftState als Parameter übergeben,
können von außen direkt über die Objekt-Variable SIH aufgerufen werden.
Deshalb muss die Objekt-Variable SIH im Interface-Teil deklariert werden!
*)

initialization

SIH := TSendInputHelper.Create;

finalization

SIH.Free;

end.

ATS3788 31. Mär 2016 10:56

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Nach so viele Jahre :thumb:

Danke

WladiD 25. Jul 2019 11:31

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von PeterPanino (Beitrag 1188701)
Nachdem ich eine Lösung für das TShiftState-Problem aufgezeigt habe, habe ich jetzt endlich den Fehler gefunden, bei dem keine Akzentzeichen (z.B. éèáà usw.) ausgegeben wurden. Ich habe jetzt die Delphi XE3 IDE zum Debuggen verwendet, und die stürzt im Debug-Modus nicht ab, obwohl AQtime installiert ist! ;-)
Delphi-Quellcode:
if not((Ord(SendChar) > 0) and (Ord(SendChar) < 127)) then // war: 255
begin
  Result := GetUnicodeChar(SendChar, Press, Release);
  Exit;
end;
Der Fehler liegt darin, dass Waldemar die Unicode-Zeichenbehandlung erst ab Ord(SendChar) = 255 angesetzt hat. Akzentzeichen wie é sind jedoch auf einer deutschen Tastatur mit den Umschalttasten Shift/Ctrl/Alt(/AltGr) nicht erreichbar (und nur diese sind im Rückgabewert von VkKeyScan kodiert), deshalb hat das nachfolgende VkKeyScan(SendChar) immer einen falschen ScanCode von 65535 (!) ergeben, wodurch natürlich kein Zeichen gesendet wurde. Die deutschen Umlaute sind zwar auch 8-bittig kodiert (haben also einen ASCII-Wert im Bereich 128-255), sind jedoch mit den Umschalttasten auf einer deutschen Tastatur erreichbar und liefern deshalb bei VkKeyScan einen korrekten Wert zurück. Bei Verwendung einer US-Tastatur würde VkKeyScan aber auch bei den deutschen Umlauten einen falschen Wert zurückgeben. Es ist also richtig, bei VkKeyScan vom kleinsten gemeinsamen Nenner - nämlich 7-bit ASCII - auszugehen!

Hi PeterPanino,

sicherlich ist es nach all den Jahren nicht mehr relevant, aber ich wollte hier mal erwähnen, das dieser Fehler jetzt beseitigt ist. Auch habe ich das Set TShiftState nach TSIHShiftState umbenannt, damit es keine Namenskonflikte mit anderen Bibliotheken geben kann.

Das Repository befindet sich schon seit einigen Jahren auf GitHub:
https://github.com/WladiD/SendInputHelper

KodeZwerg 11. Sep 2020 08:22

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von WladiD (Beitrag 1437612)
sicherlich ist es nach all den Jahren nicht mehr relevant, aber ich wollte hier mal erwähnen, das dieser Fehler jetzt beseitigt ist. Auch habe ich das Set TShiftState nach TSIHShiftState umbenannt, damit es keine Namenskonflikte mit anderen Bibliotheken geben kann.

Sorry für's hochpushen aber ich möchte mich bedanken für diesen Beitrag der mir echt gut gefallen hat! Das mit den Arrays war wirklich eine Qual, dank Dir nun nicht mehr! :thumb::thumb::thumb:

PeterPanino 3. Dez 2023 09:39

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
DANKE nochmals für SendInputHelper - einfach nützlich und genial!

WladiD 4. Dez 2023 09:51

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von PeterPanino (Beitrag 1530347)
DANKE nochmals für SendInputHelper - einfach nützlich und genial!

Danke! Das geht runter wie Öl. :-D

PeterPanino 4. Dez 2023 10:34

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von WladiD (Beitrag 1530379)
Zitat:

Zitat von PeterPanino (Beitrag 1530347)
DANKE nochmals für SendInputHelper - einfach nützlich und genial!

Danke! Das geht runter wie Öl. :-D

Ich habe SendInputHelper neulich in einem wichtigen Projekt verwendet - es funktioniert zum Glück auch mit Unicode!! Was will man mehr?

Es sind solche Programmierer wie Waldemar Derr, die mit ihrer ausgezeichneten Arbeit Delphi voranbringen!

himitsu 4. Dez 2023 11:03

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Wenn beim Add oder Flush eine Exception auftreten könnte, dann würde noch der Ressourcenschutzblock fehlen.
Delphi-Quellcode:
with TSendInputHelper.Create do
  try
    AddShortCut([ssWin], 'r'); // [Win] + [R]
    AddDelay(100); // Verzögerung in ms
    AddText('cmd', TRUE); // TRUE = AppendReturn
    Flush; // Erst hier werden die zuvor hinzugefügten Eingaben gebündelt abgesetzt.
  finally
    Free;
  end;

Wenn du das auf einen Record umschreibst und z.B. intern nur dynamische Arrays (anstatt objekte/Pointer/Listen) verwendets,
OK, ein Interface statt Class ginge auch, aber warum komplizierter. :angle2:

dann kann man auf Create und Free verzichten, also wie eine einfach Variable benutzten und dann vergessen. (wie Integer, TPoint oder String+StringHelper)

PS, dann ginge auch sowas wie Ducktyping. (überall jeweils Self als Result zurückgegeben)
Delphi-Quellcode:
TSendInputHelper.Create.AddShortCut([ssWin], 'r').AddDelay(100).AddText('cmd', TRUE).Flush;



Ja, Dank Custom-Managed-Records kann man nun auch in Records eigentlich alles benutzen,
so lange ein Record nur sein eigenens Zeugs nutzt und man beim Kopieren von Variablen alle Inhalte, anstatt nur die Referenzen kopiert,
oder man für Objekte/Pointer sich eine Referenzzählung baut (geht, aber macht kein Spaß, da es keine Record-Vererbung gibt und Generics hier kaum nutzbar sind).



Warum eigentlich .Flush und nicht .Send ?

PeterPanino 4. Dez 2023 11:18

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Zitat:

Zitat von himitsu (Beitrag 1530382)
Wenn beim Add oder Flush eine Exception auftreten könnte, dann würde noch der Ressourcenschutzblock fehlen.
Delphi-Quellcode:
with TSendInputHelper.Create do
  try
    AddShortCut([ssWin], 'r'); // [Win] + [R]
    AddDelay(100); // Verzögerung in ms
    AddText('cmd', TRUE); // TRUE = AppendReturn
    Flush; // Erst hier werden die zuvor hinzugefügten Eingaben gebündelt abgesetzt.
  finally
    Free;
  end;

Wenn du das auf einen Record umschreibst und z.B. intern nur dynamische Arrays (anstatt objekte/Pointer/Listen) verwendets,
OK, ein Interface statt Class ginge auch, aber warum komplizierter. :angle2:

dann kann man auf Create und Free verzichten, also wie eine einfach Variable benutzten und dann vergessen. (wie Integer, TPoint oder String+StringHelper)

PS, dann ginge auch sowas wie Ducktyping. (überall jeweils Self als Result zurückgegeben)
Delphi-Quellcode:
TSendInputHelper.Create.AddShortCut([ssWin], 'r').AddDelay(100).AddText('cmd', TRUE).Flush;



Ja, Dank Custom-Managed-Records kann man nun auch in Records eigentlich alles benutzen,
so lange ein Record nur sein eigenens Zeugs nutzt und man beim Kopieren von Variablen alle Inhalte, anstatt nur die Referenzen kopiert,
oder man für Objekte/Pointer sich eine Referenzzählung baut (geht, aber macht kein Spaß, da es keine Record-Vererbung gibt und Generics hier kaum nutzbar sind).

Kannst du bitte mal ein paar Code-Beispiele zeigen?

himitsu 4. Dez 2023 14:19

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
z.B. statt TList<> und TStringList verwende ich zunehmend lieber ein TArray<String>, sowie eben Records anstatt Klassen. (sind teilweise einfacher als die doppelte Deklaration eines Interfaces)
und dennoch kann man so von der automatischen Speicherverwaltung profitieren. (seit den Custom-Managed-Records sind nun auch die letzten Probleme mit "unsicheren" Typen behebbar, also Pointer und Objekte innerhalb des Reocrds, sowie die einfachen dynamischen Arrays, welchen leider seit Jahrzehnten das CopyOnWrite fehlt, entgegen z.B. einem String, der intern auch nur ein aufgemotztes Char-Array ist, aber bei Welchem das CopyOnWrite funktioniert)

Delphi-Quellcode:
type
  TMyRecord = record
  private
    FList: TArray<Integer>;
  public
    class function Create: TMyRecord; static;

    function Add(a: Integer): TMyRecord:
    function Send;

    function Clear;
  end;

class function TMyRecord.Create: TMyRecord;
begin
  Result := Self;
end;

function TMyRecord.Add(a: Integer): TMyRecord:
begin
  FList := FList + [a];
end;

function TMyRecord.Send;
begin
  // mach was mit FList
end;

function TMyRecord.Clear;
begin
  FList := []; // oder FList := nil; oder SetLength(FList, 0);
end;



TMyRecord.Create.Add(123).Add(456).Send;

var R: TMyRecord;
R.Add(123);
R.Add(456);
R.Send;
R.Clear; // damit die 123 und 456 verschwinden (oder beim Send, z.B. via Parameter, nach dem Senden automatisch leeren lassen)
R.Add(789);
R.Send;

R.Add(123).Add(456).Send;
R.Clear;
R.Add(789).Send;
Delphi-Quellcode:
type
  TSendInputHelper = record
  private
    FData: TArray<TInput>; // oder eben dein TInputArray
    class function MergeInputs(InputsBatch: array of TInputArray): TInputArray; static;
  public
    class function ConvertShiftState(ClassesShiftState: System.Classes.TShiftState): TSIHShiftState; static;
    ...
    procedure AddShift(ShiftState: TSIHShiftState; Press, Release: Boolean); overload;
    procedure AddShift(ShiftState: System.Classes.TShiftState; Press, Release: Boolean); overload;
    ...
  end;
bzw.
Delphi-Quellcode:
    function AddShift(ShiftState: TSIHShiftState; Press, Release: Boolean): TSendInputHelper; overload;
    function AddShift(ShiftState: System.Classes.TShiftState; Press, Release: Boolean): TSendInputHelper; overload;
    ...
  end;
---

Delphi-Quellcode:
protected
gibt es nicht, da es leider keine Recordvererbung gibt. :cry:
Sinnvoll ist somit eh nur
Delphi-Quellcode:
private
und
Delphi-Quellcode:
public
.

Delphi-Quellcode:
class function
können nur müssen
Delphi-Quellcode:
static
sein, wodurch es dann auch kein Self gibt. (ohne Vererbung gibt es in einer Funktion eh immer nur den einen "Klassen"-Typ)
Delphi-Quellcode:
class function ConvertShiftState(ClassesShiftState: System.Classes.TShiftState): TSIHShiftState; static;


Das in TMyRecord.Add, nennt sich "string-like Operator", sowie Insert und Delete gibt es auch seit 'ner Weile.
https://docwiki.embarcadero.com/RADS...Dynamic_Arrays

Ebenso weitere Array-Helper (unit System.Generics.Collections)

Delphi-Quellcode:
FList := [456, 123, 789];
TArray.Sort<Integer>(FList);
if TArray.BinarySearch<Integer>(FList, 456, idx) then
  ShowMessage(idx.ToString); // = '1'
BinarySearch geht aber nur für sortierte Arrays.
Für TArray<String> gäbe es auch Delphi-Referenz durchsuchenIndexStr und Delphi-Referenz durchsuchenIndexText.

Constructoren für Record sind zu praktisch, aber die müssen leider immer Parameter besitzen (vermutlich eine kranke Kompatibilität zu C++ oder so), aber
Delphi-Quellcode:
type
  TMyRecord = record
    constructor Create; // [dcc32 Fehler] E2394 Parameterlose Konstruktoren sind für Record-Typen nicht zulässig

    constructor Create(Value: Integer);
    constructor Create(Values: array of Integer); overload; // bzw. (Values: TArray<Integer>)
  end;

  TMyRecord = record
    class function Create: TMyRecord; static; // der Bugfix für den E2394 
  end;
Das alte TPoint kennst'e doch noch?
Delphi-Quellcode:
  TPoint = record
    X: Integer;
    Y: Integer;
  end;
neu sieht es so aus
Delphi-Quellcode:
  TPoint = record
    X: FixedInt;
    Y: FixedInt;
  public
    constructor Create(P : TPoint); overload;
    constructor Create(const X, Y : Integer); overload;

    //operator overloads
    class operator Equal(const Lhs, Rhs : TPoint) : Boolean;
    class operator NotEqual(const Lhs, Rhs : TPoint): Boolean;
    class operator Add(const Lhs, Rhs : TPoint): TPoint;
    class operator Subtract(const Lhs, Rhs : TPoint): TPoint;

    class operator Implicit(Value: TSmallPoint): TPoint;
    class operator Explicit(Value: TPoint): TSmallPoint;

    class function PointInCircle(const Point, Center: TPoint; const Radius: Integer): Boolean; static; inline;
    /// <summary> Zero point having values of (0, 0). </summary>
    class function Zero: TPoint; inline; static;

    function Distance(const P2 : TPoint) : Double;

    procedure SetLocation(const X, Y : Integer); overload;
    procedure SetLocation(const P : TPoint); overload;
    procedure Offset(const DX, DY : Integer); overload;
    procedure Offset(const Point: TPoint); overload;
    function Add(const Point: TPoint): TPoint;
    function Subtract(const Point: TPoint): TPoint;
    function IsZero : Boolean;

    function Angle(const APoint: TPoint): Single;
  end;
Delphi-Quellcode:
var Demo: TArray<TPoint>; // oder : array of TPoint;
Demo := [TPoint.Create(1, 2), TPoint.Create(3, 4)];
Demo := Demo + [TPoint.Create(5, 6)];
Insert(TPoint.Create(7, 8), Demo, 3);
Viele Records haben keinen Constructor und Delphi generiert auch (noch) keinen Standard-Constructor. :cry:
TInput leider auch, aber das kann man noch nachrüsten:
Delphi-Quellcode:
type
  TInputHelper = record helper for TInput
    constructor Create(P: TMouseInput); overload;
    constructor Create(P: TKeybdInput); overload;
    constructor Create(P: THardwareInput); overload;
  end;

  // bzw.
  TInputHelper = record helper for TInput
    constructor Create(dx, dy: LongInt; mouseData, dwFlags: DWORD); overload;
  //constructor Create(dx, dy: LongInt; mouseData, dwFlags, time: DWORD; dwExtraInfo: ULONG_PTR); overload;
    constructor Create(wVk, wScan: WORD; dwFlags: DWORD); overload;
  //constructor Create(wVk, wScan: WORD; dwFlags, time: DWORD; dwExtraInfo: ULONG_PTR); overload;
  //constructor Create(P: THardwareInput); overload;
  end;







PS: Record-Helper für sowas wie Enum und Set geht noch nicht so, aber man könnte sich einen Record mit AutoCasts basteln.
Also TShiftStateEx als Parameter für deine Funktionen. Dann brauchst'e das Overload nicht mehr, da sich die beiden Sets größtenteils automatisch von und dahin casten, bei Zuweisung := , sowie bei Übergabe an den Parameter.

Zu geil wäre es natürlich, wenn man stattdessen TSIHShiftState so anpassen könnte, dass TShiftState zuweisungskompatibel würde. (aber sowas erwarte ich frühestens im nächsten Jahrtausend von Embarcadero)

Delphi-Quellcode:
  TShiftStateEx = record
  private
    FState: TSIHShiftState;
    function GetShiftState: TShiftState; // Result := TShiftState(FState - ssWin);
    procedure SetShiftState(Value: TShiftState); // FState := TSIHShiftState(Value);
  public
    class operator Implicit(a: TSIHShiftState): TShiftStateEx; // Result.FState := a; -> entspricht TheRecord := SIHShiftState;
    class operator Implicit(a: TShiftState): TShiftStateEx; // Result.FState := TSIHShiftState(a); -> entspricht TheRecord := ShiftState;
    class operator Implicit(a: TShiftStateEx): TShiftState; // Result := TShiftState(a.FState - ssWin); -> entspricht ShiftState := TheRecord;
    class operator Explicit(a: TShiftStateEx): TSIHShiftState; // Result := a.FState; -> entspricht SIHShiftState := TSIHShiftState(TheRecord);

    // Und noch ein paar kleine Hilfsfunktionen, falls man nicht alles direkt über harte oder implizite Casts machen will:
    property AsShiftState: TShiftState read GetShiftState write SetShiftState;
    property AsSIHShiftState: TSIHShiftState read FState write FState;
    function HasMenu: Boolean; // Result := ssWin in FState;
  end;
Delphi-Quellcode:
    // anstatt
    procedure AddShift(ShiftState: TSIHShiftState; Press, Release: Boolean); overload;
    procedure AddShift(ShiftState: System.Classes.TShiftState; Press, Release: Boolean); overload;
    procedure AddShortCut(ShiftState: TSIHShiftState; ShortChar: Char); overload;
    procedure AddShortCut(ShiftState: TSIHShiftState; ShortVK: Word); overload;
    procedure AddShortCut(ShiftState: System.Classes.TShiftState; ShortChar: Char); overload;
    procedure AddShortCut(ShiftState: System.Classes.TShiftState; ShortVK: Word); overload;

    // nur noch
    procedure AddShift(ShiftState: TShiftStateEx; Press, Release: Boolean); overload;
    procedure AddShortCut(ShiftState: TShiftStateEx; ShortChar: Char); overload;
    procedure AddShortCut(ShiftState: TShiftStateEx; ShortVK: Word); overload;

WladiD 6. Dez 2023 08:07

AW: SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
 
Hi Himitsu,

vielen Dank für dein ausführliches Feedback. Wenn du magst, kannst du einen PullRequest erstellen. Ich würde dich natürlich in den Credits aufführen.

Sonst weiß ich nicht, wann ich dazu komme, dies einzubauen...


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:43 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz