FMX: TCanvas.DrawLine issue on Android - delphi

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.

Related

Taking a screenshot of a game work if is in dx9 but dont if is dx11

If i run a game in dx11 i get a white box with PrintWindow and a black box with GetDC/BitBlt but in dx9 mode it work correctly...
I tried to find another way with google but i cant find one working also i read that both don't work with hardware accellerated windows, but isnt dx9 hardware accellerated ?
PS.
If i do a screenshot of the whole desktop i get the dx11 window content.
PPS.
I need the specific window only, cropping the desktop wont do because in dx9 i can get the content of the whole window even if half window is outside the monitor while with the desktop i get it cut.
EDIT:
Windows is 10 21H2
The game is "Guild Wars 2", it can run in both dx9 and dx11.
This is the code working in dx9 mode even with the window outside the monitor:
function TakeSS(WindowHandle: HWND; FullWindow: boolean = true): TBitMap;
var
vRect: TRect;
vFlag: Cardinal;
GibRect: function(h: THandle; r: TRect): longbool;
begin
if FullWindow then
begin
GibRect := #GetWindowRect;
vFlag := PW_RENDERFULLCONTENT;
end
else
begin
GibRect := #GetClientRect;
vFlag := PW_CLIENTONLY;
end;
if GibRect(WindowHandle, vRect) then
begin
result := TBitMap.Create(vRect.Width, vRect.Height);
PrintWindow(WindowHandle, result.Canvas.Handle, vFlag);
end
else
result := nil;
end;

TCanvas.DrawLine behaves differently on Windows and on iOS

When drawing a list of connected lines using TCanvas.DrawLine they are not drawn connected on iOS, but they are on Windows. Here are the results of a simple test app connecting 40 points:
Targeting iPad 2 on simulator (non-retina, but it also happens on retina) I get this:
Targeting 32-bit Windows I get this:
The code in the test app (to reproduce the problem I have in a much more complex app):
var
LineList: array[0..39] = (
(X:8.00; Y:45.00),
(X:14.00; Y:43.00),
(X:21.00; Y:43.00),
(X:27.00; Y:44.00),
(X:31.00; Y:45.00),
(X:37.00; Y:45.00),
(X:40.00; Y:43.00),
etc.
);
procedure TForm24.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var i: integer;
begin
Canvas.BeginScene;
Canvas.StrokeThickness := 6;
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.Stroke.Color := $FF000000;
for i := 1 to 39 do
Canvas.DrawLine(LineList[i-1], LineList[i], 1.0);
Canvas.EndScene;
end;
Note: problem increases the higher value of StrokeThickness. If set to 1 the problem is gone.
I am using Delphi 10 Seattle update 1.
What can I do to solve this?
Edit:
Reported as a bug here:
https://quality.embarcadero.com/browse/RSP-13129

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.

Why do I get "left side cannot be assigned to" for TRect after upgrading Delphi?

I am migrating the code from Delphi 7 to XE2 one of the Graphical module.
we are using TRect variable , the old code is working in Delphi 7 without issue
Ex:
Var
Beold : TRect
begin
Beold.left := Beold.right;
end.
while porting the code to new XE2 we are facing the issue
E0264 : Left side cannot be assigned to
Can you please explain what is the changes in XE2 TRect and D7, how we can assign the valuse
The code you posted compiles and runs fine in a quick Delphi test app, so it's not your real code.
I'd suspect what you've hit is a change in the with statement when it's related to using properties, however. There was a bug in previous versions of Delphi that existed for many years that was finally fixed recently. IIRC, it was first mentioned in a note in the README.HTML file for D2010. It's been added to the documentation in XE2 (not as a behavior change, but the new behavior is documented). The documentation is located here at the docwiki.
(Additional info: It must have been 2010 where it changed; Marco Cantù's Delphi 2010 Handbook mentions it on page 111 as "The With Statement Now Preserves Read-Only Properties" which describes this behavior and the solution I indicated below.)
Instead of accessing the property of a class directly using a with statement, you now need to declare a local variable, and read and write the whole thing directly (error handling omitted for clarity - yes, I know there should be a try..finally block to free the bitmap).
var
R: TRect;
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
Bmp.Width := 100;
Bmp.Height := 100;
R := Bmp.Canvas.ClipRect;
{ This block will not compile, with the `Left side cannot be assigned to` error
with Bmp.Canvas.ClipRect do
begin
Left := 100;
Right := 100;
end;
}
// The next block compiles fine, because of the local variable being used instead
R := Bmp.Canvas.ClipRect;
with R do
begin
Left := 100;
Right := 100;
end;
Bmp.Canvas.ClipRect := R;
// Do other stuff with bitmap, and free it when you're done.
end.
turns out, using
with (Bmp.Canvas.ClipRect) do
begin
Bottom := 100;
end;
throws error: [Left side cannot be assigned to]
Yet,
with Bmp.Canvas.ClipRect do
begin
Bottom := 100;
end;
does not.
Delphi 10.3.3 is just as finicky about parenthesis as the older versions.

Issues with nVidia nView desktop manager and Delphi applications?

I've recently had a customer report an issue with my applications and NVidia nView desktop manager. Basically nView seems to 'unhide' hidden secondary forms when moving an application between monitors. I'm testing with Delphi 2010. The problem can also be seen using just the IDE (show a non-docked window, close it and then choose to send the Delphi IDE to a different monitor.) The previously hidden forms are in an unresponsive state and can't be closed.
To reproduce:
. Use a multi-monitor system with NVidia nView desktop manager.
. Start any Delphi application with secondary forms that will be hidden when they are closed.
. Show or ShowModal the form, then close it (form needs to have been shown at least once).
. Choose to send the application to a different monitor. (via hotkeys or the caption icons)
. The application will move to the other monitor and any hidden forms will be visible.
Has anyone else seen this issue? I've verified it with an older version of nView, but a customer with the latest version has the issue whenever he tries to move the main form or resize it. Unfortunately my laptop can't be updated to the latest version so I can't easily test with it.
Thanks for any comments or suggestions!
-Mark
I had the same problem. The Delphi application stopped responding without any obvious reason. Looking at the stack trace from madExcept I could see that the application froze in nview.dll. The only "solution" that I found was to look for nView and advise the user to turn it off using the code below:
function CheckForNview: Boolean;
function IsNviewModuleRunning(AHandle: THandle; AProcessId: DWord): Boolean;
var
AModuleEntry: TModuleEntry32;
begin
AModuleEntry.dwSize := SizeOf(AModuleEntry);
AModuleEntry.th32ProcessID := AProcessId;
Result := False;
if Module32First(AHandle, AModuleEntry) then
begin
if SameStr(AModuleEntry.szModule, 'nview.dll') then
Result := True;
while Module32Next(AHandle, AModuleEntry) do
begin
if SameStr(AModuleEntry.szModule, 'nview.dll') then
Result := True;
end;
end;
end;
var
AHandle: THandle;
AProcessEntry: TProcessEntry32;
begin
Result := False;
AHandle := CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if Process32First(AHandle, AProcessEntry) then
begin
if SameStr(AProcessEntry.szExeFile, ExtractFileName(ParamStr(0))) then
Result := Result or IsNviewModuleRunning(AHandle, AProcessEntry.th32ProcessID);
while Process32Next(AHandle, AProcessEntry) do
begin
if SameStr(AProcessEntry.szExeFile, ExtractFileName(ParamStr(0))) then
Result := Result or IsNviewModuleRunning(AHandle, AProcessEntry.th32ProcessID);
end;
end;

Resources