Why is my icon ugly on the tray bar with TTrayIcon? - delphi

I recently discovered the TTrayIcon component in Delphi 2007. The code used is pretty straightforward.
procedure TForm1.FormCreate(Sender: TObject);
begin
AppTrayIcon := TTrayIcon.Create(nil);
AppTrayIcon.OnDblClick := OnAppTrayIconDblClick;
Application.OnMinimize := OnApplicationMinimize;
Application.OnRestore := OnApplicationRestore;
end;
procedure TForm1.OnApplicationRestore(Sender: TObject);
begin
AppTrayIcon.Visible := False;
ShowWindow(Application.Handle, SW_SHOW);
Application.BringToFront;
end;
procedure TForm1.OnApplicationMinimize(Sender: TObject);
begin
AppTrayIcon.Visible := True;
ShowWindow(Application.Handle, SW_HIDE);
end;
procedure TForm1.OnAppTrayIconDblClick(Sender: TObject);
begin
Application.Restore;
end;
Since there is no icon assigned, Delphi uses Application.Icon, which is that icon: http://artbyloveland.com/icon.ico This icon includes the following sizes: 64x64, 48x48, 32x32, 24x24 and 16x16.
Now, on my Windows Vista, everything fine.
On a non-themed Windows like Windows Server 2003, the result is all screwed-up:
EDIT:
At first, I thought it was because of the alpha channel. So I tried to make a version of the ico file without the use of alpha channel. I also tried GreenFish Icon Editor as suggested by Ken; I selected every color depth and every size available. In both cases, the end result is better. However, there is a black stroke that doesn't exist at all in the ico file.

You state that you are not assigning the icon. In which case the component uses Application.Icon. But that will typically be an icon that is the wrong size for the notification area.
For the notification area you need to use a square icon with size determined by the SM_CXSMICON system metric. The best way to get that is to call LoadImage which allows you to specify the icon size. Once you have loaded the icon into an HICON you can just write this:
AppTrayIcon.Icon.Handle := IconHandle;

You don't have the proper size or color depth for your icon.
You can use an icon editor to provide multiple size and color depth icons to a single .ico file, and Windows will automatically choose the proper one based on the user's settings and video driver configuration. Windows will then have several choices to use when selecting the closest match, and the scaling and blending will have a much better appearance.
I use GreenFish Icon Editor, which is donation-ware. It will allow you to open any supported graphic type and then create a Windows icon with multiple color depths and resolutions automatically from it (see the Icon menu). I've tested the multi-image icon files in Delphi 7, 2007, 2010, XE, and XE3, and they work fine for the Application.Icon and TForm.Icon.
Also see Best Icon size for displaying in the tray

I thought, I'd share my solution to this problem, as there is currently no complete solution here.
This problem was driving me nuts, because this is actually clearly a Delphi/VCL bug. If you assign an icon with all required sizes (16, 24, 32, 48, 256) to your project, Delphi should automatically use the correct size in TTrayIcon, but instead it only takes the 32px icon and scales it down.
Since the required images are already in the exe file (for being displayed in the Windows Explorer), you can simply fix it like this:
procedure FixTrayIcon(TrayIcon: TTrayIcon);
var
i: Integer;
begin
i := GetSystemMetrics(SM_CXSMICON); //Gets the correct size for the tray (e.g. 16)
TrayIcon.Icon.Handle := LoadImage(hInstance, 'MAINICON', IMAGE_ICON, i, i, LR_DEFAULTCOLOR);
TrayIcon.SetDefaultIcon; //Updates the icon
end;
Just call it in FormCreate and your tray icon will look as designed.

Related

How to maximize form to desktop size?

