「MFCの基礎 - 常駐ソフトウェア」の版間の差分

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動
(ページの作成:「== 概要 == タスクトレイに常駐するソフトウェアを開発する手順を記載する。<br> <br> 常駐ソフトウェアを開発するには、ソフト…」)
 
13行目: 13行目:
== 常駐ソフトウェア ==
== 常駐ソフトウェア ==
==== タスクバーに非表示のウィンドウを作成する ====
==== タスクバーに非表示のウィンドウを作成する ====
CView派生クラスを継承したCTrayWndクラスに、ウインドウを作成するメソッドを記述する。<br>
CDialog派生クラスまたはCView派生クラスを継承したCTrayWndクラスに、ウインドウを作成するメソッドを記述する。<br>
<br>
<br>
タスクバーに非表示のウィンドウを作成するため、Createメソッドを使用して、拡張ウィンドウスタイルとしてWS_EX_TOOLWINDOWを指定する。<br>
タスクバーに非表示のウィンドウを作成するため、Createメソッドを使用して、拡張ウィンドウスタイルとしてWS_EX_TOOLWINDOWを指定する。<br>
49行目: 49行目:
     delete this;
     delete this;
  }
  }
</syntaxhighlight>
<br>
==== タスクトレイにアイコン・ツールチップ・コールバックを登録・変更・削除する ====
上記で作成したCTrayWndクラスに、タスクトレイにアイコンを登録・変更・削除するメソッドを記述する。
このメソッドは、最終的には、Win32APIのShell_NotifyIcon関数が行う。
Shell_NotifyIcon関数の第1引数で、アイコンの登録・編集・削除を指定する。
Shell_NotifyIcon関数の第2引数のNOTIFYICONDATA構造体において、アイコン、ツールチップ文字列、コールバックを指定する。
<syntaxhighlight lang="c++">
BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, LPCTSTR pszTip /*= NULL*/ )
{
    ASSERT(NIM_ADD == dwMessage || NIM_DELETE == dwMessage || NIM_MODIFY == dwMessage);
    NOTIFYICONDATA nid = {0};
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd  = GetSafeHwnd();
    nid.uID    = ID_ICON;  // アイコンを1つだけサポートする
                          // タスクトレイのアイコンID
                          // 1つのプロセスで複数のアイコンをタスクトレイに表示する場合は、それぞれ異なるIDを指定する
                          // このIDはプロセスごとに独立しているので、他のプロセスが同じIDを指定していたとしても、それらが衝突することはない
    nid.uCallbackMessage = WM_USER_TRAYNOTIFYICON;
    nid.uFlags          = NIF_MESSAGE;
    if(NULL != hIcon && m_hIcon_current != hIcon)
    {  // アイコン変更
      nid.uFlags |= NIF_ICON;
      nid.hIcon  = hIcon;
      m_hIcon_current = hIcon;
    }
    if(NULL != pszTip)
    {  // ツールチップ表示
      nid.uFlags |= NIF_TIP;
      _tcsncpy_s(nid.szTip, sizeof(nid.szTip), pszTip, _TRUNCATE);
    }
    return Shell_NotifyIcon(dwMessage, &nid);
}
BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, UINT nStringResource )
{
    // 表示メッセージを、ストリングリソースのリソースIDで指定する関数
    CString msg = _T("");
    VERIFY(msg.LoadString(nStringResource));
    return NotifyIcon( dwMessage, hIcon, msg );
}
</syntaxhighlight>
<br>
TrayWnd.cppファイルのヘッダ部に、ID_ICONおよびWM_USER_TRAYNOTIFYICONの定義を記述する。<br>
CTrayWndクラスのコンストラクタに、m_hIcon_currentメンバ変数の初期化処理を記述する。<br>
<syntaxhighlight lang="c++">
// TrayWnd.cpp : 実装ファイル
//
#include "stdafx.h"
#include "MyApplication.h"
#include "TrayWnd.h"
#define ID_ICON (100)
#define WM_USER_TRAYNOTIFYICON (WM_APP+100)
IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
CTrayWnd::CTrayWnd() : m_hIcon_current(NULL)
{
}
// ...略
</syntaxhighlight>
<br>
TrayWnd.hファイルに、メンバ変数m_hIcon_currentおよびNotifyIconメソッドの宣言を記述する。<br>
<syntaxhighlight lang="c++">
#pragma once
class CTrayWnd : public CWnd
{
    private:
      HICON m_hIcon_current;
    public:
      BOOL Create();
      BOOL NotifyIcon( DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL );
      BOOL NotifyIcon( DWORD dwMessage, HICON hIcon, UINT nStringResource );
      DECLARE_DYNAMIC(CTrayWnd)
      // ...略
}
</syntaxhighlight>
<br>
==== タスクトレイのアイコンにおけるイベント(常駐ソフトウェアの共通処理) ====
CTrayWndクラスに、タスクトレイのアイコンにおけるイベントの応答処理を記述する。<br>
<br>
イベントの応答処理は、早く処理を完了させる必要があるため、イベントに応じて、ウィンドウメッセージをポストする。<br>
<syntaxhighlight lang="c++">
LRESULT CTrayWnd::OnTrayNotify(UINT wParam, LONG lParam)
{
    UINT uiIconID  = (UINT)wParam;
    UINT uiMouseMsg = (UINT)lParam;
    if(ID_ICON != uiIconID)
    {
      return 0;
    }
    // ここで模造メッセージをポストする理由:
    // この通知関数からは、できるだけ早く抜け出す必要がある。
    // たとえば、OnLButtonDownでOLEコントロールを作成しようとすると、
    // 暗号のようなエラーコードRPC_E_CANTCALLOUT_ININPUTSYNCCALLをMFCから受け取ることになるだろう。
    // WinError.hによるとこのエラーの意味は「アプリケーションが入力同期呼び出しをディスパッチしているので、発信呼び出しはできない。」
    switch(uiMouseMsg)
    {
      case WM_LBUTTONDOWN:
          m_bFireDoubleClick = FALSE;
          SetTimer( ID_CLICKTIMER, GetDoubleClickTime(), NULL );
          break;
      case WM_LBUTTONUP:
          if(m_bFireDoubleClick)
          {
            PostMessage(WM_LBUTTONDBLCLK);
          }
          break;
      case WM_LBUTTONDBLCLK:
          m_bFireDoubleClick = TRUE;
          KillTimer(ID_CLICKTIMER);
          break;
      case WM_RBUTTONUP:
          PostMessage(WM_RBUTTONUP);
          break;
    }
    return 0;
}
</syntaxhighlight>
<br>
TrayWnd.cppファイルに、ID_CLICKTIMERの定義を記述する。<br>
CTrayWndクラスのコンストラクタに、メンバ変数m_bFireDoubleClickの初期化処理を記述する。<br>
<br>
また、CTrayWndクラスに、WM_TIMER、WM_LBUTTONUP、WM_RBUTTONUP、WM_LBUTTONDBLCLKメッセージに対応するイベントハンドラを記述する。<br>
<syntaxhighlight lang="c++">
// TrayWnd.cpp
#include "stdafx.h"
#include "MyApplication.h"
#include "TrayWnd.h"
#define ID_ICON                100
#define WM_USER_TRAYNOTIFYICON  WM_APP + 100
#define ID_CLICKTIMER          4
IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)
// ... 略
BEGIN_MESSAGE_MAP(CTrayWnd, CWnd)
    ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)
