USB barcode scanner inputs are ignored in FMX projects - delphi

i am trying to upgrade a program from Delphi7 to DelphiXE8.
In the programm there are some TEdit fields. You could input data in that fields via keyboard or a usb scanner. The usb scanner emulates a keyboard and works fine in all other programs. (Same program in Delphi7, Firefox, Editor, etc.......)
If I use the scanner in Delphi XE8 the TEdit field doesn't get correct data. If I trigger a KeyDown event I see that there are many Key 16/17/18 coming in but KeyChar is always #0.
Same Problem with TMemo.
I just tried something different:
In a VCL project the scanner works fine.
In a FMX project the scanner fails.
The scanner is a Birch BF-481BU/N.
Any ideas what could solve that problem?

My scanner has a Caps Lock setting.
Auto
Alt+Keypad
Caps Lock Off
Caps Lock On
With "Auto","Off","On" the scanner works fine with FMX.
With "Alt+Keypad" the scan fails in FMX.

I tried with a normal USB Scanner (Keyboard wedge) with this code that works fine.
procedure TForm2.Edit1KeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
begin
if KeyChar = #13 then ShowMessage('Your code is ' + Edit1.Text);
end;
Alt + Keypad is used to enter particular chars typing the ascii code.
As an example if you press ALT + 126 the result will be "~"
So probably you have to remove the Alt+Keypad settings on your scanner.

I had this problem with a game I'm building. It seems that the keycode is getting "handled" and set to null before it ever even gets to the form. It looks like they are only passing things through when the ALT or CTRL keys are pressed.
To solve this particular problem, I commented out a line in FMX.Platform.Win
procedure CurrentChar(Msg: tagMsg; var Key: Word; var Ch: WideChar; var Shift: TShiftState);
begin
Key := wParam;
Ch := WideChar(Msg.wParam);
Shift := KeyDataToShiftState(lParam);
if (Ch >= ' ') then
begin
if ((Shift * [ssAlt, ssCtrl]) = [ssAlt, ssCtrl]) then
begin
// AltGr + Char (in German keyboard)
Shift := Shift - [ssAlt, ssCtrl];
end;
//WHYYYY?!?!?!?!?!?!?
//if (([ssAlt, ssCtrl, ssCommand] * Shift) = []) then
// Key := 0;
end;
if ((([ssAlt, ssCtrl, ssCommand] * Shift) <> []) or (Ch < ' ')) and (Key > 0) then
Ch := #0;
end;
What I did FIRST though is I copied FMX source folder into your own private source tree and built it from there. Then your apps will build with any minor patches you put into it, but it is simpler than installing hacked design-time packages.
Once you have your own private FMX source, then you can start hacking it (which you will have to do from time to time).
The first thing you should do when starting a new project, is copy the FMX source folder into your own private source tree and build from there.
FMX still has a way to go before it is a comprehensive cross platform solution, but it is getting closer, so you'll have to mess with it occasionally. Using similar approaches, I added Android pen support, fixed some BLE issues... you'll probably come across your own things.

Related

FMX: TCanvas.DrawLine issue on Android

Lines with thickness > 1 appear to be drawn differently on Windows and Android. I'm using Delphi 11.0. Create a blank multi-platform application and add the following in the FormPaint event.
procedure TMainForm.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
begin
Canvas.Stroke.Thickness := 20;
Canvas.Stroke.Cap := TStrokeCap.Round;
Canvas.Stroke.Color := TAlphaColorRec.Red;
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.DrawLine(PointF(20,20), PointF(70,70), 1);
Canvas.DrawLine(PointF(70,70), PointF(130,70), 1);
end;
This results in the following. The same happens when drawing on a TImage.
It seems that in Windows the endpoints of the line are at the center of the line caps, whereas on Android they're at the extremes of the line caps. I'm testing on a Huawei P10 running Android 8.0.0. I'm not currently able to test on a more recent Android version. A Google search doesn't seem to give any results on this issue. I'd appreciate if anyone has any info on this issue and what could be done about it? If someone could test this on a more recent Android version, that would also be appreciated. I could of course add special code for Android to extend the line endpoints by half the line thickness, but I'd like to avoid that if possible.
The Android documentation seem to imply that it shouldn't behave like this.
https://developer.android.com/reference/android/graphics/Paint.Cap
The main difference between Windows and Android is that they use different implementations of TCanvas. Windows uses TCanvasD2D, whereas Android is using TCanvasGpu. Looking into the Delphi code. I wonder if the following code in FMX.StrokeBuilder.pas is causing the issue. This code gets runs from FMX.Canvas.GPU.pas, even with TStrokeDash.Solid. I can't work out why it would offset the ends like that.
procedure TStrokeBuilder.InsertDash(SrcPos, DestPos: TPointF; const DashDirVec, ThickPerp: TPointF);
var
InitIndex, DivIndex, Divisions: Integer;
SinValue, CosValue: Single;
RoundShift: TPointF;
begin
if FBrush.Cap = TStrokeCap.Round then
begin
RoundShift := DashDirVec * FHalfThickness;
SrcPos := SrcPos + RoundShift;
DestPos := DestPos - RoundShift;
end;
I can confirm that TCanvasGpu is the issue by setting FMX.Types.GlobalUseGPUCanvas to True before Application.Initialize. Then TCanvasGpu is used even on Windows instead of TCanvasD2D and I get the same issue that I see on Android.

