How can I stop changing style in firemonkey in ScaleChanged message - macos

I use Firemokey 10.2. In macOS, internally when you move your from from a normal display to a retina display it automatically changes style of the controls. I would like to stop this message which is TScaleChangedMessage. I would appreciate If you could help me how can I stop this message in my app. In other word how can I stop changing from normal styles to High-Resolution Styles

The easiest way would be to create a copy of the FMX.Platform.Mac unit, and modify the TFMXWindow.windowDidChangeBackingProperties method so that the message is never sent, e.g:
procedure TFMXWindow.windowDidChangeBackingProperties(notification: NSNotification);
begin
// if (Application = nil) or (Application.Terminated) then
// Exit;
// try
// TMessageManager.DefaultManager.SendMessage(nil, TScaleChangedMessage.Create(Wnd), True);
// except
// HandleException(Self);
// end;
end;
i.e. just comment out everything in it
Unfortunately if you're using Delphi 10.2 Update 1, it means you'll need to include all the FMX units in the project path (so that they're also recompiled), due to this issue:
https://quality.embarcadero.com/browse/RSP-18836

I found the solution,
changing this function
function TMacWindowHandle.GetScale: Single;
begin
//Result := Wnd.backingScaleFactor
result := 1;
end;
will solve this problem :)

Related

Dynamically insert number on Delphi app's icon

I'm on Delphi 10.4.
I'm looking for a way to dynamically insert a number on the app's icon on the taskbar, so the user can know about how many tasks the apps has done so far. This would be dynamically, as soon as the app does a new task, it will increase the icon's number.
Something like the image below.
Is this possible ?
I don't have any code to post here because i don't have any idea how to do this.
You might not be aware of the TTaskbar taskbar-configuration component and its OverlayIcon property.
Example:
with a very straightforward implementation:
procedure TForm1.btnInfoClick(Sender: TObject);
var
io: TIcon;
begin
io := TIcon.Create;
try
io.Handle := LoadIcon(0, IDI_INFORMATION);
Taskbar1.OverlayIcon := io
finally
io.Free;
end;
end;
In your case, you can either create icons 1.png, 2.png, ... non-programmatically and use those, or you can create icons programmatically (create a CreateOverlayIcon(ANumber: Integer): TIcon function).
I should warn you, however, that the TTaskbar component used to be (very) buggy. Therefore I would not use that one; instead, I would use the ITaskbarList3::SetOverlayIcon API directly.
In any case, my suggestion is to split your problem into two parts:
Create overlay icons.
Use the Windows 7 taskbar overlay icon feature to display these on top of your original icon.

How to draw on a TTrayIcon

I'm trying to draw on the canvas of a TTrayIcon but have no success.
I'm programming with Lazarus V1.6.2 on windows 10.
What I tried so far is:
procedure TForm1.TrayIcon1Paint(Sender: TObject);
var
tmpcanvas: TCanvas;
begin
tmpcanvas := TrayIcon1.Canvas;
tmpcanvas := TrayIcon1.Icon.Canvas; // also not working
tmpcanvas.Font.Size := 22;
tmpcanvas.Brush.Color := RGBToColor(255,255,255);
tmpcanvas.FillRect(1,1,200,200);
tmpcanvas.Font.Color := RGBToColor(0,0,0);
tmpcanvas.TextOut(1,1, 'TEST');
end;
I tried this code in the event Form1.OnPaint with the Form1.Canvas and there it works as expected.
But I have no luck with painting on the TTrayIcon.Canvas.
It seems, that drawing on canvas of TTrayIcon is different from drawing on other canvases...
After some debuging I realized, that the TTrayIcon.OnPaint event is never triggered. But even when I force to execute TrayIcon1Paint(..) nothing happens.
Now I am out of ideas. Any help is highly appreciated.
The Windows notification icons do not offer any interface that would match an OnPaint event. I can only imagine that the OnPaint event is intended for use on different platforms.
On Windows notification icons are provided to the system in the form of Windows icon objects. If you wish to change the appearance of your notification icon, you need to provide a new icon object. I'm not familiar with this particular wrapper of the Windows API function, but I would expect that you could write code like this to update the notification icon's appearance:
TrayIcon1.Icon := MyNewIcon;

Focus Ring effect missing when using Delphi FMX in OSX

