Strange WM_CHAR behaviour (wrong chCharCode) - delphi

I need to automate entering a certain character (Russian letter Э). In Spy++ the corresponding message looks like this:
WM_CHAR chCharCode: '221' (221) cRepeat:1 ScanCode:28 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
In order to send this message programmatically, I use this Delphi code:
SendMessage(Self.PassengerGrid, WM_CHAR, WPARAM(221), LPARAM($280001));
When I examine the results of running my code in Spy++, I see following message:
WM_CHAR chCharCode: '89' (89) cRepeat:1 ScanCode:28 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
Something must be wrong with wParam of my SendMessage call.
How can I fix it (so that the chCharcode is equal to 221)` ?
Update 1:
The machine, where this error occurs, has two keyboard languages - English and Russian.
I noticed that when the following code
SendMessage(MyGridHandle, WM_KEYDOWN, VK_OEM_7, LPARAM($390000));
SendMessage(MyGridHandle, WM_CHAR, WPARAM(221), LPARAM($280001));
SendMessage(MyGridHandle, WM_KEYUP, VK_OEM_7, LPARAM($c0390001));
is executed, the selected language (according to tray icon) changes from Russian to English.
Whatever character I transmit in WM_CHAR, WPARAM of the message is always 0x59 (89).
Update 2: Using WM_UNICHAR instead of WM_CHAR doesn't help.

You should send UTF-16 code of a character as WPARAM (that is 1069 for Russian 'Э'), ex:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(Edit1.Handle, WM_CHAR, WPARAM(1069), LPARAM(0));//$280001));
end;

Related

Delphi, TEdit text as action trigger

I am using a TEdit to allow the user to enter a number, e.g. 10.
I convert the TEdit.Text to an integer and a calculation procedure is called.
In this calc procedure, a check was built-in to make sure no numbers below 10 are processed.
Currently I use the OnChange event. Suppose the user wants to change '10' into e.g.'50'. But as soon as the '10' is deleted or the '5' (to be followed by the 0) is typed, I trigger my warning that the minimum number is 10. I.e. the program won't wait until I have fully typed the 5 and 0.
I tried OnEnter, OnClick, OnExit, but I seem not to overcome this problem. The only solution is to add a separate button that will trigger the calculation with the new number. It works, but can I do without the button?
Use a timer for a delayed check, e.g.:
procedure TForm1.Edit1Change(Sender: TObject);
begin
// reset the timer
Timer1.Enabled := false;
Timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := false;
// do your check here
end;
Setting the timer to 500 ms should be fine for most users.
And as David suggested in the comments to the question: Do not show an error dialog, use something less intrusive instead, e.g. an error message in a label near the edit or change the background color. And also: Do not prevent the focus to be moved away from that control and do not play a sound, that's also very annoying.
For our in house software we set the background of a control to yellow if there is an error and display the error message of the first such error in the status bar and also as a hint of the control. If you do that, you probably don't even need to have the delay.
Thanks, for your help. I tried the timer option, but could not get that to work. I now have this code, which works (almost - see below), but requires the used to always type a CR:
procedure Calculate;
begin
// this is my calculation procedure
ShowMessage('calculation running correctly');
end;
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
var
N : integer;
begin
if Key = #13 then
begin
N := StrtoInt(Edit1.Text);
if N<10 then ShowMessage('<10!') else
if N>100 then ShowMessage('>100!') else Calculate;
end;
end;
I used the ShowMessage() here only to see if the sample code worked. In the real program I have left that out, as you all suggested.
I also included the 'turn yellow on wrong entry' (thanks David). The only issue is that if the user runs this I get a beep from my computer. I can't see what went wrong. But what is causing the beep?

Delphi printing to Zebra printer

I have written a function that prints out ZPLII data to a Zebra printer as shown below
var
St : string;
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
Escape(PrinterHandle,PASSTHROUGH,Length(St),PAnsiChar(St),Nil);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I have tested the label data by using the command prompt to print it from a text file and the label prints out correctly but i cannot print it from my application. If i pause the printer i can see the job gets sent to the printer and the size of the job is 2.12Kb, roughly the size the label should be, but nothing prints out. The light on the Zebra printer for data lights up but nothing will print. I have tried this with two of the Zebra printers we own, so it is not a printer issue. My guess at this point is that maybe the program isn't sending the entire label data to the printer and the end is never received but when i trace through the send request, everthing is sent properly. the printer also shows the job has 0/0 pages for the label but i cannot understand why it is not sending the label. Is there something special that needs to go at the end of the label data besides the ^XZ termination character? I am also using Delphi XE3 if that helps.
Thanks to everyone's help I was able to successfully print my labels using the following changes:
St: AnsiString;
...
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
WritePrinter(PrinterHandle,PChar(St),Length(St),N);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I also had to change to writeprinter instead of escape. Escape did not print anything out after i change st to type AnsiString but writeprinter was successful.

How to send virtual keys to other application using delphi 2010?

I need to send several virtual keys (VK_RETURN) from my delphi application (myapp.exe) into another application (target.exe).
Eg : Send VK_RETURN twice , from myapp.exe , into target.exe
The OS that I use are Windows 7 64 bit and Windows XP.
I read : How to send an "ENTER" key press to another application? , Send Ctrl+Key to a 3rd Party Application (did not work for me) and other previous asked question.
But still I'm getting confused.
How to set the focus to the target application ?
How to send the virtual keys to the targeted application ?
Simple example : I want to send VK_RETURN twice into notepad.exe or calc.exe (already loaded) or any other program from my delphi application. How to do that ?
The simplest way to do this in Delphi 2010, please...
PS :
I tried SndKey32.pass from http://delphi.about.com/od/adptips2004/a/bltip1104_3.htm
And got error : [DCC Error] SndKey32.pas(420): E2010 Incompatible types: 'Char' and 'AnsiChar'
If (Length(KeyString)=1) then MKey:=vkKeyScan(KeyString[1])
If your target application isn't the foreground window, you need to use PostMessage to send keystrokes to its window handle. You can get that window handle using FindWindow. The code below sends the Enter key to a the text area in a running instance of Notepad (note it uses an additional FindWindowEx to locate the memo area first). It was tested using both Delphi 2007 and Delphi XE4 (32-bit target) on Windows 7 64.
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd, NpEdit: HWnd;
begin
NpWnd := FindWindow('Notepad', nil);
if NpWnd <> 0 then
begin
NpEdit := FindWindowEx(NpWnd, 0, 'Edit', nil);
if NpEdit <> 0 then
begin
PostMessage(NpEdit, WM_KEYDOWN, VK_RETURN, 0);
PostMessage(NpEdit, WM_KEYUP, VK_RETURN, 0);
end;
end;
end;
To find the window by title (caption) instead, you can just use the second parameter to FindWindow. This finds a new instance of Notepad with the default 'Untitled' file open:
NpWnd := FindWindow(nil, 'Untitled - Notepad');
Note that this requires as exact match on the window title. An extra space before or after the -, for instance, will cause the match to fail and the window handle to not be retrieved.
You can use both the window class and title if you have multiple instances running. To find the copy of Notepad running with Readme.txt loaded, you would use
NpWnd := FindWindow('Notepad', 'Readme.txt - Notepad');
To find other applications, you'll need to use something like WinSpy or WinSight to find the window class names. (There are others also, such as Winspector or WinDowse (both of which are written in Delphi).)
Your comment mentions Calculator; according to Winspector, the Calculator main window is in a window class called CalcFrame on Windows 7, and the area the numbers are displayed in is a Static window (meaning it doesn't seem to receive keystrokes directly). The buttons are simply called Button, so you'd have to loop through them using EnumChildWindows looking for the individual buttons to identify them in order to obtain their handles.
(How to enumerate child windows is a separate question; you can probably find an example by searching here or via Google. If you can't, post a new, separate question about that and we can try to get you an answer.)
Here's a quick example of sending keys to Calculator after finding it by window class. It doesn't do anything useful, because it needs some time spent to identify different buttons and the keys that each responds to (and the proper combination of messages). This code simply sends 11Numpad+22 to the calculator window (a quick test showed that they were properly received and displayed, and that's about all the time I wanted to spend on the process).
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd: HWnd;
begin
NpWnd := FindWindow('CalcFrame', nil);
if NpWnd <> 0 then
begin
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD1, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_ADD, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD2, 0);
end;
end;

Sending WM_COMMAND to a TMenuItem

In my Delphi form's OnShow method, I determine that a dialog must be opened automatically once the form is opened - and I should be able to do this by simulating a click on a menuitem.
However, calling menuitem.Click brings up the dialog before the main form has opened - which is not what I want.
I expect that should do what I want, but I cannot find what parameters to pass for "wparam" to send the click to my menuitem.
PostMessage(handle, WM_COMMAND, wparam, 0)
The MSDN WM_COMMAND docs talk about IDM_* identifiers, but how does that appear in Delphi?
(I know this is a very old question but despite being resolved in some way the real question has really gone unanswered.)
--
The command item identifier of a 'TMenuItem' is in the Command property. According to WM_COMMAND's documentation the high word of 'wParam' would be '0' and the low word would be the menu identifier;
PostMessage(Handle, WM_COMMAND, MakeWParam(MyMenuItem.Command, 0), 0);
or simply;
PostMessage(Handle, WM_COMMAND, MyMenuItem.Command, 0);
With a popup menu item there would be a slight difference: the VCL handles popup menus' messages with a different utility window. The global PopupList variable has the handle to it in its Window property;
PostMessage(PopupList.Window, WM_COMMAND, MyPopupMenuItem.Command, 0);
Perhaps you can try to open the dialog in the OnActivate event ?
I am not really sure if the OnActivate gets fired again other than when the form is shown but if it does you can use :
procedure TForm1.FormActivate(Sender: TObject);
begin
Form2.ShowModal;
Self.OnActivate := nil;
end;
Wouldn't you have to do this with a one-time timer, if you want the form to appear as per a normal Show/ShowModal, get drawn (etc) fully, and then immediately do something else?
tmrKickOff : a TTimer, 100 ms interval, disabled at design time,
fires off a 'tmrKickOffTimer' event.
in form create,
tmrKickOff.Enabled:=false; //just in case something happened in IDE
in form show, at end of all other stuff;
tmrKickOff.Enabled:=true;
in tmrKickOffTimer
begin
tmrKickOffTimer.Enabled:=false;
menuItemClick(nil);
end;
with apologies for style, form and any error-trapping :-)
Alternatively, handle the Application.OnIdle event with something along the lines of ...
if not DialogDone then
begin
MyDialogForm.ShowModal; // or menuItem.Click ....
DialogDone := true;
end;
OnIdle won't fire (for the first time) until the Form is shown and the message queue is empty.
I don't think you can send a message directly to your menu item, but you can just post it to the main window and show your dialog from there. I do this and it works great so that the dialog box (in my case, a login prompt) appears on top of the main window to avoid confusion.
-Mark
procedure WMPostStartup(var Message: TMessage); message WM_POSTSTARTUP;
procedure TMainForm.WMPostStartup(var Message: TMessage);
begin
Self.Refresh;
// Now show the dialog box.
end;
One method I have used, which is very simular to MarkF's solution, is to create a new user defined message type and send a message using that type to yourself when you determine that you need to perform this other process after your main form displays:
const
wm_SpecialProc = wm_User + 1;
procedure TForm1.WMSpecialProc(var Message:tMessage); message wm_SpecialProc;
begin
Form2.ShowModal;
end;
procedure TForm1.OnShow(Sender:tObject);
begin
if true then
PostMessage(Application.MainForm.Handle,wm_SpecialProc,0,0);
end;
The nice thing about this method is that you are in control of the message generation, so can populate ANY lparam or wparam you want to later use by your handler. I sent the message directly through the application.mainform but you could also just say handle for the current form.

determine if another application is busy

How do I check if another application is busy?
I have a program that sends text to a console. The text that I will send contains #13 char (e.g. ls#13cd documents#13dir). In other words I want to send many commands at one time and the console will process them one by one. I am sending the text character by character. Sometimes the console only executes ls and cd documents. I think maybe this is because my program continuously sends character even if the console is busy, in which case the console does not receive incoming characters.
This is my code:
procedure TForm1.SendTextToAppO(Str: String; AHandle: Integer);
var
iWindow, iPoint, i: Integer;
SPass: PChar;
sList: TStringList;
begin
sList := TStringList.Create;
ExtractStrings([#13],[' '],PChar(Str),sList);
iWindow := AHandle;// AHandle is the handle of the console
iPoint := ChildWindowFromPoint(iWindow, Point(50,50));
for i:=0 to sList.Count-1 do begin
SPass := PChar(sList[i]);
try
while(SPass^ <> #$00) do begin
SendMessage(iPoint,WM_CHAR,Ord(SPass^),0);
Inc(SPass);
end;
SendMessage(iPoint,WM_KEYDOWN,VK_RETURN,0);
except
// do nothing;
end;
end;
end;
I am using Delphi 7.
If I interpret you question correctly you are sending the text to some sort of shell/command line interpreter and you want it to execute your commands.
Usually command line interpreters output a certain prompt (like $ on a Linux system or C:\ for DOS) that indicate that they can accept new commands. You need to read the output to wait for the appropriate prompt before you send another command. If you don't your sent text will be consumed as input by the currently running command (like you experienced).
lothar is on the right track; what you want to do is, instead of using ShellExecute, use CreateProcess. Look around Stack Overflow and Google for "Console Redirection" - that'll get you what you're looking for.
I think I understand what's going on, not that I have a fix for it:
You send a command to the console. While the command is running that program will receive the keys you send.

Resources