In a Delphi 10.4.2 Win32 VCL application running on Windows 10, in a dual-monitor setup, when I set my MainForm (or any other secondary form) to start maximized by setting WindowState := wsMaximized, then the form is maximized only on the Primary Monitor.
How can I maximize the Form to the whole Desktop instead, to set the Form size to include BOTH MONITORS? Is there a built-in method of the TForm class to achieve this?
In general, this problem isn't as simple as you may think. I suppose you are imagining a desktop like this:
In this case, I assume you want the window to be placed like this:
However, what if the user has this layout:
Do you want
(entire window visible, but some screen space unused) or
(no unused space, but some parts of the window not visible)?
If you want to use the full virtual desktop space -- the last case -- it is easy though:
BoundsRect := Screen.DesktopRect;
This will do the expected thing in a simple setup, and the "no unused space, but some parts of the window might not be visible" thing in general.
Also be aware that Windows doesn't like that windows behave like this, so the user might not get a nice experience using the app.
In general, don't do this.
Please note that even a two-monitor setup, in which both monitors are landscape, can be non-trivial:
The geometry may be non-trivial even if both monitors are the same size:
Per MSDN:
Positioning Objects on Multiple Display Monitors
A window or menu that is on more than one monitor causes visual disruption for a viewer. To minimize this problem, the system displays menus and new and maximized windows on one monitor.
So, if you want the TForm window to stretch across the whole desktop, using WindowState=wsMaximize is not the way to go, as it will only work on the single monitor that the Form is being mostly displayed in.
To do what you ask, you will have to get the rectangle of the Virtual Screen from GetSystemMetrics() (or Vcl.Forms.TScreen), and then set the Form's Left/Top/Width/Height accordingly, eg:
if Screen.MonitorCount > 1 then
begin
Form.WindowState := wsNormal;
Form.Left := Screen.DesktopLeft;
Form.Top := Screen.DesktopTop;
Form.Width := Screen.DesktopWidth;
Form.Height := Screen.DesktopHeight;
// or:
Form.SetBounds(Screen.DesktopLeft, Screen.DesktopTop, Screen.DesktopWidth, Screen.DesktopHeight);
// or:
Form.BoundsRect := Screen.DesktopRect;
end else
begin
Form.WindowState := wsMaximized;
end;
This is not standard behaviour for a Windows application. Also note that as the desktop can have multiple monitors which do not need to be aligned so the desktop may not be a rectangle - which means that the bounding rectangle for the desktop may contain parts which are not visible.
If you want to do this you can use the Windows function GetDesktopWindow to get the desktop window, then get its size, and then set the size of the form to that.
procedure TMyForm.GoLarge();
var
rctDesktop: TRect;
hDT: HWND;
begin
hDT:=GetDesktopWindow();
if(hDT<>0) then
begin
GetWindowRect(hDT, rctDesktop);
Self.SetBounds(rctDesktop.Left, rctDesktop.Top, rctDesktop.Width, rctDesktop.Height);
end;
end;
This makes no allowance for the task bar or anything else which is using some of the desktop space.

Changing picture of tool button at runtime does not work anymore

I have a tool bar and I am using the following procedure to change the color of a rectangle in one of the tool buttons. The ColorDepth of the ImageList is cl24Bit and the DrawingStyle is dsTransparent. The procedure works fine.
procedure TANewMain.BtReplaceHighOnClick(Sender: TObject);
var
ABitmap: TBitmap;
ARect: TRect;
begin
ABitmap := TBitmap.Create;
try
ImgList.GetBitmap(1, ABitmap);
ABitmap.Canvas.Brush.Color := ColorToRGB(clRed); // S04
ABitmap.Canvas.Pen.Color := ColorToRGB(clBlue);
ARect := Rect(5, 1, 11, 15);
ABitmap.Canvas.Rectangle(ARect);
ImgList.ReplaceMasked(1, ABitmap, clWhite);
finally
ABitmap.Free;
end;
end;
If I add the program to the repository for reuse it works fine. However, if I start a new program from scratch and use the exact same procedure, I get a white button. I made sure that the properties for the image list and the tool bar are the same in both programs. The program that works was written some time ago. Could the problem have anything to do with Windows updates? I am using Windows 10 and Delphi 10.
There are two solutions to your problem.
1) Disabling themeing of your application
Disable by unticking the 'Enable Runtime themes' checkbox in 'Project - Options - Application'.
The downside of this is that the application looks as developed for Windows 95.
2) Change following properties of the ImageList
ColorDepth: cdDeviceDependent
DrawingStyle: dsNormal
ImageType: itMask
The result looks like this on Windows 10 (and with respect to the toolbuttons, the same also on Windows 7):
I modified your code to act as a toggle for the buttons, therefore two buttons have the red rectangle.
The numbers are simply 64 x 64 pixel bitmaps with black text on white background.
Caveat: The principle of copying - modifying - copy back repeatedly might lead to reduced quality of the images. A better way could be to have two imagelists, one with the original images and one with the rectangle readily drawn.
Having said that, it appears that the purpose of the rectangle is to indicate some kind of 'active' state. That can be achived also with Down property of the buttons.

