最終更新:2008/05/24
〜 プログラミング Q & A 〜
ネタはあれども準備できず...
| Q | 既存のツリーアイテムがある状態で、MFCのCTreeCtrl::DeleteAllItems(TVI_ROOT
を指定したTVM_DELETEITEM メッセージ)を実行すると、TVN_SELCHANGEDメッセージが大量に送信されてしまう。そして非常に遅い。 独自のフラグを立てるなどしてTVN_SELCHANGEDメッセージですぐreturnするようにしても、アイテムが減り行く 際にスクロールバーが短くなっていく様子が見える。 |
| A | ポイントは2つある。 1つ目はアイテム削除時の選択変更自体を起こさせない事である。 2つ目は全削除中に内部で行われる順次削除による表示の更新を抑止する事だ。 |
CTreeCtrl &rTree = GetTreeCtrl();
// ツリーコントロールを非表示にして、アイテムが1つ1つ消える毎の再描画を抑止する
rTree.ShowWindow(SW_HIDE);
// 削除中の選択変更メッセージを抑止するためにダミーを追加する
// ツリールートの最後に追加するところがポイントである(おそらく上から順に削除している)
HTREEITEM hDummy = rTree.InsertItem("");
rTree.SelectItem(hDummy);
// 全て削除する
rTree.DeleteAllItems();
// ダミーを削除する
rTree.DeleteItem(hDummy);
// ツリーを作成する
CreateTree();
// 非表示状態を戻す
rTree.ShowWindow(SW_SHOW);
|
記:2008/05/24
| Q | SetCaptureでマウスをキャプチャして WM_SETCURSOR メッセージでマウスカーソルを 変更するようにしても、ウインドウの外側にマウスが移動した場合に対応できない。 |
| A | システムカーソルを変更することでマウスカーソルそのものを変更する。 |
{
// マウスキャプチャ開始
SetCapture();
// 設定したいカーソルをロードする
HCURSOR hCursor = AfxGetApp()->LoadCursor(IDC_MY_CURSOR);
// 形状ごとにシステムカーソルを変更する
SetSystemCursor(CopyCursor(hCursor), OCR_NORMAL); // 矢印
SetSystemCursor(CopyCursor(hCursor), OCR_IBEAM); // Iビーム
}
{
// マウスキャプチャを解放する
ReleaseCapture();
// カーソルを元に戻す
SystemParametersInfo(SPI_SETCURSORS, 0, NULL, 0);
}
|
記:2003/06/04
| Q | キャプション無しのダイアログ等でアプリケーションを実行すると、タスクバー(スタート メニューの右側の部分)上では空白のボタンとして表示されてしまう。 SetWindowTextを用いればタイトル文字列は表示できるが、右クリックしても、システム メニューが表示されない。 |
| A | システムメニュー表示メッセージ(0x0313:define未定義)を起点に独自にポップアップ メニューを表示する。 【方法】 1.DefWindowProc にて 0x0313 メッセージを処理できるようにする。 0x0313 は右クリックでシステムメニューを表示する際に送信されるが、システム メニューは無いので何もおきない。 2.このメッセージ処理内で TrackPopupMenu を用いてメニュー表示を行うと、メニュー 項目選択後になぜか無いはずのシステムメニューも表示されてしまうので、メッセー ジを PostMesgage で中継する。 3.中継されたユーザ定義のメッセージ(例えば WM_USER + 100)にてメニューを表示 する。 4.各メニュー項目の状態などは表示する際に調整しておくこと 5.ユーザ定義のコマンドやメニューIDを追加する場合は 0xF000 以上を WM_SYSCOMMAND とし、それ以外を WM_COMMAND として PostMesgage するか OnSysCommand で処理するようにする。 ※ 標準のバージョン情報表示処理は OnSysCommand に実装されているので注意。 |
LRESULT CMyDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if(0x0313 == message)
{
// lParam はクリック位置を表すPOINTデータ
PostMesgage(WM_USER + 100, wParam, lParam);
}
if(WM_USER + 100 == message)
{
// クリック位置(スクリーン座標なので変換の必要なし)
CPoint point = lParam;
// メニューを作成する(項目左横の絵は非対応)
CMenu menu;
menu.CreatePopupMenu();
// この位置でメニューの各項目を調整します
menu.AppendMenu(MF_STRING, SC_RESTORE, "元のサイズに戻す(&M)");
menu.AppendMenu(MF_STRING, SC_MOVE, "移動(&M)");
menu.AppendMenu(MF_STRING, SC_SIZE, "サイズ変更(&S)");
menu.AppendMenu(MF_STRING, SC_MINIMIZE, "最小化(&N)");
menu.AppendMenu(MF_STRING, SC_MAXIMIZE, "最大化(&X)");
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, SC_CLOSE, "閉じる(&C)\tAlt+F4");
// メニューを表示して選択されたコマンドIDを得る
UINT nIDItem
nIDItem = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON |
TPM_RETURNCMD,
point.x, point.y, this, NULL);
// コマンドを実行させる
PostMessage(WM_SYSCOMMAND, nIDItem, NULL);
return 0;
}
}
|
ダミー画面を用いる方法
LRESULT CMyDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if(0x0313 == message)
{
// lParam はクリック位置を表すPOINTデータ
PostMesgage(WM_USER + 100, wParam, lParam);
}
if(WM_USER + 100 == message)
{
// クリック位置(スクリーン座標なので変換の必要なし)
CPoint point = lParam;
// メニューを作成する(項目左横の絵に対応)
// ダミーの画面を一時的に作成してシステムメニューを借りる
CWnd wnd;
wnd.Create(NULL, NULL, WS_OVERLAPPEDWINDOW, CRect(0, 0, 0, 0),
GetDesktopWindow(), 0);
CMenu *pSysMenu = wnd.GetSystemMenu(FALSE);
// この位置でメニューの各項目を調整します
// メニューを表示して選択されたコマンドIDを得る
UINT nIDItem;
nIDItem = pSysMenu->TrackPopupMenu(TPM_LEFTALIGN |
TPM_LEFTBUTTON |
TPM_RETURNCMD,
point.x, point.y,
this, NULL);
// ダミーウィンドウを破棄する
wnd.DestroyWindow();
// コマンドを実行させる
PostMessage(WM_SYSCOMMAND, nIDItem, NULL);
return 0;
}
}
|
記:2003/04/27
| Q | コントロールの操作など OnKeyDown に仮想キーを設定して直接呼び出しても動作しない がどうすればよいか? |
| A | 引数に値を設定しても、基本クラスはその値を見ない。 また、実際にはキー押下後にどう処理すべきかを経てから OnKeyDown などが呼び出さ れるので、直接呼び出しても意味が無い。 そこで、キー押下のエミュレートAPIを用いる。 |
{
// キー押下のエミュレート(例ではスクリーンショット用のキー押下)
keybd_event(VK_SNAPSHOT, 0, 0, 0); // 全体「PintScreen」
keybd_event(VK_SNAPSHOT, 1, 0, 0); // アクティブ「Alt」+「PintScreen」
}
|
注意:Microsoftより正常に機能させる方法についてのコメントが出ています。
詳しくはこちらをご覧下さい。
記:2003/06/03
| Q | CListCtrlにはアイテムの高さを変更するメンバ関数は無いが、アイテムの高さを変更したい。 |
| A | フォントを変更することで高さは変わるが、字のサイズも当然変わってしまう。 CImageListを用いて任意のイメージを設定することで高さを変更する。 イメージリストのソースはアイコンでもビットマップでも良い。 この例では、イメージデータを持たない空のイメージリストを用いている。 |
class CMyDialog : public CDialog
{
public:
//(略)
CImageList m_image; // 高さを調整する為のイメージリスト
};
CMyDialog::PreSubclassWindow()
{
// 幅1、高さ40のイメージを持つ
m_image.Create(1, 40, ILC_COLOR, 0, 0);
}
BOOL CMyDialog::OnInitDialog()
{
//(略)
// TODO: 初期化をここに追加します。
// 高さ調整用のイメージリストを設定する
m_List.SetImageList(&m_image, LVSIL_STATE);
// サンプル表示用のコード
m_list.InsertColumn(0, "ヘッダー", LVCFMT_LEFT, 100);
m_list.InsertItem(0, "アイテム1");
m_list.InsertItem(1, "アイテム2");
m_list.InsertItem(2, "アイテム3");
}
|

