通常 ダイアログリソースから CDialogクラスを派生した場合、
[Enter]キーを押すと[ OK ]ボタンが、[ESC]キーを押すと[キャンセル]
ボタンが押されたと判定され、デフォルトのダイアログプロシージャによって
ダイアログボックスが終了してしまいます。
テキストボックスの入力をして最後に[Enter]を押してしまったために
ダイアログボックスが終了してしまった経験は誰にでもあると思います。
しかも厄介なことに、[ OK ]ボタン、[キャンセル]ボタンを取り除いても
同様の処理が行われます。
この機能をサプレスするためには、派生クラス内での仮想関数である
OnOK()、OnCancel()を修正し、基本クラスの関数が
呼ばれないように変更します。
*ただし OnCancel()をサプレスした場合、他の方法でダイアログを終了させる
手段を用意しておかないと、通常の[閉じる]ボタンで終了できなくなって
しまいます。
ボタンやメニューから、EndDialog() を呼び出すようにしておきましょう。
[CTestDlg.h ヘッダファイル] ..... //{{AFX_MSG(CTestDlg) ..... virtual void OnOK(); virtual void OnCancel(); //}}AFX_MSG DECLARE_MESSAGE_MAP() [CTestDlg.cpp インプリメンテーションファイル] void CTestDlg::OnOK() { // 実際の[Enter]キーが押された場合の処理を記述 ..... // 終了させないのなら以下の関数をコメントアウト // CDialog::OnOK(); } void CTestDlg::OnCancel() { // 実際の[ECS]キーが押された場合の処理を記述 .....
// 終了させないのなら以下の関数をコメントアウト // CDialog::OnCancel(); }
別の方法として、ダイアログボックスでメッセージが処理される前に
メッセージをチェックする方法が考えられます。次に実際のコードを示します
// ダイアログ上でのリターンキーの押下をサプレスする BOOL xxxx::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) { return 0; // 必ず0で戻ること!! // 0以外で戻ると各コントロールへ次の処理が回らなくなる } return CDialog::PreTranslateMessage(pMsg); }
この場合 [ OK ]ボタン自体は有効で、マウスでのクリックには対応が可能です。 キーボードでの [Enter] のみを無効にすることができます。
コンボボックスは、それ自体のウィンドウ(リスト部分)と エディット用の
ウィンドウは 内部的には別々の扱いになってます。このため、キーボード
などのイベントはコンボボックスの直接のコントロールでは扱えません。
内部エディットコントロールのハンドルを取得する方法を説明します。
1. ChildWindowFromPoint() 関数を使用する
通常コンボボックス内部のエディットコントロールは、コンボボックスの
内部座標(3, 3) 以降に配置されていることを利用します。サンプルでは
安全のために CPoint(4, 4) としています。
CWnd* pWnd = m_ComboBox.ChildWindowFromPoint( CPoint(4, 4), CWP_SKIPINVISIBLE | CWP_SKIPDISABLED);
2. コンボボックスが所有するすべてのウィンドウを調べる
コンボボックス内部のウィンドウを順に調べていって、そのクラスが "EDIT"
であるものを使用します。(複数あったらどうするのだろう?)
char buf[256];
CWnd *pWnd = m_ComboBox.GetTopWindow();
while (pWnd != NULL) {
::GetClassName(pWnd->m_hWnd, buf, 255);
if (lstrcmpi(buf, "EDIT") == 0) break;
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
// pWnd = エディットコントロールのハンドル
1. 2. いずれかの方法でエディットコントロールのハンドルを得た後に、これらを
操作するために、自前のエディットコントロールのサブクラス化を行います。
サブクラス化の方法は、いずれの場合も、WM_CREATE や WM_INITDIALOG 内部で、
CMyEdit m_MyEdit; // 内部で処理を行う自前の派生クラス
m_MyEdit->SubclassWindow(pWnd->GetSafeHwnd());
以上の処理で、キー操作などは CMyEditクラスの WM_CHAR、WM_KEYDOWNで
扱うことが出来るようになります。
サブクラスでの使用が終了したら(WM_DESTROY処理で)、以下の後始末を
必ず行いましょう。
m_MyEdit.UnsubclassWindow();
エディットボックス(コンボボックス)での一般キー操作は、WM_CHARで
処理することが出来ますが、[TAB]、[Enter]、[BS]、[←]、[→]
等のキー操作は、WM_CHARが呼ばれないようです。
(ダイアログボックス内部限定されるのかもしれません)
通常のキー操作では、WM_KEYDOWN、WM_CHAR、WM_KEYUP とイベントが発生
しますが、上記特殊キー操作では、WM_KEYDOWN、WM_KEYUP のみイベントが
発生するようです。
一方、ActiveXコンポーネントとして作成したコントロールを、
Internet Explorer に配置した場合、[BS]、[↑]、[↓]といった
通常のカーソルコントロールが、ActiveXコンポーネントでは使用
できませんでした。
どうやら ActiveXコンポーネントにキー操作が渡る以前に、
Internet Explorer 側で処理を行っているみたいです。リストボックスの
選択中であっても、[↑]、[↓] キーを押すと、Internet Exploere 内の
コンテンツがスクロールしました。
回避方法はまだ見つけておりません。ご存知の方はお教えください m(_ _)m;
キーボードを含めて、各種イベントをその本来のイベントプロシージャが
処理する前に横取りして処理を行うことをフックといいます。
ここではキーボードフックを例に挙げて解説します。
1. フック処理用のコールバック関数を作成する
HHOOK hHook; // フック識別用のハンドル LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam) { if (wParam == VK_RETURN) { if ((lParam & 0xc0000000) == 0) { // キー押下時 // ... 実際の処理 } else { // キー解放時 // ... 実際の処理 } return TRUE; } // フックを行わない処理は、必ず次の関数に処理を委ねる return CallNextHookEx(hHook, code, wParam, lParam); }
2. 実際のフックを行う
SetWindowsHookEx( WH_KEYBOARD, // フックの種類 KeyboardProc, // 処理用関数 AfxGetApp()->m_hInstance, // フックするインスタンス AfxGetApp()->m_nThreadID); // 同 スレッドID
3. 終了時、ハンドルを元に戻す
UnhookWindowsHookEx(hHook);
タスクトレイに常駐するプログラムのために、コントロールIDと ウィンドウメッセージを設定します。どのヘッダファイルで行ってもかまいませんが 全てのクラスで参照できるように StdAfx.h で宣言します。
[StdAfx.h] ... #define MYWM_NOTIFY_ICON (WM_USER+1) #define IDC_TRAY_ICON
次にタスクバーへのアイコンの登録を行います。
// アプリケーションのアイコンを読み込む HICON hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); NOTIFYICONDATA tnid; tnid.cbSize = sizeof(NOTIFYICONDATA); tnid.hWnd = GetSafeHwnd(); // メインウィンドウハンドル tnid.uID = IDC_TRAY_ICON; // コントロールID(独自) tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; tnid.uCallbackMessage = MYWM_NOTIFY_ICON; // ウィンドウメッセージ(独自) tnid.hIcon = hIcon; // アイコンの設定 ::lstrcpy(tnid.szTip, "アプリケーションタイトル"); ::Shell_NotifyIcon(NIM_ADD, &tnid); // 実際の設定
終了時(OnDestroyハンドラ)にはタスクバー内部の後始末を記述します。これを 忘れるとタスクバーにアイコンが残ったり、リソースがうまく開放されなかったり しますので注意してください。
void Cxxxxx::OnDestroy() { .... NOTIFYICONDATA tnid; tnid.cbSize = sizeof(NOTIFYICONDATA); tnid.hWnd = GetSafeHwnd(); // メインウィンドウハンドル tnid.uID = IDC_TRAY_ICON; // コントロールID ::Shell_NotifyIcon(NIM_DELETE, &tnid); .... }
タスクトレイから送られる独自メッセージを処理するために メッセージマップを設定します。
// タスクトレーでマウス操作されたときのイベント ON_MESSAGE(MYWM_NOTIFY_ICON, OnMyNotifyIcon) // 上記操作でメニューを表示したときのイベント ON_COMMAND(IDM_XXX_YYYY, OnXxxYyyy) ON_COMMAND(IDM_XXX_ZZZZ, OnXxxZzzz)
タスクトレイからのメッセージマップに対するイベントハンドラの例です
LRESULT Cxxxx::OnMyNotifyIcon(WPARAM wParam, LPARAM lParam) { CString s; POINT pt; CMenu menu; ::GetCursorPos(&pt); // マウス座標の取得 switch (lParam) { case WM_LBUTTONDOWN: menu.CreatePopupMenu(); m_Edit.GetWindowText(s); // CEdit menu.AppendMenu(MF_STRING, WM_USER+10, s); menu.AppendMenu(MF_SEPARATOR, 0); for (int i = 0; i < m_List.GetCount(); i++) { m_List.GetText(i, s); menu.AppendMenu(MF_STRING, WM_USER+11+i, s); } // マウスの処理を行うため前面に配置する SetForegroundWindow(); menu.TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON, pt.x, pt.y, this); menu.DestroyMenu(); break; case WM_LBUTTONDBLCLK: ShowWindow(SW_SHOW); ShowWindow(SW_RESTORE); break; case WM_RBUTTONUP: // マウスの処理を行うため前面に配置する SetForegroundWindow(); menu.LoadMenu(IDR_POPUP_MENU); menu.GetSubMenu(0)->TrackPopupMenu( TPM_LEFTALIGN|TPM_LEFTBUTTON, pt.x, pt.y, this); menu.DestroyMenu(); break; default: return FALSE; break; } return TRUE; }
表示されたメニューへに対するイベントハンドラの例です。
BOOL Cxxx::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (wParam >= WM_USER .... ) {
// 実際の処理
}
return CDialog::OnCommand(wParam, lParam);
}
Windows NT/2000 では、システムの終了・時刻合わせのいずれの場合にも普通に
APIをコールしただけでは権限がないために、実行時にエラーが返されます。
このため プログラムの内部で、Access Token という権限を取得しなければ
これらの APIは使用できません。
さらに時刻合わせに関しては、ログオンしたときの絶対的な権限が必要で
時刻合わせを行うことのできないユーザでログオンしている時は、どうやら
時刻を変更する権限が得られない状況です。
(TOMOsanにはどうすればいいのか見つけられませんでした)
時刻合わせの権限の取得ルーチンを以下に示します。
HANDLE hToken; TOKEN_PRIVILEGES tkp; // 時刻合わせ用の権限の取得 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ; LookupPrivilegeValue(NULL, "SeSystemTimePrivilege", &luid); tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); // 実際の時刻合わせ SetSystemTime(&st); // 権限を元に戻す AdjustTokenPrivileges(hToken, TRUE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
システムのシャットダウンには、TOKEN_PRIVILEGESに SE_SHUTDOWN_NAME 権限が 必要です。以下にサンプルを示します
HANDLE hToken; TOKEN_PRIVILEGES tkp; // このプロセスの Token を取得します if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) error("OpenProcessToken"); // シャットダウン用の LUID を取得します LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); tkp.PrivilegeCount = 1; // one privilege to set tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // このプロセス用の シャットダウン権を取得します AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); // 権限が得られたかどうかチェックします if (GetLastError() != ERROR_SUCCESS) error("AdjustTokenPrivileges"); // 強制的に全アプリケーションを終了させシャットダウンします if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, 0)) error("ExitWindowsEx");
Microsoftネットワークでファイルを共有している場合、UNC名でファイルを 指定することで、ローカルディスクのファイルと同じようにファイルを読み書き することが可能です。UNC名は 以下の形式で指定します。
\\サーバ名\共有名
しかし UNIXサーバ等からファイルを読み込もうとする場合、SAMBA等を使用して
Microsoftネットワークのファイル共有設定を行わないと
使用することができません。サーバ上の設定ファイルを読み込むためだけに
SAMBAを設定するのも無駄ですし、DNS名のほか NetBIOS名の管理も行わなければ
ならないため、サーバを更新した場合などの処理が煩雑になります。
HTTPプロトコルや、FTPプロトコルを使用してファイルを読み込むようにすれば
これらの問題は解決します。
MFCでは インターネットクラスとして CInternetSession、CFtpConnection、
CHttpConnection、CInternetFile を用意しています。これらのクラスを使用する
には、afxinet.h をインクルードする必要があります。
FTPプロトコルを使用して サーバからファイルを読み込む例を示します。
#include <afxinet.h> .... void ReadFileData() { CInternetSession IntSession("My Session"); CFtpConnection *pFtpCon = IntSession.GetFtpConnection( "data.sample.org", "MyAccount", "MyPassword"); CInternetFile *pFile = pFtpCon->OpenFile("data.txt"); // 以下は、文字列 msg に読み込む場合 // バッファに読み込む場合は Read(buffer, length)を使用する CString msg; while (pFile->ReadString(msg) == TRUE) { ... // ここに処理を記述 } pFile->Close(); pFtpCon->Close(); delete pFtpCon; IntSession.Close(); }
サーバ上のファイル(リモートファイル)を、ローカルディスクに ダウンロードする場合は
void GetFile() { CInternetSession IntSession("My Session"); CFtpConnection *pFtpCon = IntSession.GetFtpConnection( "data.sample.org", "MyAccount", "MyPassword"); pFtpCon->GetFile("remote.txt", "D:\\Data\\newdata.txt"); pFtpCon->Close(); delete pFtpCon; IntSession.Close(); }
とすればOKです。
HTTPプロトコルを使用してファイルを読み込む場合は以下のとおりです。
この場合接続を確立した後で、リクエストヘッダを送信する必要があります。
void ReadHttpFile() { CInternetSession IntSession("My App"); CHttpConnection *pHttpCon = IntSession.GetHttpConnection("www.sample.org"); CHttpFile *pHttpFile = pHttpCon->OpenRequest( CHttpConnection::HTTP_VERB_GET, "sample.html"); // サーバに対してリクエストヘッダを送信する pHttpFile->AddRequestHeaders("Accept: text/*\r\nUser-Agent: http_Sample\r\n"); pHttpFile->SendRequest(); CString msg; while(pHttpFile->ReadString(msg) == TRUE) { ... // ここに処理を記述 } pHttpFile->Close(); pHttpCon->Close(); IntSession.Close(); }
レジストリを操作するためのクラスとして、CRegKeyクラスが提供されています。 具体的には、
#include <atlbase.h> // 必要なヘッダファイル CRegKey reg; // レジストリキーを開く。 存在しない場合も考慮して Createを使用 reg.Create(HKEY_CURRENT_USER, "Software\\TOMOsan\\TestProg\\Data"); // 値の読み込み ... DWORDの場合 reg.QueryValue((DWORD&)m_iPort, "Listening Port"); // 文字列の場合 char szBuffer[96]; DWORD dwLength = 96; reg.QueryValue(szBuffer, "Log File", &dwLength); QueryValue(.....) == ERROR_SUCCESS をチェックして処理を分岐させる
VCでアプリケーションの2重起動を防止する方法は、Mutexを使用するなど
高度な方法もあるようですが、ここでは作成されるウィンドウのタイトルを
チェックすることで 2重起動の防止を行う方法を説明します。
プログラム自体は アプリケーションクラスに記述します。
ウィンドウタイトルは、起動時には適当な名称をつけておき、起動後に表示する
正式なタイトルを リソースに設定しておきます。このリソースと同じウィンドウ
タイトルを持ったアプリケーションがあればそれをアクティブに設定し
起動したプログラムを終了させますが、アプリケーションが見つからなかった場合、
自分のウィンドウタイトルをリソースの文字列に設定して起動処理を行います。
[アプリケーションクラス] // リソースからウィンドウタイトルを取得 CString sTitle((LPCSTR)IDS_APPTITLE); // 指定するウィンドウを検索 HWND hWnd = FindWindow(NULL, sTitle); if (hWnd != NULL) { // 見つかった場合 HWND hPopWnd = GetLastActivePopup(hWnd); SetForegroundWindow(hPopWnd); // 最前面に移動 ShowWindow(hPopWnd, SW_RESTORE); // 元のサイズで表示 return FALSE; } [メインウィンドウクラス] //-- ウィンドウタイトルを設定 ... 2重起動のチェックにも使用 CString sTitle((LPCSTR)IDS_APPTITLE); SetWindowText(sTitle);
リソースエディタを使用してコントロールを貼り付ける際、基本クラスのコントロールから派生させたクラスを使用した場合、派生クラスに対して、WM_CREATE等の初期化処理が行われない(OnCreate(...)内部の関数が実行されない)。
これは、ダイアログテンプレートから作成した場合、MFCはその基本コントロール本来の初期化のみを行うためで、初期化に関しては注意が必要。
ただし、メンバー変数の管理や、イベントプロシージャなどは行うことが可能。
GetWindowThreadProcessId AttachThreadInput を使用する BOOL SetForceActiveWindow(HWND inactivehWnd,HWND activehWnd) { DWORD inactiveTID, activeTID, pid; BOOL Stat; inactiveTID = GetWindowThreadProcessId(inactivehWnd, &pid); activeTID = GetWindowThreadProcessId(activehWnd, &pid); Stat = AttachThreadInput(activeTID, inactiveTID, TRUE); Stat = SetForegroundWindow(activehWnd); Stat = AttachThreadInput(activeTID, inactiveTID, FALSE); return TRUE; }
* ダイアログエディタで作成する場合 [プロパティ]の,[スタイル]で,プッシュボタン型を選択する * 子ウィンドウとして作成する場合, Create(dwStyle,,,)の中で, dwStyle に, BS_AUTOCHECKBOX | BS_PUSHLIKE BS_AUTORADIOBUTTON | BS_PUSHLIKE を指定する * 途中でスタイルを変更する場合, 現在のスタイルは dwStyle = m_pControl->GetStyle() として取得 変更は m_pControl->ModifyStyle(REMOVE STYLE, NEWSTYLE, flag) MFCを使用しない場合は,GetWindowLong() / SetWindowLong()で
* 使用は,必ず派生クラスを作って! Async モードで使用するため,受信するためには必ず OnReceive() をオーバライドする必要がある. * SOCK_STREAM(TCP)モードの使用方法 クライアントでは Sock.Create(); Sock.Connect(サーバ,ポート); サーバでは Sock.Create(ポート); // 待ち受け用ポート Sock.Listen(); * SOCK_DGRAM(UDP)モードの使用方法 ReceiveFrom(...)では,すぐにはデータは得られないため, この関数はまず起動だけ行って, 実際の入力は,ソケットクラスの OnReceive()ハンドラ内部で ReceiveFrom()を使用する.
TOMOsan Top Page に戻る
パソコン・プログラミングに戻る