Replace the icon of a VCL form with the icon of another .exe file at runtime?

With Delphi XE7, I want to change the icon of the current VCL form (not of the application) at runtime. So I tried this code:
procedure TForm1.LoadExeIcon(const AExeFileName: string);
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Icon.Handle := ExtractIcon(HInstance, PWideChar(AExeFileName), 0);
Self.Icon.Assign(Icon);
finally
Icon.Free;
end;
end;
The changed icon should then be visible on the left top corner of the window (small image format) and in the taskbar (large image format).
It works, but there is a small problem: The new small icon in the top left corner of the window looks blurred, supposedly because the large image from the exe file gets stretched to the smaller size.
This is how the small image looks in the window of the original exe program:
And this is how the small image looks in the test program after replacement:
The large icon in the taskbar looks perfect.
So how can I make the small icon look nice like in the original exe file?
EDIT:
I followed David's advice and here is the working solution. To make the cake really sweet I added an overlay icon to the taskbar icon:
procedure TForm1.LoadExeIcon(const AExeFileName: string);
// Load Large and Small Icon from Exe file and assign them to the Form Icon
// Add Overlay to Taskbar Icon
var
LIcon: HICON;
SIcon: HICON;
OLIcon: TIcon;
NumberOfIconsInExeFile: Integer;
begin
NumberOfIconsInExeFile := ExtractIconEx(PWideChar(AExeFileName), -1, LIcon, SIcon, 0);
if NumberOfIconsInExeFile > 0 then // if there are any icons in the exe file
begin
ExtractIconEx(PWideChar(AExeFileName), 0, LIcon, SIcon, 1);
SendMessage(Form1.Handle, WM_SETICON, 1, LIcon);
SendMessage(Form1.Handle, WM_SETICON, 0, SIcon);
end;
// apply an overlay icon to the taskbar icon with new TTaskbar component:
OLIcon := TIcon.Create;
try
ilTest.GetIcon(0, OLIcon);
Taskbar1.OverlayIcon.Assign(OLIcon);
Taskbar1.OverlayHint := 'My Hint'; // does not work?
Taskbar1.ApplyOverlayChanges;
finally
OLIcon.Free;
end;
end;
Call ExtractIconEx to extract both large and small icons.
However, do be warned that the VCL has a design flaw, introduced in Delphi 1, and does not allow you to set both small and large icons. You are, in my view, better off ignoring the Icon property and sending WM_SETICON manually. Once for each icon size.
Try remove Icon.Free; , if it works it means that Icon.Free; also invalidate the assigned Icon Handle

How to make a form align to the edge of the screen like the taskbar?

I'm building a "dashboard" application which is always visible along any edge of any given monitor, even when other applications are maximized. I don't necessarily need it "always on top" (although I will) but I need to make it a part of the screen as my own desktop toolbar, like the Windows Taskbar is. Even when applications are maximized, the windows are inside of this area, making this window always visible (and the desktop area smaller).
How can I make my application's main form align to the edge of a screen like this?
PS - I don't need an answer to all the extra gritty handling, such as screen resolution changes... I just need to know how to make it aligned as "part of the screen" in the first place.
You're looking for Application Desktop Toolbars, which is what the Windows task bar uses internally. It involves creating a window with specific styles, setting it up correctly, and then communicating with it using SHAppBarMessage.
It can get pretty complex, but there are some free components available with source (one at Torry, or another at DelphiPages) that have the basic shell to get you started.
An example from the AppBar.pas unit of the second link (which, according to the link's text, is freeware with source - I've used it to create an app launcher task bar, complete with buttons with application icons and descriptions read from .lnk files):
type
TAppBarMessage = (abmNew, abmRemove, abmQueryPos, abmSetPos, abmGetState,
abmGetTaskBarPos, abmActivate, abmGetAutoHideBar,
abmSetAutoHideBar, abmWindowPosChanged);
TAppBarEdge = (abeLeft, abeTop, abeRight, abeBottom, abeUnknown, abeFloat);
...
function TAppBar.AppBarMessage(abMessage: TAppBarMessage;
abEdge: TAppBarEdge; lParam: LPARAM; bRect: Boolean; var rc: TRect): UINT;
var
abd: TAppBarData;
begin
// Initialize an APPBARDATA structure
abd.cbSize := sizeof(abd);
abd.hWnd := Handle;
abd.uCallbackMessage := WM_APPBARNOTIFY;
abd.uEdge := Ord(abEdge);
if bRect then
abd.rc := rc
else
abd.rc := Rect(0, 0, 0, 0);
abd.lParam := lParam;
Result := SHAppBarMessage(Ord(abMessage), abd);
// If the caller passed a rectangle, return the updated rectangle
if bRect then
rc := abd.rc;
end;
If nothing else, you can determine this information manually. Have a look at the global Screen object in the Forms unit for information on the current resolution. (Make sure to check the MonitorCount and Monitors properties.)
Between that and a bit of basic arithmetic, it shouldn't be too hard to set up the form to align to the edge of a monitor.

