2

I have a few old apps I've written (in Delphi) which for various reasons use a system tray icon. Most are using AppControls TacTrayIcon or some other similar component.

Here's my question: How does one control the position of a tray icon? (i.e. where it is, say, relative to the system time -- 1st position/"slot", 2nd position/"slot", etc). I recall seeing a demo (C#, if memory serves) that allowed the user to "shift icon to the left" and "shift icon to the right", but don't recall how it was done.

I'd like to allow the user to select what position they want to icon to appear in, for Windows 2000 - Windows 7. (I understand Windows 7 handles system tray stuff a little differently, but haven't tested that out yet).

Thanks for any and all help.

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
Jamo
  • 3,238
  • 6
  • 40
  • 66
  • 15
    Every time you want to do something and can't find a solution in the Windows API you should probably ask yourself whether there's a good reason for that, like it's simply none of your business. It's not an exact match, but see http://blogs.msdn.com/oldnewthing/archive/2006/11/01/922449.aspx for the general idea. Maybe you should reconsider your various reasons for having a tray icon, at all. And if the reason is indeed sound, just come up with a good icon, and leave its position alone. – mghie Dec 28 '09 at 22:50
  • @mghie - It's hard not to infer from both the tone of your comment and link you provide, that you're implying I'm trying to hijack or intrude into some user-controlled setting. That's not the case -- I'm trying to help give the user some control of the icon position because they have *requested* it, Windows (XP, anyway) doesn't facilitate it directly, and I've seen it accomplished usefully elsewhere. – Jamo Dec 29 '09 at 08:07
  • @Jamo: I'm sorry, I didn't mean to imply anything. The Windows API is huge and often has multiple ways to achieve the same thing; if there's no ready way to achieve something, then it's more often than not on purpose. And you would of course not intrude on some user-controlled setting - your customer requests control over something that isn't meant to be controlled. – mghie Dec 29 '09 at 08:50
  • It must be possible, taskbarshuffle allows you to move these icons – BlueRaja - Danny Pflughoeft May 27 '11 at 22:10

2 Answers2

10

There is no documented or supported way for programs to control the positions of their shell notification icons. There's not even anything guaranteeing they will appear at all, or if they do appear, that they will appear anywhere near the clock, such that your positioning instructions would make any sense.

(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. It was TraySaver, by Mike Lin. The source is available if you wish to see how his hack worked.)

The icon's position is not under your control. My advice to you is to not try to make it your program's responsibility, especially if nobody has actually requested such functionality from your program in the first place. If people want to control your program's icon location, they probably want to control other programs' icon locations, in which case the problem is bigger than you anyway.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • 2
    Thanks for a legitimate answer, rather than just judgement/condemnation of the question -- its refreshing and appreciated. This is actually a feature users are requesting, though I agree w/you re: the problem being bigger than just my app. I'll take a look at the TraySaver code. Thanks again. – Jamo Dec 29 '09 at 07:56
6

Accessing and modifying the shell notification area is hackish but possible. You first need to find the top level window:

var
  Wnd: HWND;
begin
  Wnd := FindWindow('Shell_TrayWnd', nil);
  if IsWindow(Wnd) then
    EnumChildWindows(Wnd, @FindTrayWnd, 0);
end;

then enumerate its children to find the tray notification area:

function FindTrayWnd(AWnd: HWND; AParam: LPARAM): BOOL; stdcall;
var
  ClassName: string;
begin
  SetLength(ClassName, 64);
  SetLength(ClassName, GetClassName(AWnd, PChar(ClassName), 64));
  Result := True;
  if AnsiCompareText(ClassName, 'TrayNotifyWnd') = 0 then begin
    EnumChildWindows(AWnd, @FindToolbar, 0);
    Result := False;
  end;
end;

then enumerate its children to find the standard Windows toolbar with the notification icons. Windows messages are used to get or set toolbar properties. Since the toolbar lives in another process you need to employ ReadProcessMemory() and WriteProcessMemory() for all messages that involve a buffer of some sort (like getting the button text or button info):

function FindToolbar(AWnd: HWND; AParam: LPARAM): BOOL; stdcall;
const
  VMFLAGS = PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE;
var
  ClassName: string;
  i, ButtonCount: integer;
  ProcessId, BytesRead: Cardinal;
  ProcessHandle: THandle;
  ExplorerButtonInfo: PTBButton;
  ButtonInfo: array of TTBButton;
begin
  SetLength(ClassName, 64);
  SetLength(ClassName, GetClassName(AWnd, PChar(ClassName), 64));
  if AnsiCompareText(ClassName, 'ToolbarWindow32') = 0 then begin
    GetWindowThreadProcessId(AWnd, @ProcessId);
    ProcessHandle := OpenProcess(VMFLAGS, FALSE, ProcessId);
    ExplorerButtonInfo := VirtualAllocEx(ProcessHandle, nil, SizeOf(TTBButton),
      MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
    if ExplorerButtonInfo <> nil then try
      ButtonCount := SendMessage(AWnd, TB_BUTTONCOUNT, 0, 0);
      SetLength(ButtonInfo, ButtonCount);
      for i := 0 to ButtonCount - 1 do begin
        SendMessage(AWnd, TB_GETBUTTON, i, LPARAM(ExplorerButtonInfo));
        ReadProcessMemory(ProcessHandle, ExplorerButtonInfo, @ButtonInfo[i],
          SizeOf(TTBButton), BytesRead);
      end;
      // manipulate the button info, use WriteProcessMemory() and SendMessage()
      // to repopulate the toolbar

    finally
      VirtualFreeEx(ProcessId, ExplorerButtonInfo, SizeOf(TTBButton),
        MEM_RELEASE);
    end;
    Result := False;
  end else
    Result := True;
end;

You should be able to identify the button of your notification icon via its name, then delete that button, then insert it at the desired position. All error handling omitted, but this should get you started.

mghie
  • 32,028
  • 6
  • 87
  • 129
  • mghie, thanks so much for taking the time to post this, and also for your kind and clarifying commments above -- I really appreciate both. (Very much so!). As well, your point is noted re: "your customer requests control over something that isn't meant to be controlled." It seems strange to me that this isn't already "controllable" by the user directly in Windows, even if not programmatically by applications, like Chen refers to in the article you linked to. – Jamo Dec 30 '09 at 01:47