情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

C#でWIN32API step5 タスクトレイアイコンにフライアウトウィンドウを追加する(その1)

2017/07/22

step1:ウィンドウの表示

step2:タスクトレイアイコンを表示する

step3:タスクトレイアイコンにコンテキストメニューを追加する

step4:タスクトレイアイコンにバルーンチップを表示する

タスクトレイアイコンの話はまだ続きます。今回はタスクトレイアイコンにフライアウトウィンドウを追加してみます。

タスクトレイアイコンが左クリックされるとWM_APPメッセージが発生し、そのlParamの下位にはNIN_SELECTが格納されています。この時、適切な位置にウィンドウを表示するようにします。

適切なウィンドウの範囲の算出は、

  1. RECT構造体を作成。
  2. Shell_NotifyIconGetRect関数を使ってRECT構造体にタスクトレイアイコンの範囲を格納。
  3. POINT構造体にタスクトレイアイコンの中心座標を格納。
  4. SIZE構造体にウィンドウの大きさを格納。
  5. CalculatePopupWindowPosition関数を呼ぶ。

という手順で行います。

以下のプログラムではクライアント領域の大きさからウィンドウの大きさを求めるためにAdjustWindowRectEx関数も使用していますがこれは必須ではありません。

フライアウトウィンドウの範囲が求まったら、後はウィンドウを作成して、前面に表示するだけです。

また、フライアウトのウィンドウプロシージャでは、ウィンドウがアクティブでなくなった時に自分のウィンドウを破棄するようにしています。

注意としては、Shell_NotifyIconGetRect関数とCalculatePopupWindowPosition関数はWindows 7で新たに追加された関数のため、Vista以前の環境では動作しません。Vista以前の環境への対応は別途考えるべきです。

まず、Main関数内でフライアウトウィンドウ用のウィンドウクラスを登録する処理を追加します(以前と同じ部分は省略しました)。

[STAThread]
public static void Main(string[] args)
{
  (省略)

    wndclassex.lpfnWndProc = FlyoutWndProc;
    wndclassex.hIcon = IntPtr.Zero;
    wndclassex.lpszClassName = "anttnFlyout";

    WIN32.Window.RegisterClassEx(ref wndclassex);

  (省略)
}

次に、前回のプログラムのWndProcWM_APPNIN_SELECTを受け取った場合の処理を追加します(前回と同じ部分は省略しました)。