I am using following code to add a native NSTextField into a Delphi FMX form in OSX. The textfield has been added and works very well, except the system default focus ring effection (the blue border outside the textfield when user select it) is missing.
(tested with XE8, and OSX10.9, 10.10).
I guess this is caused by how Delphi/FMX implemented the TForm under OSX, because this system focus ring effects seems draw outside the control self. The Delphi form may have some special settings to "prevent" system drawing this effects on it by mistake, and I tried all the related settings of NSTextField without success.
uses MacApi.AppKit;
procedure TForm1.AddNSTextField;
var
tf: NSTextField;
vw: NSView;
r: NSRect;
begin
vw := (WindowHandleToPlatform(Form1.Handle)).View;
if Assigned(vw) then
begin
r.origin.x := 10;
r.origin.y := 30;
r.size.width := 300;
r.size.height := 22;
tf := TNSTextField.Wrap(TNSTextField.Wrap(TNSTextField.OCClass.alloc).initWithFrame(r));
vi.addSubview(tf);
//tf.setFocusRingType(0); //Also tried to manually set but no use.
tf.setStringValue(NSStr('Untitled'));
end;
end;
Anyone could help? Thanks.

How can I stop my application showing on the taskbar?

My application has an option for the users to run it only in the system tray, and not in the task bar. This worked fine when my application was built by Delphi 6. After switching to Delphi XE2 it no longer functions.
I've messed with it some, and I have this working for Windows 7, but when running on Windows XP I still have a problem. The application correctly hides from the task bar, and shows in the system tray. But when I create and show any additional form, the icon shows up in Windows XP.
procedure TfrmAppointment.HideWindowFromTaskbar;
var
TaskbarList: ITaskbarList;
begin
Application.MainFormOnTaskBar := False;
// Windows 7 seems to behave differently. This seems to fix it.
if (CheckWin32Version(6, 1)) then
begin
// We are in Win7, and we requested the tray.
TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
TaskbarList.HrInit;
TaskbarList.DeleteTab(Application.Handle);
end
else
begin
// Previous code from D6 days
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
ShowWindow(Application.Handle, SW_SHOWNOACTIVATE);
end;
end;
That code is ran if the user chooses the option to show the application in the system tray. It works fine on all versions of Windows I've tested on. On Windows XP, however, when I show any child form, the application instantly shows up in the taskbar. In Windows 7 all is fine.
Any ideas what I'm missing?
I should add that I know this is likely the same question as Hide the Main Form in a Delphi 2009 Application, however I already have the MainFormOnTaskBar being set, so that answer does not seem to apply.
[EDIT:] To be more specific, I'm adding additional information here. This application has two modes: Show in task bar, and show in system tray.
The first mode is the same as any normal application. The application exists only in the task bar. It minimizes to the task bar. It restores from the task bar.
The second mode behaves exactly the same, BUT that task bar icon instead exists in the system tray only. So, when a user minimizes the application, I intercept that message, grab the TRect for 'Shell_TrayWnd'/'TrayNotifyWnd', and call DrawAnimatedRects() to simulate the minimize to the tray. Then I hide the main form. On message from the system tray I draw the same animation rects in reverse, and make it visible again. While the form is visible it does not show in the task bar.
This all works perfectly fine in all Windows versions.
The specific issue I am having is that when any other form gets shown, Windows XP is creating the application icon in the task bar. Windows 7 does not do this. So if a Windows XP user only uses the application main form, no problems arise and both viewing modes work fine. If they open another window, the application icon appears, and stays there even after that window closes. Windows 7 does not do this, and the icon stays gone.
You should set
Application.MainFormOnTaskBar := True;
in your .dpr file and then never modify that setting.
Then, when you want to remove the main form from the taskbar you simply write
MainForm.Hide;
When you need to bring the main form out of hiding again write
MainForm.Show;
And that's it.
Naturally you'll want to show and hide your notification area icon in concert with hiding and showing the main form.
The code in HideWindowFromTaskbar is not necessary and you should remove it. When you application is in MainFormOnTaskBar equals True mode, the main form is an un-owned top-level window. And so it appears on the taskbar whenever it is visible. So you can remove the main form from the taskbar simply my hiding it.
The other forms in your application will be owned top-level windows. Typically they will be owned by your main form. By virtue of being owner, they will not appear on the taskbar.
By and large you should try hard to avoid fiddling with window styles. You can usually make your application behave the way you need without doing so. What's more, if ever you have to adjust window styles, you must do it in CreateParams. That way the window style will persist when the window gets re-created. But I re-iterate, avoid modifying window styles where you can.
The key MSDN references are:
Window Features.
The Taskbar.
Here's the smallest program I can produce that proves the point:
program MainFormHiding;
uses
Forms, StdCtrls;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleMainFormVisible(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleMainFormVisible(Sender: TObject);
begin
MainForm.Visible := not MainForm.Visible;
end;
begin
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
MainForm.Caption := 'Main Form';
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleMainFormVisible;
Application.Run;
end.
In the comments you make it clear that you want to be able to hide the taskbar window without hiding the main form. In that case I suggest that you set MainFormOnTaskbar to False. That will mean that Application.Handle will be the window associated with the taskbar button. You can then hide that window to remove it from the taskbar.
You will now need to explicitly set PopupParent for any auxiliary forms. If you want those windows to be owned by the main form, then you can set it up.
Here's my example adjusted for this scenario:
program MainFormHiding;
uses
Forms, StdCtrls, Windows;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleTaskbarButton(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleTaskbarButton(Sender: TObject);
begin
if IsWindowVisible(Application.Handle) then
ShowWindow(Application.Handle, SW_HIDE)
else
ShowWindow(Application.Handle, SW_SHOW);
end;
begin
Application.MainFormOnTaskbar := False;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
OtherForm.PopupParent := MainForm;
MainForm.Caption := 'Main Form';
Application.Title := MainForm.Caption;
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleTaskbarButton;
Application.Run;
end.
Run this program and click on the toggle button. Now you will see main form and other form showing. And nothing in the taskbar. I included the toggle button to show that you can switch between your two modes of operation whilst the program is running. No need to restart it.
The key here is to make a window other than your visible forms be the window associated with the taskbar. Once you do that you can once again control taskbar presence by showing and hiding that window. In this case that window is the application window, Application.Handle. Because that's the window on the taskbar, you need to set its Title property to control its text.
I stress finally, once again, that interaction with the taskbar is best controlled with window owner and visibility. Always search for solutions using those methods rather than ITaskbarList, extended window styles etc.
Update
Hopefully the last word on the subject. As you have noticed, the code directly above has poor behaviour when the main form is minimised. When that happens, the application window is made visible again and so appears once more in the taskbar.
I'm not so sure of myself when it comes to suppressing this behaviour. The behaviour comes about because of the code in TApplication.Minimize which shows the application handle when the main form is minimized. The best solution that I have is to convert a main form minimize into a hide.
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
....
procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if (Msg.CmdType and $FFF0)=SC_MINIMIZE then
begin
Hide;
exit;
end;
inherited;
end;
Or another way would be to suppress the application window show by means of an OnMinimize event handler for TApplication.
class procedure TEventHandlerClass.ApplicationMinimize(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
David's answer is correct. There were a couple minor issues with it, but I ran with it and got everything working. He posted his last update while I was figuring this out. I am posting some additional code samples here, and accepted his answer. First I assigned:
Application.OnMessage := AppMessage;
Then the procedure is as follows:
procedure TfrmAppointment.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
// This first check decides if we are minimizing via the upper right button OR
// The context menu in the upper left hand corner of the window.
// Minimizing twice restores, so this can be a restore as well.
if ((((Msg.message = WM_NCLBUTTONDOWN) and (Msg.wParam = HTMINBUTTON)) or
((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_MINIMIZE))) and
(Screen.ActiveForm = Self)) then
begin
// This function is defined as (bool, bool) where the variables are:
// Param1: Mimimizing (true), Restoring (false)
// Param2: Draw animation rectangles for doing this or not
Handled := MinimizeOrRestore(Self.WindowState <> wsMinimized, True);
end
else if ((Msg.message = WM_SYSCOMMAND) and
(Msg.wParam = SC_RESTORE) and
(Screen.ActiveForm = Self)) then
begin
// Specifically, restore has been asked for
Handled := MinimizeOrRestore(False, True); // Minimize with animation
end
else if ((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_CLOSE)) then
begin
// The user just used the system menu to close the application
ApplicationIsClosing := True; // see below for this
end
end;
Then in my FormCloseQuery, I check for "ApplicationIsClosing" to be true. If it's FALSE, then I know the user hit the X, and I simply minimize the application calling the other function referenced here. If it's true, I allow the close.
Finally, MinimizeOnRestore grabs the TRect for the form itself, as well as the system tray and then executes DrawAnimatedRects. This doesn't work always on Vista or higher, but it doesn't error either. Next, it hides the main application window or makes it visible. It always returns true unless it encounters an error. Then it returns false.

Borderless TForm with drop shadow

I have made a TForm derivative that acts like the drop down part of a combo, or a hint window, or a popup menu - a temporary thing. It has no caption - its BorderStyle is set to bsNone. The form is displayed non-modally using Show, having set its position.
To make it stand out, it needs a drop shadow around its border. However, a consequence of setting its border to bsNone is that the drop shadow disappears.
Various Google sources suggest variations of this:
procedure TdlgEditServiceTask.CreateParams(var Params: TCreateParams);
const
CS_DROPSHADOW = $00020000;
begin
inherited;
{ Enable drop shadow effect on Windows XP and later }
if (Win32Platform = VER_PLATFORM_WIN32_NT) and
((Win32MajorVersion > 5) or
((Win32MajorVersion = 5) and (Win32MinorVersion >= 1))) then
Params.WindowClass.Style := Params.WindowClass.Style or
CS_DROPSHADOW;
end;
but it doesn't work - the shadow is not displayed (unless I also set a resizable border with WS_THICKFRAME set, which looks terrible). This is a popup window, not a child window, so I don't see why it should fail.
Suggestions please?
NB: this is a similar question to this question, which remains unanswered.
NB2: There is an obscure VCL component called TShadowWindow that looks like it will do the right thing, but turns out to be too crudely written to be practical.
Update: Following Andreas' comments below, I have investigated this further, and found some niceties.
Under Windows 7, I discovered that the shadow does not appear when the popup window if it is over another window from the same application.
Here is a simple Delphi app, which uses CreateParams on a popup window to request a shadow as described above.
See how the drop shadow appears where it extends beyond the main window?
But I want to use the borderless window as a popup over the main window. The drop shadow distinguishes the popup from the window underneath. All my description up above refers to this circumstance. Obviously some Windows mechanism is interfering here.
I have also tried the same application under Windows XP. Here is how it looks.
This works correctly with shadow everywhere*. Gah!
So it would seem to be a Vista/W7 thing, as Andreas suggests.
(*An earlier version of this text and screendump suggested that no shadow appeared. However, this turned out to be because I had the Windows XP display option 'Shadows under menus' turned off. Duh.)
Found it! Here is the proof:
As you can see, the drop shadow now shows properly over the form.
The problem was one of Z-order. It turns out that the shadow is itself a separate window maintained by Windows itself. In Windows 7, it seems to show the shadow underneath the main window. In order to get it to display properly, one needs to move it up.
A genius called Łukasz Płomiński explained this in a thread in the Embarcadero newsgroup. Here is his code to sort it out:
procedure TForm1.FixSysShadowOrder;
function FindSysShadowOrderProc(WindowHandle: HWND; // handle to window
Form: TForm1 // application-defined value, 32-bit
): BOOL; stdcall;
var
Buffer: array [0 .. 255] of char;
Rect: TRect;
begin
Result := True;
if IsWindowVisible(WindowHandle) then
begin
// this code search for SysShadow window created for this window.
GetClassName(WindowHandle, Buffer, 255);
if 0 <> AnsiStrComp(Buffer, PChar('SysShadow')) then
Exit;
GetWindowRect(WindowHandle, Rect);
if (Rect.Left <> Form.Left) or (Rect.Top <> Form.Top) then
Exit;
Form.FSysShadowHandle := WindowHandle;
// stop enumeration
Result := False;
end;
end;
begin
if not(csDesigning in ComponentState) and
((GetClassLong(Handle, GCL_STYLE) and CS_DROPSHADOW) = CS_DROPSHADOW)
and IsWindowVisible(Handle) then
begin
// for speed, proper SysShadow handle is cached
if FSysShadowHandle = 0 then
EnumThreadWindows(GetCurrentThreadID(), #FindSysShadowOrderProc,
lParam(Self));
// if SysShadow exists, change its z-order, and place it directly below this window
if FSysShadowHandle <> 0 then
SetWindowPos(FSysShadowHandle, Handle, 0, 0, 0, 0,
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOOWNERZORDER or SWP_NOSIZE);
end;
end;
You have to work out when to call FixSysShadowOrder(), because Z orders change, and it won't stay right. Łukasz suggested calling it in an idle routine (for example when updating an Action), and on receipt of WM_WINDOWPOSCHANGED message.
For making drop shadow to work we have to invoke SystemParametersInfo win32 API with SPI_SETDROPSHADOW parameter, to turn on the entire system's drop shadow effect, for more information, please refer to:
SystemParametersInfo
"It works on my computer."
(High-res)
But it is quite funny, for I have a faint memory of making the same conclusion as you make, that is, that CS_DROPSHADOW does not work without the thick, resizable, frame. Are you still running Windows Vista, perhaps?

Resources