Delphi - Populate an imagelist with icons at runtime 'destroys' transparency

I've spended hours for this (simple) one and don't find a solution :/
I'm using D7 and the TImageList. The ImageList is assigned to a toolbar.
When I populate the ImageList at designtime, the icons (with partial transparency) are looking fine.
But I need to populate it at runtime, and when I do this the icons are looking pretty shitty - complete loose of the partial transparency.
I just tried to load the icons from a .res file - with the same result.
I've tried third party image lists also without success.
I have no clue what I could do :/
Thanks 2 all ;)
edit:
To be honest I dont know exactly whats going on. Alpha blending is the correkt term...
Here are 2 screenies:
Icon added at designtime:
(source: shs-it.de)
Icon added at runtime:
(source: shs-it.de)
Your comment that alpha blending is not supported just brought the solution:
I've edited the image in an editor and removed the "alpha blended" pixels - and now it looks fine.
But its still strange that the icons look other when added at runtime instead of designtime. If you (or somebody else ;) can explain it, I would be happy ;)
thanks for you support!
To support alpha transparency, you need to create the image list and populate it at runtime:
function AddIconFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Icon.LoadFromResourceID(HInstance, ResID);
Result := ImageList.AddIcon(Icon);
finally
Icon.Free;
end;
end;
function AddPngFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Png: TPngGraphic;
ResStream: TStream;
Bitmap: TBitmap;
begin
ResStream := nil;
Png := nil;
Bitmap := nil;
try
ResStream := TResourceStream.CreateFromID(HInstance, ResID, RT_RCDATA);
Png := TPNGGraphic.Create;
Png.LoadFromStream(ResStream);
FreeAndNil(ResStream);
Bitmap := TBitmap.Create;
Bitmap.Assign(Png);
FreeAndNil(Png);
Result := ImageList.Add(Bitmap, nil);
finally
Bitmap.Free;
ResStream.Free;
Png.Free;
end;
end;
// this could be e.g. in the form's or datamodule's OnCreate event
begin
// create the imagelist
ImageList := TImageList.Create(Self);
ImageList.Name := 'ImageList';
ImageList.DrawingStyle := dsTransparent;
ImageList.Handle := ImageList_Create(ImageList.Width, ImageList.Height, ILC_COLOR32 or ILC_MASK, 0, ImageList.AllocBy);
// populate the imagelist with png images from resources
AddPngFromResource(ImageList, ...);
// or icons
AddIconFromResource(ImageList, ...);
end;
I had the exact same problems a couple of years ago. It's a Delphi problem. I ended up putting the images in the list at design time, even though I really didn't want to. I also had to use a DevExpress image list to get the best results and to use 32 bit color images.
As Jeremy said this is indeed a Delphi limitation.
One work around I've used for images that I was putting onto buttons (PNGs with alpha transparency in my case) is to store the PNGs as resources, and at run time paint them onto a button sized bitmap filled with clBtnFace. The bitmap was then used as the control's glyph.
Delphi's built in support for icons with alpha masks is very limited, however there's an excellent icon library kicon which may help.

Resources