END_MESSAGE_MAP()
// ...略
CTrayWnd::CTrayWnd() : m_hIcon_current(NULL), m_bFireDoubleClick(false)
{
}
// ...略
void CTrayWnd::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
    if(ID_CLICKTIMER == nIDEvent)
    {
      KillTimer(nIDEvent);
      ::PostMessage(WM_LBUTTONUP);
    }
    // CWnd::OnTimer(nIDEvent);
}
void CTrayWnd::OnLButtonUp(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
    LButtonClick();
    // CWnd::OnLButtonUp(nFlags, point);
}
void CTrayWnd::OnRButtonUp(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
    RButtonClick();
    // CWnd::OnRButtonUp(nFlags, point);
}
void CTrayWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
    LButtonDoubleClick();
    // CWnd::OnLButtonDblClk(nFlags, point);
}
void CTrayWnd::LButtonClick()
{
    POINT pt;
    GetCursorPos( &pt );
    CMenu menu;
    menu.LoadMenu(IDM_TRAY_L);
    CMenu *pPopup = menu.GetSubMenu(0);
    // SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
    SetForegroundWindow();
    pPopup->TrackPopupMenu( TPM_RIGHTBUTTON, pt.x, pt.y, this );
    ::PostMessage(WM_NULL);
}
void CTrayWnd::RButtonClick()
{
    POINT pt = {0};
    GetCursorPos(&pt);
    CMenu menu;
    menu.LoadMenu(IDM_TRAY_R);
    CMenu *pPopup = menu.GetSubMenu(0);
    // SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
    SetForegroundWindow();
    pPopup->TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
    ::PostMessage(WM_NULL);
}
void CTrayWnd::LButtonDoubleClick()
{
    ::SendMessage(WM_COMMAND, ID_APP_ABOUT);
    // または
    CAboutDlg dlg;
    dlg.DoModal();
}
</syntaxhighlight>
<br>
TrayWnd.hファイルに、メンバ変数m_bFireDoubleClickおよびOnTrayNotifyメソッドの宣言を記述する。<br>
<syntaxhighlight lang="c++">
// TrayWnd.h
#pragma once
class CTrayWnd : public CWnd
{
    private:
      HICON m_hIcon_current;
      BOOL m_bFireDoubleClick;
    protected:
      virtual void LButtonClick();
      virtual void RButtonClick();
      virtual void LButtonDoubleClick();
    public:
      BOOL Create();
      BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL);
      BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, UINT nStringResource);
      DECLARE_DYNAMIC(CTrayWnd)
    public:
      CTrayWnd();
      virtual ~CTrayWnd();
    protected:
      DECLARE_MESSAGE_MAP()
      virtual void PostNcDestroy();
      afx_msg LRESULT OnTrayNotify(UINT wParam, LONG lParam);
      afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
      afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
      afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
};
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>

