AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht
Thema durchsuchen
Ansicht
Themen-Optionen

SendInputHelper - Ein Wrapper, der den Umgang mit SendInput vereinfacht

Ein Thema von WladiD · begonnen am 1. Okt 2010 · letzter Beitrag vom 6. Dez 2023
Antwort Antwort
Seite 3 von 3     123   
WladiD
Registriert seit: 27. Jan 2006
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

Geändert von WladiD (25. Jul 2019 um 12:23 Uhr) Grund: Repository umgezogen
 
Benutzerbild von KodeZwerg
KodeZwerg

 
Delphi 11 Alexandria
 
#21
  Alt 11. Sep 2020, 09:22
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!
  Mit Zitat antworten Zitat
PeterPanino

 
Delphi 10.4 Sydney
 
#22
  Alt 3. Dez 2023, 10:39
DANKE nochmals für SendInputHelper - einfach nützlich und genial!
  Mit Zitat antworten Zitat
WladiD

 
Delphi 11 Alexandria
 
#23
  Alt 4. Dez 2023, 10:51
DANKE nochmals für SendInputHelper - einfach nützlich und genial!
Danke! Das geht runter wie Öl.
Waldemar Derr
  Mit Zitat antworten Zitat
PeterPanino

 
Delphi 10.4 Sydney
 
#24
  Alt 4. Dez 2023, 11:34
DANKE nochmals für SendInputHelper - einfach nützlich und genial!
Danke! Das geht runter wie Öl.
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!

Geändert von PeterPanino ( 4. Dez 2023 um 11:38 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

 
Delphi 12 Athens
 
#25
  Alt 4. Dez 2023, 12:03
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.

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)
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 ?

Geändert von himitsu ( 4. Dez 2023 um 12:09 Uhr)
  Mit Zitat antworten Zitat
PeterPanino

 
Delphi 10.4 Sydney
 
#26
  Alt 4. Dez 2023, 12:18
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.

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)
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?
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

 
Delphi 12 Athens
 
#27
  Alt 4. Dez 2023, 15:19
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;
---

protected gibt es nicht, da es leider keine Recordvererbung gibt.
Sinnvoll ist somit eh nur private und public .

class function können nur müssen static sein, wodurch es dann auch kein Self gibt. (ohne Vererbung gibt es in einer Funktion eh immer nur den einen "Klassen"-Typ)
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.
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;

Geändert von himitsu ( 4. Dez 2023 um 15:37 Uhr)
  Mit Zitat antworten Zitat
WladiD

 
Delphi 11 Alexandria
 
#28
  Alt 6. Dez 2023, 09:07
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...
Waldemar Derr
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 3 von 3     123   


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:36 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