タイトルだけでは何のことかわかないと思います。多くの人はPCを使用する時、 狭い画面で開発をしたり、インターネットを利用していると思います。中には デュアルディスプレイで贅沢な環境の方もいらっしゃるでしょうがほんの一部の方だと思います。
いくつもウィンドウを開いてデスクトップが手狭になってくると、
大抵は以前のものは閉じない限り、下に隠れていきますが、アプリケーションによっては常に
最前面に表示する場合があります。
当方もDesktopClockをそのように使用しています。
背景は透明なのですが、それでも針やルーラーが邪魔になる場合があります。ましてや
透明化してないウィンドウでは邪魔になる場面が多々あると思います。
そこで今回はマウスカーソルをウィンドウに合わせたら 一時的に非表示、又は透明化できるようにしてみます。ただ、マウスを当てた瞬間に ウィンドウが消えてしまっては、そのウィンドウに対して何かしたいときに非常に不便ですし、 再表示処理とでちらつきが発生してうっとうしいので、ある一定 時間が経過したら非表示(透明化)するようにしてみます。
マウスをウィンドウに合わせたら非表示するだけでしたらそんなに難しくないです。 WM_NCHITTESTとかWM_SETCURSORメッセージのハンドラにShowWindow(SW_HIDE)を記述するだけで できるかと思います。一定時間経過したら消えるようにするために最初に思いつくのが SetTimerを使うことです。
この場合、マウスをウィンドウに合わせてSetTimerを発行して一定期間後に ウィンドウを消去しますが、その期間内にウィンドウを非表示にしたくなくてマウスをウィンドウ外に 移動した場合でも非表示になってしまします。こういう用途にうってつけのAPIがあります。
・TrackMouseEvent
対象のウィンドウに合わせてからそのウィンドウ上にマウスが一定期間存在 (ホバリング)した場合にメッセージを発行してくれる便利?なAPIがTrackMouseEventです。 定義は
BOOL TrackMouseEvent( LPTRACKMOUSEEVENT lpEventTrack // TRACKMOUSEEVENT 構造体へのポインタ );
で、TRACKMOUSEEVENT構造体の設定にしたがって一定期間後にメッセージを 発行してくれます。この構造体の設定によってはマウスがウィンドウから離れた時にもメッセージを 発行してくれるのでウィンドウの再表示にも都合がいいです。
肝心の構造体の定義は
typedef struct tagTRACKMOUSEEVENT { DWORD cbSize; //この構造体のサイズ DWORD dwFlags; //発行するメッセージの種類 HWND hwndTrack; //マウスを追跡する対象のウィンドウハンドル DWORD dwHoverTime; //ホバリングの時間(WM_MOUSEHOVER発行までの時間) } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;
です。
TRACKMOUSEEVENT構造体の各メンバの設定内容は上表のとおりです。 説明だけではわかりづらいので例をあげます。
TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = m_hWnd; tme.dwHoverTime = 3000; ::TrackMouseEvent(&tme);
m_hWndのハンドルを持つウィンドウのクライアント領域上にマウスがある状態で 上記の設定でTrackMouseEventを実行したとします。3秒以上その領域に留まっていた場合は m_hWndのウィンドウにWM_MOUSEHOVERメッセージが届けられます。そして、 ウィンドウ外にマウスがでたときはWM_MOUSELEAVEが発行されます。
・SystemParametersInfo
クライアント領域で一定期間マウスが留まっていたらWM_MOUSEHOVERが 発行されることはすでに書きましたが、実はマウスが留まってもよい領域 (WM_MOUSEHOVERが発行される領域)は、クライアント領域全体とは限らないです。
では、TrackMouseEventが発行されてからマウスが移動してもWM_MOUSEHOVERが 発行される領域はどこからどこまで?ということになりますが、それを調査/設定するのが SystemParametersInfoです。
::SystemParametersInfo(SPI_SETMOUSEHOVERWIDTH, 300, NULL, 0); ::SystemParametersInfo(SPI_SETMOUSEHOVERHEIGHT, 200, NULL, 0);
これでTrackMouseEventが発行されてからマウスが幅300ピクセル、高さ200ピクセルに
一定期間留まっていればWM_MOUSEHOVERメッセージが送られます。
クライアント領域全体にこの領域を設定するにはTrackMouseEventを発行する前にウィンドウの幅、
高さを調べてその倍の値をSystemParametersInfoで設定すればいいと思います。
・カレンダー
では、TrackMouseEventを使ってみたいのですがウィンドウを作成しないといけません。
今回はTrackMouseEventの効果が分かりやすいように、前準備として常に前面に表示される当月の
カレンダを作成してみます。
その後にTrackMouseEventのロジックを実装します。仕様はカレンダ画面上に一定期間マウスが
留まったら(WM_MOUSEHOVERが発行されたら)、カレンダ画面を半透明にしてその下のウィンドウが
見えるようにします。そして、マウスがカレンダのクライアント領域から離れたら
再び非透明化して表示します。
本当は半透明にしないで非表示または透明化したほうが、下の画面の操作ができるので 実践的ですが、カレンダが非表示、完全透明化した時点でWM_MOUSELEAVEメッセージが発行されるので、 再表示の実装を工夫しないといけないのでより単純な仕様にしました。
<プロジェクトの作成>
メニューのファイル(F)→新規作成(N)→プロジェクト(P)から
プロジェクトの種類(P) : Visual C++ テンプレート(T) : MFC アプリケーション
を選択して任意のプロジェクト名を入力してください
アプリケーションウィザードで以下のように設定してください。
アプリケーションの種類 : ダイアログベース リソース言語 : (デフォルト) ユニコードライブラリを使用する : チェックする プロジェクト形式 : (デフォルト) MFCの使用法 : (デフォルト) メインフレームのスタイル : システムメニューにのみチェックする ダイアログタイトル : (デフォルト) 高度な機能 :(デフォルト)
最後にCWinAppとCDialogの派生クラス、CxxxApp、CxxxDlgクラスの名前を確認して 完了してください。2つクラスが作成されますが、以後CDialogの派生クラスは使用せずCWinAppの 派生クラスのみ使用します。
<カレンダクラスの作成>
CDialogの派生クラスを使用しない代わりにCWndの 派生クラスCCalenderを作成します。簡単に移動など出来ないようにキャプションバーがない ポップアップウィンドウを作ります。ツールバーのプロジェクト(P)→クラスの追加(C)で クラスウィザードを起動して以下のようにしてください。
カテゴリ : MFC テンプレート : MFCクラス クラス名 : CCalender 基本クラス : CWnd
完了するとスケルトンが作成されています。 それではカレンダ表示のための実装を以下のようにしてください。
戻値の型 : BOOL 関数名 : Create パラメータ : CRect型 rect UINT型 uiHoverTime アクセス : publicとして作成してください。
2.3.4の実装内容は以下のコードをご覧ください。
//Calender.h ヘッダファイル #pragma once // CCalender class CCalender : public CWnd { DECLARE_DYNAMIC(CCalender) public: CCalender(); virtual ~CCalender(); protected: UINT m_uiHoverTime; //Hover時間 protected: virtual void PostNcDestroy(); //C++オブジェクト自動破棄 public: BOOL Create(CRect rect, UINT uiHoverTime); //カレンダウィンドウ構築 afx_msg void OnPaint(); //カレンダ描画 DECLARE_MESSAGE_MAP() }; |
// Calender.cpp : 実装ファイル // #include "stdafx.h" #include "xxxxxxxxxxxx.h //CWinAppの派生クラスのヘッダファイル名 #include "Calender.h" // CCalender IMPLEMENT_DYNAMIC(CCalender, CWnd) //コンストラクタ CCalender::CCalender() : m_uiHoverTime(0) { } //デストラクタ CCalender::~CCalender() { } BEGIN_MESSAGE_MAP(CCalender, CWnd) ON_WM_PAINT() END_MESSAGE_MAP() //カレンダウィンドウ構築 //引数 rect カレンダウィンドウ表示位置(スクリーン座標) // uiHoverTime 透明化までの時間(WM_MOUSEHOVER発行までの時間) BOOL CCalender::Create(CRect rect, UINT uiHoverTime) { CPoint point; BOOL bRet = FALSE; do { //Hover時間設定 m_uiHoverTime = uiHoverTime; //Window構築 if (! CreateEx(WS_EX_TOPMOST | WS_EX_LAYERED, AfxRegisterWndClass(CS_DBLCLKS, 0, reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), 0), _T("Calender"), WS_POPUPWINDOW | WS_VISIBLE, rect, NULL, 0) ) break; //レイヤード設定 if (! SetLayeredWindowAttributes(0, 255, ULW_ALPHA)) break; //Window更新 UpdateWindow(); //カーソル処理 if (! GetCursorPos(&point) ) break; GetWindowRect(&rect); if (! SetCursorPos(rect.left, rect.top)) break; if (! SetCursorPos(point.x, point.y)) break; bRet = TRUE; } while(0); return bRet; } //C++オブジェクト自動破棄 void CCalender::PostNcDestroy() { delete this; } //カレンダ表示 void CCalender::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージハンドラコードを追加します。 // 描画メッセージでCWnd::OnPaint() を呼び出さないでください。 CRect rect; int nMonth = CTime::GetCurrentTime().GetMonth(); CTime start = CTime(CTime::GetCurrentTime().GetYear(), nMonth, 1, 0, 0, 0); int width, height; CPoint point(0, 0); CString str; TCHAR *pszWeek[] = {_T("日"), _T("月"), _T("火"), _T("水"), _T("木"), _T("金"), _T("土") }; GetClientRect(&rect); width = rect.Width() / 7; height = rect.Height() / 8; //月の表示 str.Format(_T("%d月"), nMonth); dc.DrawText(str, CRect(point, CSize(rect.Width(), height)), DT_CENTER); //曜日の表示 point += CSize(0, height); for (int i=0; i < sizeof pszWeek / sizeof pszWeek[0]; i++) dc.DrawText(pszWeek[i], -1, CRect(point + CSize(i*width, 0), CSize(width, height)), DT_CENTER); //各日付の表示 point += CSize( (start.GetDayOfWeek() - 1) * width, height ); for (CTime t = start; t.GetMonth() == nMonth; t += CTimeSpan(1, 0, 0, 0) ) { if ( t.GetDayOfWeek() <= 1 ) point = CPoint(0, point.y + height); str.Format(_T("%d"), t.GetDay()); dc.DrawText(str, CRect(point, CSize(width, height)), DT_CENTER); point += CSize(width, 0); } } |
これでCCalenderができたので、CWinAppの派生クラスCxxxxApp::InitInstanceでインスタンス化します。
アプリケーションウィザードでCxxxApp::InitInstanceの スケルトンが既にできていますが、CDialogの派生クラスを使うようになっているので以下のように コメントアウトした上、CCalenderに差し替えてください。
// TME.cpp : アプリケーションのクラス動作を定義します。 // #include "stdafx.h" #include "TME.h" //#include "TMEDlg.h" コメントアウトする #include "Calender.h" // 追加する // ・ // ・ // ・ // CTMEApp 初期化 BOOL CTMEApp::InitInstance() { // ・ // ・ // ・ //CTMEDlg dlg; //m_pMainWnd = &dlg; //INT_PTR nResponse = dlg.DoModal(); //if (nResponse == IDOK) //{ // // TODO: ダイアログが<OK> で消された時のコードを // // 記述してください。 //} //else if (nResponse == IDCANCEL) //{ // // TODO: ダイアログが<キャンセル> で消された時のコードを // // 記述してください。 //} CCalender *pCalender = new CCalender; if (pCalender) m_pMainWnd = pCalender; else return FALSE; // ダイアログは閉じられました。アプリケーションのメッセージポンプを開始しないで // アプリケーションを終了するためにFALSE を返してください。 //return FALSE; return pCalender->Create(CRect(100, 100, 400, 300), 2000); } |
これで準備段階のカレンダ表示ができるはずなので、ビルドして実行してみてください。 以下のようなカレンダが表示されると思います。
このウィンドウは非クライアント領域がない(ボーダー部分はありますがほとんどないに 等しい)ので、移動にドラッグ&ドロップができなかったり、最小化のシステムメニューなしで ウィンドウを作っていて、最小化できなかったりするので、この下にウィンドウの一部があり、 それが隠れていしまっている場合たいへん困ります。
そこでこのセクションの最初に書いたように、CCalenderに一定時間マウスがカレンダ上に 留まれば、ウィンドウを半透明化し、離れたら再び非透明化するようにします。
・TrackMouseEventの使用
ずいぶん前置きが長くなってしまいましたがここからが本題です。 といってもコード量はそんなに多くないです。
コードの内容は上記の通りですが、「マウスがウィンドウに移動」を確認する 必要があります。spy++で調べるとWM_NCHITTEST、WM_SETCURSORが送られてきているのでOnNcHitTest、 OnSetCursorで処理すればいいと思います。
afx_msg UINT OnNcHitTest( CPoint point ); afx_msg BOOL OnSetCursor( CWnd* pWnd, UINT nHitTest, UINT message );
ただ、WM_MOUSEHOVER、WM_MOUSELEAVEはクライアント領域上のマウスに対して
ポストされるので非クライアント上にいないことを確認しなければなりません。
OnNcHitTestでは戻値でその位置を返すので関数内では確認できません。一方、OnSetCursorは
第二引数のnHitTestでマウスの位置が渡されるので、この値を利用すればクライアント領域上か
そうでないか判断できます。
ということで、このハンドラでTrackMouseEventを 実行することにします。実装内容はすべてCCalenderで次の通りです。
//Calender.h ヘッダファイル // ・ // ・ class CCalender : public CWnd { // ・ // ・ protected: BOOL m_bHoverFlg; //TrackMouseEvent Activeフラグ public: //WM_SETCURSORハンドラ afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); //WM_MOUSEHOVER, WM_MOUSELEAVEハンドラ afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam); //・ }; |
// Calender.cpp : 実装ファイル // ・ // ・ //コンストラクタ CCalender::CCalender() : m_uiHoverTime(0) , m_bHoverFlg(FALSE) { } // ・ // ・ BEGIN_MESSAGE_MAP(CCalender, CWnd) ON_WM_PAINT() ON_WM_SETCURSOR() ON_MESSAGE(WM_MOUSEHOVER, &CCalender::OnMouseHover) ON_MESSAGE(WM_MOUSELEAVE, &CCalender::OnMouseLeave) END_MESSAGE_MAP() // ・ // ・ BOOL CCalender::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { //TrackMouseEventの実行確認 & マウスのクライアント上の存在確認 if (! m_bHoverFlg && nHitTest == HTCLIENT) { do { CRect rect; //Windowのクライアント領域 GetClientRect(&rect); //WM_MOUSEHOVERの発行を許可できるマウスの移動範囲の設定 if (! ::SystemParametersInfo(SPI_SETMOUSEHOVERWIDTH, rect.Width() * 2, 0, 0) ) break; if (! ::SystemParametersInfo(SPI_SETMOUSEHOVERHEIGHT, rect.Height() * 2, 0, 0) ) break; //WM_MOUSEHOVER, WM_MOUSELEAVEメッセージ発行許可 TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = m_hWnd; tme.dwHoverTime = m_uiHoverTime; if (! ::TrackMouseEvent(&tme)) break; m_bHoverFlg = TRUE; } while (0); } return CWnd::OnSetCursor(pWnd, nHitTest, message); } //MouseHoverハンドラ LRESULT CCalender::OnMouseHover(WPARAM wParam, LPARAM lParam) { return ! SetLayeredWindowAttributes(0, 50, ULW_ALPHA); } //MouseLeaveハンドラ LRESULT CCalender::OnMouseLeave(WPARAM wParam, LPARAM lParam) { m_bHoverFlg = FALSE; return ! SetLayeredWindowAttributes(0, 255, ULW_ALPHA); } |
以上でできたのでビルドしてカレンダを表示してください。上記の例では ウィンドウ上に2秒以上留まれば、半透明化されて下のウィンドウ(デスクトップ) が見えるようになるはずです。
今回はWM_MOUSELEAVEのポストのタイミングのために半透明化にしましたが、 下のウィンドウの処理をしたい場合は非表示にしないといけません。その際、再表示のためには グローバルフックなどでマウスメッセージを補足する必要がでてくるかもしれません。