2021年7月20日 (火) 20:09時点における版

概要

タスクトレイに常駐するソフトウェアを開発する手順を記載する。

常駐ソフトウェアを開発するには、ソフトウェアのメインウィンドウとして登録するウィンドウクラスに、以下の処理を記述する。

  1. タスクバーに非表示のウィンドウを作成する。
  2. ウィンドウを破棄する時、ウィンドウクラスのオブジェクトが自身のオブジェクトを削除する。
  3. タスクトレイに対して、アイコン、ツールチップ文字列、コールバックを登録・変更・削除する。
  4. タスクトレイのアイコンに対するイベントの応答。
  5. ウィンドウを作成する時、タスクトレイに対してアイコンを登録する処理を呼び出す。
  6. ウィンドウを破棄する時、タスクトレイに対してアイコンを削除する処理を呼び出す。



常駐ソフトウェア

タスクバーに非表示のウィンドウを作成する

CDialog派生クラスまたはCView派生クラスを継承したCTrayWndクラスに、ウインドウを作成するメソッドを記述する。

タスクバーに非表示のウィンドウを作成するため、Createメソッドを使用して、拡張ウィンドウスタイルとしてWS_EX_TOOLWINDOWを指定する。
ウィンドウを非表示にするため、ウィンドウの幅・高さを0にする。

 BOOL CTrayWnd::Create()
 {
    return CreateEx(WS_EX_TOOLWINDOW, AfxRegisterWndClass(0), _T("TrayWnd"), WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL);
 }


CTrayWndクラスに、Createメソッドの宣言を追加する。

 #pragma once
 
 class CTrayWnd : public CWnd
 {
    public:
       BOOL Create();
 
    DECLARE_DYNAMIC(CTrayWnd)
 
    // ...略
 }


ウィンドウを破棄する時にウィンドウクラスが自身のオブジェクトを削除する

CTrayWndクラスに、PostNcDestroyメソッドを追加して、自身のオブジェクトを削除する処理を記述する。

 void CTrayWnd::PostNcDestroy()
 {
    // TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
    CWnd::PostNcDestroy();
 
    delete this;
 }


タスクトレイにアイコン・ツールチップ・コールバックを登録・変更・削除する

上記で作成したCTrayWndクラスに、タスクトレイにアイコンを登録・変更・削除するメソッドを記述する。 このメソッドは、最終的には、Win32APIのShell_NotifyIcon関数が行う。