private static IntPtr WndProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam)
{
    switch (uMsg)
    {
    (省略)

        case WIN32.Window.WM_APP:
            switch ((uint)WIN32.Common.LOWORD(lParam))
            {
        (省略)

                case WIN32.NotifyIcon.NIN_SELECT:
                    WIN32.Window.RECT rectWindow = new WIN32.Window.RECT();
                    rectWindow.left = 0;
                    rectWindow.top = 0;
                    rectWindow.right = 256;
                    rectWindow.bottom = 256;
                    WIN32.Window.AdjustWindowRectEx(ref rectWindow, WIN32.Window.WS.WS_POPUP | WIN32.Window.WS.WS_THICKFRAME, false, WIN32.Window.WS_EX.WS_EX_TOOLWINDOW);

                    WIN32.Window.RECT rectNotifyIcon = new WIN32.Window.RECT();
                    WIN32.NotifyIcon.NOTIFYICONIDENTIFIER notifyiconidentifier = new WIN32.NotifyIcon.NOTIFYICONIDENTIFIER();
                    notifyiconidentifier.cbSize = (uint)Marshal.SizeOf(notifyiconidentifier);
                    notifyiconidentifier.hWnd = hWnd;
                    notifyiconidentifier.uID = 0;
                    WIN32.NotifyIcon.Shell_NotifyIconGetRect(ref notifyiconidentifier, out rectNotifyIcon);

                    WIN32.Window.POINT point = new WIN32.Window.POINT();
                    point.x = (rectNotifyIcon.left + rectNotifyIcon.right) / 2;
                    point.y = (rectNotifyIcon.top + rectNotifyIcon.bottom) / 2;

                    WIN32.Window.SIZE size = new WIN32.Window.SIZE();
                    size.cx = rectWindow.right - rectWindow.left;
                    size.cy = rectWindow.bottom - rectWindow.top;

                    WIN32.Window.CalculatePopupWindowPosition(ref point, ref size, WIN32.Menu.TPM.TPM_CENTERALIGN | WIN32.Menu.TPM.TPM_VCENTERALIGN | WIN32.Menu.TPM.TPM_VERTICAL | WIN32.Menu.TPM.TPM_WORKAREA, ref rectNotifyIcon, out rectWindow);

                    IntPtr hFlyoutWnd = WIN32.Window.CreateWindowEx(WIN32.Window.WS_EX.WS_EX_TOOLWINDOW, "anttnFlyout", null, WIN32.Window.WS.WS_POPUP | WIN32.Window.WS.WS_THICKFRAME, rectWindow.left, rectWindow.top, rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top, IntPtr.Zero, IntPtr.Zero, hinstance, IntPtr.Zero);

                    WIN32.Window.ShowWindow(hFlyoutWnd, WIN32.Window.SW.SW_SHOW);
                    WIN32.Window.UpdateWindow(hFlyoutWnd);
                    WIN32.Window.SetForegroundWindow(hFlyoutWnd);

                    break;

        (省略)
            }
            break;
        default:
            return WIN32.Window.DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return IntPtr.Zero;
}

更に、フライアウトのウィンドウプロシージャを追加します。

private static IntPtr FlyoutWndProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam)
{
    switch (uMsg)
    {
        case WIN32.Window.WM_ACTIVATE:
            if (WIN32.Common.LOWORD(wParam) == WIN32.Window.WA_INACTIVE)
                WIN32.Window.DestroyWindow(hWnd);
            break;
        default:
            return WIN32.Window.DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return IntPtr.Zero;
}

こちらが、P/I用のコードです(前回からの追加部分)。

using System;
using System.Runtime.InteropServices;

namespace Anttn.WIN32
{
    public static partial class Window
    {
    (省略)

        [DllImport("user32.dll")]
        public static extern bool AdjustWindowRectEx([In, Out] ref RECT lpRect, WS dwStyle, bool bMenu, WS_EX dwExStyle);

        [DllImport("user32.dll")]
        public static extern bool CalculatePopupWindowPosition([In] ref POINT anchorPoint, [In] ref SIZE windowSize, Menu.TPM flags, [In] ref RECT excludeRect, out RECT popupWindowPosition);

    (省略)

        [StructLayout(LayoutKind.Sequential)]
        public struct SIZE
        {
            public int cx;
            public int cy;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

    (省略)

        [Flags]
        public enum WS_EX : uint
        {
            WS_EX_ACCEPTFILES = 0x00000010,
            WS_EX_APPWINDOW = 0x00040000,
            WS_EX_CLIENTEDGE = 0x00000200,
            WS_EX_COMPOSITED = 0x02000000,
            WS_EX_CONTEXTHELP = 0x00000400,
            WS_EX_CONTROLPARENT = 0x00010000,
            WS_EX_DLGMODALFRAME = 0x00000001,
            WS_EX_LAYERED = 0x00080000,
            WS_EX_LAYOUTRTL = 0x00400000,
            WS_EX_LEFT = 0x00000000,
            WS_EX_LEFTSCROLLBAR = 0x00004000,
            WS_EX_LTRREADING = 0x00000000,
            WS_EX_MDICHILD = 0x00000040,
            WS_EX_NOACTIVATE = 0x08000000,
            WS_EX_NOINHERITLAYOUT = 0x00100000,
            WS_EX_NOPARENTNOTIFY = 0x00000004,
            WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE,
            WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
            WS_EX_RIGHT = 0x00001000,
            WS_EX_RIGHTSCROLLBAR = 0x00000000,
            WS_EX_RTLREADING = 0x00002000,
            WS_EX_STATICEDGE = 0x00020000,
            WS_EX_TOOLWINDOW = 0x00000080,
            WS_EX_TOPMOST = 0x00000008,
            WS_EX_TRANSPARENT = 0x00000020,
            WS_EX_WINDOWEDGE = 0x00000100
        }

    (省略)

        public const int WA_INACTIVE = 0;
        public const int WA_ACTIVE = 1;
        public const int WA_CLICKACTIVE = 2;

    (省略)

        public const uint WM_ACTIVATE = 0x0006;

    (省略)
    }
}
using System;
using System.Runtime.InteropServices;

namespace Anttn.WIN32
{
    public static partial class NotifyIcon
    {
    (省略)

        [DllImport("shell32.dll")]
        public static extern int Shell_NotifyIconGetRect([In] ref NOTIFYICONIDENTIFIER identifier, out Window.RECT iconLocation);

    (省略)

        [StructLayout(LayoutKind.Sequential)]
        public struct NOTIFYICONIDENTIFIER
        {
            public uint cbSize;
            public IntPtr hWnd;
            public uint uID;
            public Guid guidItem;
        }

    (省略)

        public const uint NIN_SELECT = 0x0400;

    (省略)
    }
}
pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-C#, WIN32API