How do I add the key binding Shift+Ctrl+H X to the Delphi IDE using the ToolsApi?

Adding a new ShortCut to the Delphi IDE is not too difficult because the Open Tools API provides a service for this. I am trying something apparently more complex: Add a Wordstar like additional ShortCut:
I want something to happen when the user presses
Shift+Ctrl+H followed by the single key X
where X should work regardless of the state of the Shift key.
This is my code:
procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
GExpertsShortcut: Byte;
ShiftState: TShiftState;
FirstShortCut: TShortCut;
SecondShortCut: TShortCut;
begin
GExpertsShortcut := Ord('H');
ShiftState := [ssShift, ssCtrl];
FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
SecondShortCut := ShortCut(Ord('X'), []);
BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
end;
So, if I set ShiftState := [ssCtrl] pressing
Ctrl+H X
calls my TwoKeyBindingHandler method.
But with ShiftState := [ssShift, ssCtrl] pressing
Shift+Ctrl+H X
does nothing.
Oddly enough, when specifying ShiftState := [ssShift, ssCtrl] (which should only affect the first key) pressing
Shift+Ctrl+H Shift+X
calls my TwoKeyBindingHandler method, even though the second ShortCut is added without a modifier key.
Any idea? Is this maybe a known limitation/bug of the Delphi IDE/Open Tools API? Is there a known workaround?
I tried it in Delphi 2007 and Delphi 10 Seattle, no difference.
You should be able to do it using the GetKeyState function.
The program has two operations - Think of it as opening a drop down menu item. When ctr-shift-h is pressed your programme will need to flag that the 'Menu' is now open and that subsequent keypresses will either activate an option or close the 'menu' if an invalid key is presses.
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
procedure Form1.OnkeyDown(...)
begin
if Not H_MenuOpen then
if IsKeyDown(vk_Control) and IskeyDown(vk_Shift) and IsKeyDown(vk_H) then
begin
//Some Boolean in the form
H_MenuOpen:=True;
//Will probably need to invalidate some parameters here so that
//no control tries to process the key
exit;
end;
if H_MenuOpen then
begin
if key=vk_X then
begin
//x has been pressed
*Your code here*
//possibly invalidate some of the params again
exit;
end;
//Nothing valid
H_MenuOpen:=False;
end;
end;
OK, since apparently nobody has found an answer, here is what I ended up doing:
I had already planned to show a hint window listing all possible characters for the second key (actually that code was already working fine, using the approach suggested by Helen Fairgrieve in her answer to this question). Instead, I now register only a one-key shortcut:
BindingServices.AddKeyBinding([FirstShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
And in the TwoKeyBindingHandler method, I show a popup menu which contains those characters as the shortcuts. The IDE/VCL/Windows then handles the rest for me.
This is what it looks like:
It's not an answer to the actual question but it solves my problem. Sorry if you got here expecting something more.

How to restrict input to letters only in Delphi XE5?

Ran into a couple of problems using a code that only lets the user input alphabetical characters and a backspace.
When I compile my program using RAD studio 2010, apart from Vcl problems in the Uses clause, It compiles correctly, all working fine. However when I try and compile using XE5, I get an error: E2010 Incompatible types: 'Word' and 'AnsiChar'
If anyone can point me in the right direction it would be great!
Code below:
procedure TFRMStuTakeTest.DBEDTWord01KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
(*only return value if alphabetic *)
if Key in ['A'..'Z', 'a'..'z', #8] then
else
Key := #0;
end;
Sorry if there's something about procedures having to be from clean projects (i.e. unnamed/ not descriptive)
OnKeyDown is designed to deal with virtual key codes (those that are referred to using the VK_ constants), not individual letter and number keystrokes.
Use OnKeyPress to process individual characters, not OnKeyDown.
procedure TFRMStuTakeTest.DBEDTWord01KeyPress(Sender: TObject; var Key: Char);
begin
if not CharInSet(Key, ['A'..'Z','a'..'z', #8]) then
Key := #0;
end;
Better still, use the EditMask on the underlying TField, and set a valid mask for alpha characters using something like 'LLLLL;0;_', which would require 5 letters between '['A'..'Z','a'..'z']', and will handle all of the validations, editing keystrokes, and so forth.
YourTable.FieldByName('Word1').EditMask := 'LLLLL;0;_';
See the documentation for TField.EditMask for more information, and follow the link at the bottom to TEditMask for details about the mask characters. (There's an editor for the EditMask property in the Object Inspector; they're the same as those used by TMaskEdit, so you can drop one of those on your form and click the ... button on the right side of the EditMask property to access it.)

How to use Delphi standard confirmation dialog but with checkbox "Don't ask me again"?

In many confirmation dialogs it is usefull to have such option (quick wayt to disable confirmation).
But i can't find how to do that. I don't want to design it myself because i need this dialog to be standard-like and don't wont to redesign with every update of Delphi. Is there simple way to use Delphi standard confirmation dialog with such checkbox ?
UPDATE2. Suggested SynTaskDialog library from Synopse project does great job (all i need and even more), i will use it in my projects. Thanks!
UPDATE. So, thank you guys for ideas. System function MessageBoxCheck is nice solution but seem to be not so stable as it should be. In general i agree that it is good idea to use latest API functions to provide users with best UI experience of modern os and use old-fashioned design for older systems. At moment i stay on simple solution (code is following), but if someone share the code with support of UI for modern OS, it will be nice.
function MsgDlgWithCB(const Msg,Title,CBMsg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; DefaultButton: TMsgDlgBtn;
var cbDontAskAnymore: TCheckBox): TForm;
var
i: integer;
b: TButton;
y: integer;
begin
Result := CreateMessageDialog(Msg, DlgType, Buttons, DefaultButton) ;
Result.Position := poScreenCenter;
cbDontAskAnymore := TCheckBox.Create(Result);
cbDontAskAnymore.Caption := CBMsg;
cbDontAskAnymore.Width := 130;
y := -1;
for i := 0 to result.ComponentCount-1 do
if result.Components[i] is TButton then
begin
b := TButton(result.Components[i]);
b.Left := b.Left + cbDontAskAnymore.Width + 16;
Result.ClientWidth := Max(Result.ClientWidth, b.Left+b.Width+16);
y := b.Top+b.Height-cbDontAskAnymore.Height;
end;
if y<0 then
y := Result.ClientHeight - cbDontAskAnymore.height - 16;
Result.Caption := Title;
cbDontAskAnymore.Parent := Result;
cbDontAskAnymore.Top := y;
cbDontAskAnymore.Left := 8;
end;
function MessageDlgCheckbox(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; DefaultButton: TMsgDlgBtn;
var cbDontAskAnymore: Boolean;
const Title: string ='Confirmation';
const CBMsg: string = 'Don''t ask anymore'): integer;
var
f: TForm;
c: TCheckbox;
begin
f := MsgDlgWithCB(Msg,Title,CBMsg,DlgType,Buttons,DefaultButton,c);
try
result := f.ShowModal;
cbDontAskAnymore := c.Checked;
finally
f.free;
end;
end;
You can use our Open Source SynTaskDialog unit.
Windows provides a generic task dialog available since Vista/Seven. But there is none available with previous versions of Windows, i.e. Windows XP or 2K.
This unit (licensed under a MPL/GPL/LGPL tri-license) will use the new TaskDialog API under Vista/Seven, and emulate it with pure Delphi code and standard themed VCL components under XP or 2K. It supports Delphi 6 up to XE4, and is Win32/Win64 Unicode ready.
Here is the result under a Windows Seven 64 bit computer:
And here is the same dialog created from our emulated pure Delphi code:
Since this screenshot was made on a Win 7 machine, the styling is native for that OS. When the emulated version of the dialog runs on XP it displays in a style native to that OS.
You have your "Do not ask for this setting next time" checkbox... and potentially much more!
The system native functionality that offers such facilities is the task dialog API introduced in Vista. This provides means for you to show much more capable dialogs than the older MessageBox API.
Should you need to support XP then you will have to create your own dialog. For example by deriving from TForm and calling ShowModal. If you do this, make the form capable of building itself dynamically. Don't make one form per message that you show!
In my codebase, I have my own wrapper of the task dialog API. This detects at runtime versions of Windows that do not support task dialog and falls back on a custom built Delphi dialog.
Regarding SHMessageBoxCheck I'd be a little wary of taking a dependency on that. According to its documentation it's not supported beyond XP, and you have to import it by ordinal. I'd personally be worried that it might be dropped from a future version of Windows. That said, MS has a strong track record of doing whatever it takes to keep legacy apps working with new OS releases.

Empty string in Delphi / Windows combo box causes access exception

I've got a Delphi 7.0 application that throws a memory access exception / message box every time it writeln's an empty string from the string list associated with a combo box:
csvstrlst := combobox1.Items;
csvstrlst.clear;
csvstrlst.add(''); //problem
csvstrlst.add('a'); //no problem
csvstrlst.add(''); //problem
csvstrlst.add('b'); //no problem
//throws memory access messages (I think the writeln writes a line though)
for n := 1 to csvstrlst.Count do begin
writeln(out_file,csvstrlst.strings[n-1])
end;
//throws memory access messages (writeln does write a comma text string though)
writeln(out_file,csvstrlst.commatext);
Running on Windows 7 or XP. As application or in D7 IDE. Combobox with empty string items also causes the same error if the parent of the form it is on is changed.
Has anyone else ever seen or heard of this problem? Any other information available at all?
This is a known and solved bug described in QC:
TCombobox gives AV when selecting empty item from dropdown
Although this is a bug, you should not reuse parts from controls to perform some data tasks as described in your question.
You will not save anything doing so, but getting most the time unwanted sideeffects (controls get repainted and/or fire events)
If you want to have a TStringList then create an instance.
csvstrlst := TStringList.Create;
try
// csvstrlst.Clear;
csvstrlst.Add( '' );
csvstrlst.Add( 'a' );
csvstrlst.Add( '' );
csvstrlst.Add( 'b' );
for n := 0 to csvstrlst.Count - 1 do
begin
WriteLn( out_file, csvstrlst[n] )
end;
WriteLn( out_file, csvstrlst.CommaText );
finally
csvstrlst.Free;
end;
As Sir Rufo has discovered the issue is a VCL bug introduced in Delphi 7 as described in QC#2246. According to that report the bug is resolved in a build with major version number 7 so you may be able to fix the problem by applying the latest Delphi 7 updates.
If not then you can fix the problem from the outside. I don't actually have a Delphi 7 installation to test this on, but I believe that this interposer class will work.
type
TFixedComboBoxStrings = class(TComboBoxStrings)
protected
function Get(Index: Integer): string; override;
end;
TComboBox = class(StdCtrls.TComboBox)
protected
function GetItemsClass: TCustomComboBoxStringsClass; override;
end;
function TFixedComboBoxStrings.Get(Index: Integer): string;
var
Len: Integer;
begin
Len := SendMessage(ComboBox.Handle, CB_GETLBTEXTLEN, Index, 0);
if (Len <> CB_ERR) and (Len > 0) then
begin
SetLength(Result, Len);
SendMessage(ComboBox.Handle, CB_GETLBTEXT, Index, Longint(PChar(Result)));
end
else
SetLength(Result, 0);
end;
function TComboBox.GetItemsClass: TCustomComboBoxStringsClass;
begin
Result := TFixedComboBoxStrings;
end;
The bug that was introduced in Delphi 7 is simply that the if statement reads:
if Len <> CB_ERR then
So, when Len is zero, that is when the item is the empty string, the True branch of the if is chosen. Then, the SendMessage becomes:
SendMessage(ComboBox.Handle, CB_GETLBTEXT, Index, Longint(PChar('')));
Now, PChar('') has special treatment and evaluates to a pointer to read only memory containing a zero character. And so when the combo box window procedure attempts to write to that memory, an access violation occurs because the memory is read only.

Resources