ネイティブイベントフィルターの実装
システムトレイアイコンのカスタム実装には、プラットフォーム固有のメッセージ処理が必要です。以下のコードは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();
}
レガシーな方法でのトレイアイコン位置取得は、タスクバーの内部構造を解析して実現します。