unit woTaskbarMenu;

{
  TaskbarMenu
  -----------------------------------------------------------------------------
  TaskbarMenu for Borland Delphi
    Copyright 2008 by Benedikt Loepp
    http://www.webocton.de,
    benedikt@webocton.de

  Description:
    Implements an Taskbar-System-Menu that looks like the standard-system-menu
    from MS Windows and doesn't have the disadvantages of the system-menu which
    Delphi-applications have.

  Usage:
    Create an instance of this component in your main-form of your application.
    On exit free this component to do the unhooking of the taskbar-entry.
    In the Appliction.OnMessage-event you must handle the parameters.

  Example:
    procedure TForm1.FormCreate;
    begin
      FTaskbarMenu:=TTaskbarMenu.Create(Application.Handle, Self);
      Application.OnMessage:=ApplicationMessage;
    end;

    procedure TForm1.FormDestroy;
    begin
      FreeAndNil(FTaskbarMenu);
    end;

    procedure TForm1.ApplicationMessage(var Msg: tagMSG; var Handled: Boolean);
    begin
        if (Msg.message=WM_SYSCOMMAND) then
        begin
            case Msg.wParam of
                SC_MOVE_OWN:
                begin
                    SendMessage(Handle, WM_SYSCOMMAND, SC_MOVE, 0);
                    Handled:=TRUE;
                end;
                SC_RESTORE_OWN:
                begin
                    if (IsIconic(Application.Handle)) then
                        Application.Restore
                    else if (WindowState=wsMaximized) then
                        WindowState:=wsNormal;

                    Handled:=TRUE;
                end;
                SC_MAXIMIZE_OWN:
                begin
                    if (IsIconic(Application.Handle)) then
                        Application.Restore;

                    WindowState:=wsMaximized;

                    Handled:=TRUE;
                end;
                SC_SIZE_OWN:
                begin
                    SendMessage(Handle, WM_SYSCOMMAND, SC_SIZE, 0);
                    Handled:=TRUE;
                end;
            end;
        end;
    end;
  -----------------------------------------------------------------------------
}

interface

uses Windows,
    Messages,
    Classes, Forms;

const
    SC_SIZE_OWN=WM_USER+71;
    SC_MAXIMIZE_OWN=WM_USER+72;
    SC_MOVE_OWN=WM_USER+73;
    SC_RESTORE_OWN=WM_USER+74;
    WM_TASKBARMENU=$0313;

type
    TTaskbarMenu=class(TObject)
    private
        OldWndProc: Pointer;
        NewWndProc: Pointer;
        TaskbarMenu: HWND;
        SysMenu: HWND;
        AppHandle: HWND;
        MainForm: TForm;
        procedure AppWndProc(var Msg: TMessage);
        procedure Hook;
        procedure Unhook;
        procedure CreateTaskbarMenu;

    public
        constructor Create(AppHandle: HWND; MainForm: TForm);
        destructor Destroy; override;
    end;

implementation

constructor TTaskbarMenu.Create(AppHandle: HWND; MainForm: TForm);
begin
    inherited Create;

    Self.AppHandle:=AppHandle;
    Self.MainForm:=MainForm;

    TaskbarMenu:=GetSystemMenu(AppHandle, FALSE);
    SysMenu:=GetSystemMenu(MainForm.Handle, FALSE);

    Hook;
    
    CreateTaskbarMenu;
end;

destructor TTaskbarMenu.Destroy;
begin
    Unhook;

    inherited;
end;

procedure TTaskbarMenu.CreateTaskbarMenu;
var
    Title: String;
    i: Integer;

    ID: Cardinal;

    MenuItemInfo: TMenuItemInfo;