図1 アイテム高さを変更したレポートビュー
記:2003/02/08
| Q | ダイアログにはメニューを簡単に追加できるが、メニューにあわせてアクセラレータテーブルも追加したい。 |
| A | PreTranslateMessage をオーバーライドしてキーボードアクセラレータを処理するようにする。 |
{
// アクセラレータテーブルをロードする
m_hAccelerator = ::LoadAccelerators(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_ACCELERATOR1));
}
BOOL CMyDialog::PreTranslateMessage(MSG* pMsg)
{
// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
// キーボードアクセラレータを処理する
if(0 != ::TranslateAccelerator(m_hWnd, m_hAccelerator, pMsg))
{
return TRUE;
}
return CDialog::PreTranslateMessage(pMsg);
}
|
記:2002/06/15
| Q | 表示するビューの切り替えにあわせて、使用するアクセラレータテーブルを変更するにはどうすれば良いか。 |
| A | CDocument::GetDefaultAccelerator または
CFrameWnd::GetDefaultAccelerator をオーバーライドしてアクセラレータテーブルを切り替える。 <注意> |
HACCEL CMainFrame::GetDefaultAccelerator()
{
// 別のアクセラレータテーブルを使用する場合
if(TRUE == m_bOtherAccelerator)
{
// 別のアクセラレータテーブルを返す
return ::LoadAccelerators(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_ACCELERATOR1));
}
// 標準のアクセラレータテーブルを返す
return ::LoadAccelerators(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_MAINFRAME));
}
|
記:2002/04/27
| Q1 | フォントの「MS 明朝」や「MS ゴシック」は固定幅のフォントであるが、ダイアログのプロパティで設定したり CFont::CreatePointFont で作成したフォントを設定しても正しく表示されない。 |
| A1 | これらの設定方法では、フォントは以下のようになる。
全角が半角の2倍幅となるようにするには、CFont::CreateFontIndirect、CFont::CreateFont、CFont::CreatePointFontIndirect などを使用して FIXED_PITCH を指定する必要がある。 |
{
LOGFONT logfont;
CFont font;
// LOGFONT構造体を設定する
memset(&logfont, NULL, sizeof(logfont));
logfont.lfHeight = -80; // 8ポイント
logfont.lfWeight = FW_NORMAL;
logfont.lfCharSet = SHIFTJIS_CHARSET; // Shift-JIS
logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logfont.lfQuality = DEFAULT_QUALITY;
logfont.lfPitchAndFamily = FIXED_PITCH; // 固定幅指定
strcpy(logfont.lfFaceName, "MS ゴシック"); // フォント名
// フォントを作成する
font.CreateFontIndirect(&logfont);
}
|
| Q2 | 印刷の場合はどうすれば良いか。 |
| A2 | 以下のようにすれば正しく印刷される。 (ただし、プレビュー表示は「中」以外では等幅で表示されない) |
void CMyView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
pDC->SetMapMode(MM_ISOTROPIC);
// Window, Viewport左上を( 0, 0 )に設定する
pDC->SetWindowOrg(0, 0);
pDC->SetViewportOrg(0, 0);
// View領域を取得、Windowに設定する
CRect rectClient;
GetClientRect(&rectClient);
pDC->SetWindowExt( rectClient.Width(), rectClient.Height() );
// 用紙サイズをViewportに設定する
pDC->SetViewportExt( pDC->GetDeviceCaps(HORZRES),
pDC->GetDeviceCaps(VERTRES) );
// LOGFONT構造体を設定する
LOGFONT logfont;
memset(&logfont, NULL, sizeof(logfont));
logfont.lfHeight = -80; // 8ポイント
logfont.lfWeight = FW_NORMAL;
logfont.lfCharSet = SHIFTJIS_CHARSET; // Shift-JIS
logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logfont.lfQuality = DEFAULT_QUALITY;
logfont.lfPitchAndFamily = FIXED_PITCH; // 固定幅指定
strcpy(logfont.lfFaceName, "MS ゴシック"); // フォント名
// フォントを作成する
CFont font;
font.CreatePointFontIndirect(&logfont, pDC);
// フォントを選択して描画(印刷)する
CFont *pOldFont = (CFont*)pDC->SelectObject(&font);
OnDraw(pDC);
pDC->SelectObject(pOldFont);
}
|
記:2002/06/15
| Q | ファイルメニューの「プリンタの設定」は通常、縦がデフォルトになっている。デフォルトを変えたい場合はどうすれば良いか。 |
| A | 初期化処理などを行う場所で、デバイスの情報を変更する。 |
{
PRINTDLG stPrintdlg;
CWinApp* pApp =AfxGetApp();
// デフォルトの設定を取得する
pApp->GetPrinterDeviceDefaults(&stPrintdlg);
// デバイスモードハンドルから構造体を取り出す
DEVMODE FAR* pstDevMode = (DEVMODE FAR*)::GlobalLock(stPrintdlg.hDevMode);
// 値を設定する
pstDevMode->dmOrientation = DMORIENT_LANDSCAPE; // 横向き
// ロックを解除する
::GlobalUnlock(stPrintdlg.hDevMode);
}
|
記:2002/06/15
| 〜する |
|
|
|
|
|
| Q | どうすれば良いか。 |
| A | こうする。 |
サンプルコード |
記:2002/04/27
Copyright(C) RainyLain 2000, 2003