Shell_NotifyIcon関数の第1引数で、アイコンの登録・編集・削除を指定する。 Shell_NotifyIcon関数の第2引数のNOTIFYICONDATA構造体において、アイコン、ツールチップ文字列、コールバックを指定する。

 BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, LPCTSTR pszTip /*= NULL*/ )
 {
    ASSERT(NIM_ADD == dwMessage || NIM_DELETE == dwMessage || NIM_MODIFY == dwMessage);
 
    NOTIFYICONDATA nid = {0};
 
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd   = GetSafeHwnd();
    nid.uID    = ID_ICON;  // アイコンを1つだけサポートする
                           // タスクトレイのアイコンID
                           // 1つのプロセスで複数のアイコンをタスクトレイに表示する場合は、それぞれ異なるIDを指定する
                           // このIDはプロセスごとに独立しているので、他のプロセスが同じIDを指定していたとしても、それらが衝突することはない
    nid.uCallbackMessage = WM_USER_TRAYNOTIFYICON;
    nid.uFlags           = NIF_MESSAGE;
 
    if(NULL != hIcon && m_hIcon_current != hIcon)
    {  // アイコン変更
       nid.uFlags |= NIF_ICON;
       nid.hIcon   = hIcon;
       m_hIcon_current = hIcon;
    }
 
    if(NULL != pszTip)
    {  // ツールチップ表示
       nid.uFlags |= NIF_TIP;
       _tcsncpy_s(nid.szTip, sizeof(nid.szTip), pszTip, _TRUNCATE);
    }
 
    return Shell_NotifyIcon(dwMessage, &nid);
 }
 
 BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, UINT nStringResource )
 {
    // 表示メッセージを、ストリングリソースのリソースIDで指定する関数
    CString msg = _T("");
    VERIFY(msg.LoadString(nStringResource));
 
    return NotifyIcon( dwMessage, hIcon, msg );
 }


TrayWnd.cppファイルのヘッダ部に、ID_ICONおよびWM_USER_TRAYNOTIFYICONの定義を記述する。
CTrayWndクラスのコンストラクタに、m_hIcon_currentメンバ変数の初期化処理を記述する。

 // TrayWnd.cpp : 実装ファイル
 //
 
 #include "stdafx.h"
 #include "MyApplication.h"
 #include "TrayWnd.h"
 
 #define ID_ICON					(100)
 #define WM_USER_TRAYNOTIFYICON	(WM_APP+100)
 
 IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
 
 CTrayWnd::CTrayWnd() :	m_hIcon_current(NULL)
 {
 
 }
 
 // ...略


TrayWnd.hファイルに、メンバ変数m_hIcon_currentおよびNotifyIconメソッドの宣言を記述する。

 #pragma once
 
 class CTrayWnd : public CWnd
 {
    private:
       HICON m_hIcon_current;
 
    public:
       BOOL Create();
       BOOL NotifyIcon( DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL );
       BOOL NotifyIcon( DWORD dwMessage, HICON hIcon, UINT nStringResource );
 
       DECLARE_DYNAMIC(CTrayWnd)
 
       // ...略
 }


タスクトレイのアイコンにおけるイベント(常駐ソフトウェアの共通処理)

CTrayWndクラスに、タスクトレイのアイコンにおけるイベントの応答処理を記述する。

イベントの応答処理は、早く処理を完了させる必要があるため、イベントに応じて、ウィンドウメッセージをポストする。

 LRESULT CTrayWnd::OnTrayNotify(UINT wParam, LONG lParam)
 {
    UINT uiIconID   = (UINT)wParam;
    UINT uiMouseMsg = (UINT)lParam;
 
    if(ID_ICON != uiIconID)
    {
       return 0;
    }
 
    // ここで模造メッセージをポストする理由:
    // この通知関数からは、できるだけ早く抜け出す必要がある。
    // たとえば、OnLButtonDownでOLEコントロールを作成しようとすると、
    // 暗号のようなエラーコードRPC_E_CANTCALLOUT_ININPUTSYNCCALLをMFCから受け取ることになるだろう。
    // WinError.hによるとこのエラーの意味は「アプリケーションが入力同期呼び出しをディスパッチしているので、発信呼び出しはできない。」
 
    switch(uiMouseMsg)
    {
       case WM_LBUTTONDOWN:
          m_bFireDoubleClick = FALSE;
          SetTimer( ID_CLICKTIMER, GetDoubleClickTime(), NULL );
          break;
       case WM_LBUTTONUP:
          if(m_bFireDoubleClick)
          {
             PostMessage(WM_LBUTTONDBLCLK);
          }
          break;
       case WM_LBUTTONDBLCLK:
          m_bFireDoubleClick = TRUE;
          KillTimer(ID_CLICKTIMER);
          break;
       case WM_RBUTTONUP:
          PostMessage(WM_RBUTTONUP);
          break;
    }
 
    return 0;
 }