begin
    RemoveMenu(TaskbarMenu, SC_RESTORE, MF_BYCOMMAND);

    for i:=0 to GetMenuItemCount(SysMenu) do
    begin
        ID:=GetMenuItemID(SysMenu, i);

        case ID of
            SC_MOVE, SC_RESTORE, SC_MAXIMIZE, SC_SIZE:
                begin
                    SetLength(Title, GetMenuString(SysMenu, i, nil, 0, MF_BYPOSITION)+1);

                    GetMenuString(SysMenu, i, PChar(Title), Length(Title), MF_BYPOSITION);
                end;
        end;

        case ID of
            SC_MOVE:
                begin
                    InsertMenu(TaskbarMenu, i, MF_STRING or MF_BYPOSITION, SC_MOVE_OWN, PChar(Title));
                end;
            SC_RESTORE:
                begin
                    InsertMenu(TaskbarMenu, i, MF_STRING or MF_BYPOSITION, SC_RESTORE_OWN, PChar(Title));

                    FillChar(MenuItemInfo, SizeOf(TMenuItemInfo), #0);
                    MenuItemInfo.cbSize:=SizeOf(TMenuItemInfo);
                    MenuItemInfo.fMask:=MIIM_ID or MIIM_STATE or MIIM_BITMAP;
                    MenuItemInfo.wID:=SC_RESTORE_OWN;
                    MenuItemInfo.hbmpItem:=HBMMENU_POPUP_RESTORE;
                    SetMenuItemInfo(TaskbarMenu, i, TRUE, MenuItemInfo);
                end;
            SC_MAXIMIZE:
                begin
                    InsertMenu(TaskbarMenu, i, MF_STRING or MF_BYPOSITION, SC_MAXIMIZE_OWN, PChar(Title));

                    FillChar(MenuItemInfo, SizeOf(TMenuItemInfo), #0);
                    MenuItemInfo.cbSize:=SizeOf(TMenuItemInfo);
                    MenuItemInfo.fMask:=MIIM_ID or MIIM_STATE or MIIM_BITMAP;
                    MenuItemInfo.wId:=SC_MAXIMIZE_OWN;
                    MenuItemInfo.hbmpItem:=HBMMENU_POPUP_MAXIMIZE;
                    SetMenuItemInfo(TaskbarMenu, i, TRUE, MenuItemInfo);
                end;
            SC_SIZE:
                begin
                    InsertMenu(TaskbarMenu, i, MF_STRING or MF_BYPOSITION, SC_SIZE_OWN, PChar(Title));
                end;
        end;
    end;
end;

procedure TTaskbarMenu.AppWndProc(var Msg: TMessage);
begin
    if (Msg.Msg=WM_TASKBARMENU) then
    begin
        //Set Move, Size and Restore
        if ((MainForm.WindowState=wsMaximized)or(IsIconic(AppHandle))) then
        begin
            EnableMenuItem(TaskbarMenu, SC_MOVE_OWN, MF_BYCOMMAND or MF_GRAYED);
            EnableMenuItem(TaskbarMenu, SC_SIZE_OWN, MF_BYCOMMAND or MF_GRAYED);
            EnableMenuItem(TaskbarMenu, SC_RESTORE_OWN, MF_BYCOMMAND or MF_ENABLED);
        end
        else
        begin
            EnableMenuItem(TaskbarMenu, SC_MOVE_OWN, MF_BYCOMMAND or MF_ENABLED);
            EnableMenuItem(TaskbarMenu, SC_SIZE_OWN, MF_BYCOMMAND or MF_ENABLED);
            EnableMenuItem(TaskbarMenu, SC_RESTORE_OWN, MF_BYCOMMAND or MF_GRAYED);
        end;

        //Set Maximize
        if ((MainForm.WindowState=wsNormal)or(IsIconic(AppHandle))) then
            EnableMenuItem(TaskbarMenu, SC_MAXIMIZE_OWN, MF_BYCOMMAND or MF_ENABLED)
        else
            EnableMenuItem(TaskbarMenu, SC_MAXIMIZE_OWN, MF_BYCOMMAND or MF_GRAYED);

        //Set Default-Item
        if (IsIconic(AppHandle)) then
            SetMenuDefaultItem(TaskbarMenu, SC_RESTORE_OWN, 0)
        else
            SetMenuDefaultItem(TaskbarMenu, SC_CLOSE, 0);
    end;

    Msg.Result:=CallWindowProc(OldWndProc, AppHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;

procedure TTaskbarMenu.Hook;
begin
    //Hook
    OldWndProc:=Pointer(GetWindowLong(AppHandle, GWL_WNDPROC));
    NewWndProc:=Classes.MakeObjectInstance(AppWndProc);

    if not(csDesigning in MainForm.ComponentState) then
        SetWindowLong(AppHandle, GWL_WNDPROC, LongInt(NewWndProc));
end;

procedure TTaskbarMenu.UnHook;
begin
    //Unhook
    SetWindowLong(AppHandle, GWL_WNDPROC, LongInt(OldWndProc));

    if Assigned(NewWndProc) then
        Classes.FreeObjectInstance(NewWndProc);

    NewWndProc:=nil;
end;

end.

