Qtでのシステムトレイアイコンのカスタム実装

ネイティブイベントフィルターの実装

システムトレイアイコンのカスタム実装には、プラットフォーム固有のメッセージ処理が必要です。以下のコードはWindows環境でのネイティブイベントフィルターの実装例です。

class CustomTrayIcon : public QObject, public QAbstractNativeEventFilter
{
public:
    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override
    {
        if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG")
        {
            MSG *msg = reinterpret_cast(message);
            
            if (msg->message == WM_TRAYNOTIFY)
            {
                switch (msg->lParam)
                {
                case WM_MOUSEMOVE:
                    handleMouseMovement();
                    break;
                case WM_MOUSEHOVER:
                    processMouseHover();
                    break;
                case WM_MOUSELEAVE:
                    processMouseLeave();
                    break;
                case WM_LBUTTONUP:
                    emit trayActivated();
                    break;
                case WM_LBUTTONDBLCLK:
                    emit doubleClicked();
                    break;
                case WM_RBUTTONUP:
                    showContextMenu(QCursor::pos());
                    *result = 0;
                    break;
                }
            }
        }
        return false;
    }
};

コンテキストメニューの表示制御

システムトレイの右クリックメニューは、画面内に確実に表示されるようにする必要があります。以下の関数はメニューの表示位置を調整します。

QPoint calculateOptimalMenuPosition(QWidget *menu, const QPoint &proposedPos)
{
    QRect menuRect = menu->rect();
    menuRect.moveTo(proposedPos);
    
    QRect screenRect = QApplication::desktop()->screenGeometry();
    
    // 水平方向の調整
    if (!screenRect.contains(QPoint(menuRect.left(), 0)))
        menuRect.translate(menu->width(), 0);
    else if (!screenRect.contains(QPoint(menuRect.right(), 0)))
        menuRect.translate(-menu->width(), 0);
    
    // 垂直方向の調整
    if (!screenRect.contains(QPoint(0, menuRect.bottom())))
        menuRect.translate(0, -menu->height());
    else if (!screenRect.contains(QPoint(0, menuRect.top())))
        menuRect.translate(0, menu->height());
    
    return menuRect.topLeft();
}

トレイアイコンの管理

NOTIFYICONDATA構造体を使用してトレイアイコンのプロパティを管理します。アイコンの更新は以下のように行います。

HICON CustomTrayIcon::generateIconHandle()
{
    if (m_icon.isNull())
        return m_currentIcon;
    
    int iconWidth = GetSystemMetrics(SM_CXSMICON);
    int iconHeight = GetSystemMetrics(SM_CYSMICON);
    QSize iconSize = m_icon.actualSize(QSize(iconWidth, iconHeight));
    QPixmap pixmap = m_icon.pixmap(iconSize);
    
    if (pixmap.isNull())
        return m_currentIcon;
    
    m_currentIcon = qt_pixmapToWinHICON(pixmap);
    return m_currentIcon;
}

void CustomTrayIcon::updateTrayIcon()
{
    m_notifyData.hIcon = generateIconHandle();
    m_notifyData.uFlags = NIF_ICON;
    
    if (!m_toolTip.isEmpty())
    {
        m_notifyData.uFlags |= NIF_TIP;
        qStringToLimitedWCharArray(m_toolTip, m_notifyData.szTip, 
                                 sizeof(m_notifyData.szTip) / sizeof(wchar_t));
    }
    
    Shell_NotifyIcon(NIM_MODIFY, &m_notifyData);
}

ホバー時のポップアップ表示

マウスホバー時に表示されるポップアップウィンドウの位置制御は、タスクバーの位置に応じて調整します。

void PopupWidget::displayAtPosition(const QPoint &trayCenter)
{
    m_trayCenter = trayCenter;
    QRect widgetRect = this->rect();
    
    QRect screenRect = QApplication::desktop()->screenGeometry();
    int taskbarPosition = getTaskbarLocation();
    
    switch (taskbarPosition)
    {
    case BOTTOM:
        {
            int taskbarHeight = getTaskbarHeight();
            QPoint pos(widgetRect.x() - widgetRect.width() / 2, taskbarHeight);
            move(pos);
        }
        break;
    case TOP:
        {
            QRect availableRect = QApplication::desktop()->availableGeometry();
            QPoint pos(widgetRect.x() - widgetRect.width() / 2, 
                      availableRect.height() - widgetRect.height());
            move(pos);
        }
        break;
    case LEFT:
        {
            int taskbarWidth = getTaskbarWidth();
            move(widgetRect.topLeft() + QPoint(taskbarWidth - m_trayCenter.x(), 
                                             -widgetRect.height() / 2));
        }
        break;
    case RIGHT:
        {
            if (!screenRect.contains(QPoint(widgetRect.right(), 0)))
                widgetRect.translate(-this->width(), 0);
            QRect availableRect = QApplication::desktop()->availableGeometry();
            move(widgetRect.topLeft() + QPoint(-(m_trayCenter.x() - availableRect.width()), 
                                             -widgetRect.height() / 2));
        }
        break;
    }
    
    show();
}

トレイアイコンの位置取得

Windows 7以降とそれ以前のOSでトレイアイコンの位置を取得する方法が異なります。

QRect CustomTrayIcon::getTrayIconGeometry()
{
    // Windows 7以降の場合
    auto notifyIconGetRect = getShellNotifyIconGetRect();
    if (notifyIconGetRect)
    {
        NOTIFYICONIDENTIFIER identifier;
        identifier.cbSize = sizeof(identifier);
        identifier.hWnd = (HWND)winId();
        identifier.uID = m_trayId;
        identifier.guidItem = GUID_NULL;
        
        RECT iconRect;
        HRESULT result = notifyIconGetRect(&identifier, &iconRect);
        if (SUCCEEDED(result))
        {
            return QRect(iconRect.left, iconRect.top,
                        iconRect.right - iconRect.left,
                        iconRect.bottom - iconRect.top);
        }
    }
    
    // Windows XP以前の場合
    return findTrayIconGeometryLegacy();
}

レガシーな方法でのトレイアイコン位置取得は、タスクバーの内部構造を解析して実現します。

タグ: Qt WindowsAPI システムトレイ ネイティブイベント NOTIFYICONDATA

6月29日 19:36 投稿