TrayWnd.cppファイルに、ID_CLICKTIMERの定義を記述する。
CTrayWndクラスのコンストラクタに、メンバ変数m_bFireDoubleClickの初期化処理を記述する。

また、CTrayWndクラスに、WM_TIMER、WM_LBUTTONUP、WM_RBUTTONUP、WM_LBUTTONDBLCLKメッセージに対応するイベントハンドラを記述する。

 // TrayWnd.cpp
 
 #include "stdafx.h"
 #include "MyApplication.h"
 #include "TrayWnd.h"
 
 #define ID_ICON                 100
 #define WM_USER_TRAYNOTIFYICON  WM_APP + 100
 #define ID_CLICKTIMER           4
 
 IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
 
 ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)

 // ... 略
 
 BEGIN_MESSAGE_MAP(CTrayWnd, CWnd)
    ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)
 END_MESSAGE_MAP()
 
 // ...略
 
 CTrayWnd::CTrayWnd() : m_hIcon_current(NULL), m_bFireDoubleClick(false)
 {
 
 }
 
 // ...略
 
 void CTrayWnd::OnTimer(UINT_PTR nIDEvent)
 {
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
    if(ID_CLICKTIMER == nIDEvent)
    {
       KillTimer(nIDEvent);
       ::PostMessage(WM_LBUTTONUP);
    }
 
    // CWnd::OnTimer(nIDEvent);
 }
 
 void CTrayWnd::OnLButtonUp(UINT nFlags, CPoint point)
 {
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    LButtonClick();
 
    // CWnd::OnLButtonUp(nFlags, point);
 }
 
 void CTrayWnd::OnRButtonUp(UINT nFlags, CPoint point)
 {
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    RButtonClick();
 
    // CWnd::OnRButtonUp(nFlags, point);
 }
 
 void CTrayWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
 {
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    LButtonDoubleClick();
 
    // CWnd::OnLButtonDblClk(nFlags, point);
 }
 
 void CTrayWnd::LButtonClick()
 {
    POINT pt;
    GetCursorPos( &pt );
 
    CMenu menu;
    menu.LoadMenu(IDM_TRAY_L);
 
    CMenu *pPopup = menu.GetSubMenu(0);
 
    // SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
    SetForegroundWindow();
    pPopup->TrackPopupMenu( TPM_RIGHTBUTTON, pt.x, pt.y, this );
    ::PostMessage(WM_NULL);
 }
 
 void CTrayWnd::RButtonClick()
 {
    POINT pt = {0};
    GetCursorPos(&pt);
 
    CMenu menu;
    menu.LoadMenu(IDM_TRAY_R);
 
    CMenu *pPopup = menu.GetSubMenu(0);
 
    // SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
    SetForegroundWindow();
    pPopup->TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
    ::PostMessage(WM_NULL);
 }
 
 void CTrayWnd::LButtonDoubleClick()
 {
    ::SendMessage(WM_COMMAND, ID_APP_ABOUT);
    // または
    CAboutDlg dlg;
    dlg.DoModal();
 }


TrayWnd.hファイルに、メンバ変数m_bFireDoubleClickおよびOnTrayNotifyメソッドの宣言を記述する。

 // TrayWnd.h
 
 #pragma once
 
 class CTrayWnd : public CWnd
 {
    private:
       HICON m_hIcon_current;
       BOOL m_bFireDoubleClick;
 
    protected:
       virtual void LButtonClick();
       virtual void RButtonClick();
       virtual void LButtonDoubleClick();
 
    public:
       BOOL Create();
       BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL);
       BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, UINT nStringResource);
 
       DECLARE_DYNAMIC(CTrayWnd)
 
    public:
       CTrayWnd();
       virtual ~CTrayWnd();
 
    protected:
       DECLARE_MESSAGE_MAP()
 
       virtual void PostNcDestroy();
       afx_msg LRESULT OnTrayNotify(UINT wParam, LONG lParam);
       afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
       afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
       afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
 };