How can I get the tooltips of notification-area icons? - delphi

I can enumerate the applications (handle,pid,path) with icons in the notification area, and I can control the position of the icons, but I can't get the tooltip.
How can I enumerate systray icons including the tooltips?

The shell provides no facility for inspecting notification icons that don't belong to your program. (And it provides no way of enumerating even the icons that do belong to your program; you're expected to already know about those.)
I used to use a program that hijacked some or all of the icons and optionally displayed them in its own window instead of in the area near the clock, so it must have been able to get a list of all the icons. It was TraySaver, by Mike Lin. The source is available if you wish to see how his hack worked.
You can also take a look at the answers to a previous question that asked about controlling the position of icons in the notification area.

You should take a look at the madKernal package of madshis component collection. It has some interfaces for working with trayicons. Beware, though:
With madKernel you can manage tray icons (see API "Shell_NotifyIcon") of any application. This kind of functionality is totally undocumented, but works well from win95 to winXP.
The ITrayIcon-interface has properties for hint, icon, position and more.

Here is my method tested with windows xp and delphi 2010 if you are using a version of delphi wich doesn't support unicode make shure you convert the strings read to ansi
uses CommCtrl;
function TForm1.GetIconsCount: Integer;
begin
Result := SendMessage(FindTrayToolbar, TB_BUTTONCOUNT, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ListTips;
end;
function TForm1.FindTrayToolbar: HWND;
begin
Result := FindWindow('Shell_TrayWND', nil);
Result := FindWindowEx(Result, 0, 'TrayNotifyWnd', nil);
Result := FindWindowEx(Result, 0, 'SysPager', nil);
Result := FindWindowEx(Result, 0, 'ToolbarWindow32', nil);
end;
procedure TForm1.ListTips;
var
dwTray: DWORD;
wndTray: HWND;
hTray: THandle;
remoteTray: Pointer;
tdata: TTBBUTTON;
i: Integer;
btsread:DWORD;
str:Pchar;
begin
wndTray := FindTrayToolbar;
GetWindowThreadProcessId(wndTray, #dwTray);
hTray := OpenProcess(PROCESS_ALL_ACCESS, false, dwTray);
if hTray <> 0 then
begin
remoteTray := VirtualAllocEx(hTray, nil, Sizeof(tdata), MEM_COMMIT,
PAGE_READWRITE);
for i := 0 to GetIconsCount - 1 do
begin
SendMessage(FindTrayToolbar,TB_GETBUTTON,wparam(i),lparam(remotetray));
ReadProcessMemory(hTray,remotetray,#tdata,sizeof(tdata),btsread);
GetMem(str,255);
ReadProcessMemory(hTray,Ptr(tdata.iString),str,255,btsread);
ListBox1.Items.Add(str);
end;
end
else ShowMessage('Could not locate tray icons');
end;
end.

Related

How to create a non visual component without any icon on the form?

I would like to create a non visual component (like TTimer for example) that I can drop on the form and that I can set up directly from the Object Inspector, but I don't want to see its icon on the form (it'd just obstruct anything). For example TFloatAnimation works like this but I don't understand how.
The GExperts library (http://www.gexperts.org/) has a plug-in which can toggle the visibility
of non-visual components on a form, and it is apparently not Delphi-version-specific but it is
not exactly trivial.
The method which does this is
procedure THideNonVisualCompsExpert.ToggleNonVisualVisible(Form: TCustomForm);
const
NonVisualClassName = 'TContainer';
var
VisibleState: Boolean;
FormHandle: THandle;
CompHandle: THandle;
WindowClass: string;
FirstCompFound: Boolean;
WinControl: TWinControl;
ChildControl: TWinControl;
i: Integer;
begin
Assert(Assigned(Form));
Assert(Form.Handle > 0);
FirstCompFound := False;
WinControl := Form;
if InheritsFromClass(WinControl.ClassType, 'TWinControlForm') then
begin
for i := WinControl.ComponentCount - 1 downto 0 do
begin
if WinControl.Controls[i] is TWinControl then
begin
ChildControl := WinControl.Controls[i] as TWinControl;
if InheritsFromClass(ChildControl.ClassType, 'TCustomFrame') then
begin
WinControl := ChildControl;
Break;
end;
end;
end;
end;
FormHandle := GetWindow(WinControl.Handle, GW_CHILD);
CompHandle := GetWindow(FormHandle, GW_HWNDLAST);
VisibleState := False;
GxOtaClearSelectionOnCurrentForm;
while (CompHandle <> 0) do
begin
WindowClass := GetWindowClassName(CompHandle);
if AnsiSameText(WindowClass, NonVisualClassName) then
begin
if not FirstCompFound then
begin
VisibleState := not IsWindowVisible(CompHandle);
FirstCompFound := True;
end;
if VisibleState then
ShowWindow(CompHandle, SW_SHOW)
else
ShowWindow(CompHandle, SW_HIDE);
end;
CompHandle := GetWindow(CompHandle, GW_HWNDPREV);
end;
end;
in the unit GX_HideNonVisualComps.Pas.
As written, it toggles the visibility of all the non-visual components on the
target form, but looking at the code of the ToggleNonVisualVisible method it looks like it
ought to be possible (but I have not tried) to adapt it to operate on a selected component class and
force instances of the class to a non-visible state. Once you have done that, you would probably
need to experiment with how and when to invoke the method at design-time; if I was doing it, I would probably start
with somewhere like the target component's Loaded method.
(I would feel more comfortable posting this "answer" as a comment but obviously it would be too long)
I have thought about this. A Non Visual Component does not do any painting, in a Windows environment (like the IDE) it has no Window, and therefore cannot influence how the IDE chooses to render it.
One approach would be to derive from TWinControl, making your component a Visual Component, and then to ensure that it is not drawn. Try setting the positioning properties to be non-published, and when you are parented, always set your position outside the parent window. This means that your control is always clipped and never painted.
I haven't tried this, but I can see no reason why it wouldn't work.
You can also use this approach to have an apparently non visual component that renders information in the IDE at designtime, but not at runtime.

FMX on iOS: how to perform partial invalidation?

When I invalidate one control on a form, then the Paint method is called for ALL controls on that form. This happens on iOS, while on Windows the local invalidation works and only the requested control is being painted.
To verify this, I made a small test program with just two TPaintBox on a form and assigned these Click and Paint methods to them:
procedure TForm1.PaintBox1Click(Sender: TObject);
var lPaintBox: TPaintBox;
begin
lPaintBox := TPaintBox(Sender);
lPaintBox.InvalidateRect(lPaintBox.LocalRect);
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var lPaintBox: TPaintBox;
begin
lPaintBox := TPaintBox(Sender);
lPaintBox.Canvas.Fill.Color := claBlack;
lPaintBox.Canvas.FillText(RectF(0,0,50,50),'Paint count = '+inttostr(lPaintBox.Tag),true, 1, [], ttextAlign.Center);
lPaintBox.Tag := lPaintBox.Tag +1;
end;
No matter which one of the paintboxes I touch, the other one is painted too. So on iOS they always show the same number.
Is there a way to avoid this?
I use Berlin update 2 with XCode 8.2
UPDATE
Investigating this further I found that InvalidateRect calls this:
procedure TCustomForm.AddUpdateRect(R: TRectF);
begin
...
if (Canvas <> nil) and not (TCanvasStyle.SupportClipRects in Canvas.GetCanvasStyle) then
InvalidateRect(RectF(0, 0, FCanvas.Width, FCanvas.Height))
else
InvalidateRect(R);
end;
Which means if the Canvas does not support ClipRects then the full form is invalidated. Since mobile platforms always use TCanvasGPU, I checked what it supports:
class function TCanvasGpu.GetCanvasStyle: TCanvasStyles;
begin
Result := [TCanvasStyle.NeedGPUSurface];
end;
In other words: mobile platforms does not (currently) support local invalidation and always repaints everything on the form every time any control wants to invalidate anything.
This is unfortunate when there are many and/or complex controls on a form, so could there be a workaround?

FastReport 4 and VCL Styles bugs

Some background info. I work at a very small company who has recently upgraded Delphi from version 6 (!!!) to Rad Studio XE5 and things have certainly changed a lot in 10+ years. Most things seems to have been improved in the IDE and framework, but we're having big problems with the new VCL Styles feature. It's just very buggy and not up to par with the quality we were used to from Borland back in the day. We have done lots of tweaks and work arounds to get things working but one issue is really bugging me at the moment and it has to do with the preview form in FastReport 4.
The toolbar gets a white border around it.
Controls in the print dialog and others are misaligned or wrongly positioned
We really want to use VCL Styles to give our software a new fresh look, so we hope there is a solution to these problems.
Steps to reproduce the issues:
Create a new VCL Forms Application
Check a VCL Style in Project > Options > Application > Appearance, e.g. Sapphire Kamri.
Add a TfrxReport report Component to the form
Double click the component frxReport1 and add a Page Header band just to have some content
Add a TButton and in OnClick event, call frxReport1.ShowReport();
Run the program and click on the button. In the preview form you now see that the toolbar is surrounded by a white border which looks weird.
Click the leftmost print button to bring up the print dialog and you can see how the group boxes and cancel button is positioned outside of the client area.
Do you have any solutions or suggestions to solve the issues?
Edit: RRUZ gave a good answer, but there were some side effects to his solution to problem #1 so I decided to simplify the code and just paint the border around the toolbar manually. Like this:
procedure TToolBarStyleHookEx.PaintNC(Canvas: TCanvas);
begin
if TToolBar(Control).BorderWidth>0 then
begin
Canvas.Pen.Width := 4;
Canvas.Pen.Color := StyleServices.GetStyleColor(scWindow);
Canvas.Brush.Style := bsClear;
Canvas.Rectangle(2,2,Control.Width-2,Control.Height-1);
end;
inherited;
end;
Effectively both issues it seems VCL Styles bugs.
1) Q: The toolbar gets a white border around it.
A: The TToolBarStyleHook Style hook in not handling the BorderWidth property. so you must create a new style hook and override the PaintNC to overcome this issue.
type
TToolBarStyleHookEx = class(TToolBarStyleHook)
protected
procedure PaintNC(Canvas: TCanvas); override;
end;
{ TToolBarStyleHookEx }
procedure TToolBarStyleHookEx.PaintNC(Canvas: TCanvas);
var
Details: TThemedElementDetails;
LStyle: TCustomStyleServices;
R: TRect;
begin
if TToolBar(Control).BorderWidth>0 then
begin
LStyle := StyleServices;
R := Rect(0, 0, Control.Width, Control.Height);
Details.Element := teToolBar;
Details.Part := 0;
Details.State := 0;
if LStyle.HasTransparentParts(Details) then
LStyle.DrawParentBackground(Handle, Canvas.Handle, Details, False);
LStyle.DrawElement(Canvas.Handle, Details, R);
end;
inherited;
end;
and register like so
initialization
TCustomStyleEngine.RegisterStyleHook(TToolBar, TToolBarStyleHookEx);
2) Q : Controls in the print dialog and others are misaligned or wrongly positioned
A: It seems a issue related with the TFormStyleHook, you had 3 alternatives.
1) you can edit the frxPrintDialog unit and increase the width of the form.
2) you can patch the form style hook.
3) You can change the width of the print dialog in run-time.
Check this code which changes the width of the dialog in run-time using a HCBT_ACTIVATE hook
var
hhk: HHOOK;
function CBT_FUNC(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
const
ClassNameBufferSize = 1024;
var
hWindow: HWND;
RetVal : Integer;
ClassNameBuffer: Array[0..ClassNameBufferSize-1] of Char;
i : integer;
begin
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
if nCode<0 then exit;
case nCode of
HCBT_ACTIVATE:
begin
hWindow := HWND(wParam);
if (hWindow>0) then
begin
RetVal := GetClassName(wParam, ClassNameBuffer, SizeOf(ClassNameBuffer));
if (RetVal>0) and SameText(ClassNameBuffer, 'TfrxPrintDialog') then
for i:= 0 to Screen.FormCount-1 do
if (SameText(Screen.Forms[i].ClassName, 'TfrxPrintDialog')) and (Screen.Forms[i].Width<=563) then
Screen.Forms[i].Width:=Screen.Forms[i].Width+8;
end;
end;
end;
end;
Procedure InitHook();
var
dwThreadID : DWORD;
begin
dwThreadID := GetCurrentThreadId;
hhk := SetWindowsHookEx(WH_CBT, #CBT_FUNC, hInstance, dwThreadID);
if hhk=0 then RaiseLastOSError;
end;
Procedure KillHook();
begin
if (hhk <> 0) then
UnhookWindowsHookEx(hhk);
end;
initialization
InitHook();
finalization
KillHook();
After of apply both fixes this will be the result
Note: please report these issues to the QC page of Embarcadero.

Match TButtonedEdit image to TComboBox default image

My application uses standard TComboBoxes and also TButtonedEdits to produce controls with more complex drop-down panels. I would like the two controls to look the same. In particular, I would like image on my TButtonedEdits to be identical to the image on the TComboBoxes regardless of which current or future operating system the program is run on (that is, assuming that this image is determined by the operating system and not be Delphi).
I assume that I will have to install, at runtime, the resource providing the image to TComboBox into a TImageList to make it available to my TButtonedEdits. How do I locate and extract that resource?
You can use the theme engine to draw the button yourself - try something like this for starters:
uses
Themes;
procedure DrawComboBoxButton(ACanvas: TCanvas; ADown, AMouseInControl: Boolean; const ARect: TRect);
var
ComboElem: TThemedComboBox;
Details: TThemedElementDetails;
begin
if ThemeServices.ThemesEnabled then
begin
if ADown then
ComboElem := tcDropDownButtonPressed
else if AMouseInControl then
ComboElem := tcDropDownButtonHot
else
ComboElem := tcDropDownButtonNormal;
Details := ThemeServices.GetElementDetails(ComboElem);
ThemeServices.DrawElement(ACanvas.Handle, Details, ARect);
end
else
begin
if ADown then
DrawFrameControl(ACanvas.Handle, ARect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX or DFCS_PUSHED)
else
DrawFrameControl(ACanvas.Handle, ARect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX);
end;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
DrawComboBoxButton(PaintBox1.Canvas, False, False, Bounds(0, 0, 20, 20));
DrawComboBoxButton(PaintBox1.Canvas, True, False, Bounds(20, 0, 20, 20));
end;
(adapted from the thread "Windows themes in combobox" in the Embarcadero forums).
Mike Lischke's "Windows XP Theme Explorer" can help you to find the right "Elements" and "Details". And have a look at this SO thread.

Old Delphi hide/show desktop icons method not working under Windows 7 64 Bit

I have a Delphi 2010 app which shows/hides the desktop icons under XP fine. However under my Window 7 test environment (happens to be 64 bit) the icons don't disappear.
Here is the critical code I am using (for the hide):
ShowWindow(FindWindow(nil, 'Program Manager'), SW_HIDE );
I have found I can set the registry:
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
"HideIcons"=dword:00000001
And that works fine if I restart windows (or kill explorer and restart it), however is there a way to get the old code to work and/or tell the desktop to reload using the new registry information without such radical methods.
Thank in advance.
Use SHGetSetSettings function. You're interested in fHideIcons field and corresponding SSF_HIDEICONS flag.
Alternatively, you can use corresponding group policy.
Ok, here is the revised hackish method (sorry Alexander!):
var
DeskHandle : HWND;
...
///////////////////////////////////////////////////////////////////////
// Callback function for EnumWindows
///////////////////////////////////////////////////////////////////////
function MyGetWindow (Handle: HWND; NotUsed: longint): bool; stdcall;
var
hChild : HWND;
begin
if handle <> 0 then
begin
hChild := FindWindowEx(handle, 0, 'SHELLDLL_DefView' ,nil);
if hChild <> 0 then
begin
hChild := FindWindowEx(hChild, 0, 'SysListView32' ,nil);
if hChild <> 0 then
begin
DeskHandle := hChild;
end;
end;
end;
result := TRUE;
end;
procedure ShowDesktopIcons(const Show : boolean) ;
begin
DeskHandle := 0;
EnumWindows(#MyGetWindow, 0);
if DeskHandle <> 0 then
begin
if Show then
begin
ShowWindow(DeskHandle, SW_SHOW );
end
else
begin
ShowWindow(DeskHandle, SW_HIDE );
end;
end;
end;
The issue arises because parent/child relationship between "Progman" and SysListView32 has changed from XP to Vista/Win7 (precisely why you shouldn't use a hack ;-). In addition, applying a theme with multiple pictures under Win7 (my test environment) changes this relationship even further. Therefore the new routine looks through all windows until it finds one with a "SHELLDLL_DefView" and "SysListView32" child set under one. It then returns the handle of SysListView32 in the global variable DeskHandle. Not elegant, not sure to work in future code, but works today.
If anyone can get a SHGetSetSettings version to work, that is definitely the correct way to go, not this junk.
Use 'ProgMan' instead of 'Program Manager'.
Works in Win 7 32 bits (don't have my 64 bits available here).
procedure ShowDesktopIcons(const Visible: Boolean);
var
h: THandle;
begin
h := FindWindow('ProgMan', nil);
if h = 0 then
RaiseLastOSError;
if Visible then
ShowWindow(h, SW_SHOW)
else
ShowWindow(h, SW_HIDE);
end;
procedure TForm1.btnHideClick(Sender: TObject);
begin
ShowDesktopIcons(False);
end;
procedure TForm1.btnShowClick(Sender: TObject);
begin
ShowDesktopIcons(True);
end;

Resources