サブアイテム編集1では、
項目編集で生成されるエディットを移動する方法でサブアイテムの編集を行いました。
この方法では、右の画像のようにサブアイテムの編集中、1列目の項目が消えてしまいます。
これを解決する方法の一つとしてオーナードローがあります。オーナードローは、 コントロールの表示をコントロール自身が描画する手段です。 しかしながらオーナードローでは、 項目の選択状態やシステムカラーを考慮した描画を行おうとした場合、 それらすべてをプログラマが用意しなければならないため、非常に大変です。
そこで、システムが用意するエディットを使わずに、 自前でエディットを生成、表示する方法をとってみました。
リソースエディタでダイアログを作成し、そこにList Controlを配置します。 ダイアログおよびリストコントロールの設定は以下の通りです。 今回、アイテム編集機能は自前で用意するので、Edit LabelsはFalseです。
| コントロール | Caption | ID |
|---|---|---|
| ダイアログ | ID | IDD_MAINDIALOG |
| リストコントロール | ID | IDC_LISTVIEW |
| リストコントロール | Edit Labels | False |
| リストコントロール | View | レポート |
以下にサブアイテム編集リストビュー(CSubEditList)のクラスを示します。 CSubEditListはCListViewCtrlをベースにしています。
リストコントロールのダブルクリックでアイテムの編集開始、 エディットのフォーカス消失で編集終了です。 エディットのフォーカス消失は、EN_KILLFOCUS通知メッセージによって伝えられます。 エディットはCEditをサブクラス化したCSubEditを使います(後述)。
//---------------------------------------------------------------------------
// サブアイテム編集リストビュー
//---------------------------------------------------------------------------
class CSubEditList : public CWindowImpl<CSubEditList, CListViewCtrl>
{
private:
int mItem;
int mSubItem;
//編集用エディット
CSubEdit mLabelEdit;
public:
DECLARE_WND_SUPERCLASS(NULL, CListViewCtrl::GetWndClassName());
//メッセージマップ
BEGIN_MSG_MAP(CSubEditList)
MSG_WM_LBUTTONDBLCLK(OnLButtonDblClick)
COMMAND_CODE_HANDLER_EX(EN_KILLFOCUS, OnEditKillFocus)
END_MSG_MAP()
//各種イベントハンドラ
void OnLButtonDblClick(UINT nFlags, CPoint point);
void OnEditKillFocus(UINT uNotifyCode, int nID, CWindow wndCtl);
};
WM_LBUTTONDBLCLKメッセージで編集を開始します。 サブアイテム編集1と同様、 カーソル位置からアイテムを識別します。その後、 アイテムの長方形領域を取得してエディットをCreateします。 エディット専用のスタイルとして以下を使いました。
| スタイル | 備考 |
|---|---|
| ES_AUTOHSCROLL | 自動的に横スクロールします。 |
| ES_WANTRETURN | エンターキーを入力できるようにします。ただしES_MULTILINE必須。 |
| ES_MULTILINE | 2行以上の表示ができるようにします。 |
//ダブルクリックで項目編集開始
void CSubEditList::OnLButtonDblClick(UINT nFlags, CPoint point)
{
WTL::CString str;
LVHITTESTINFO lvhit;
CRect rect;
//カーソル位置からアイテム位置を取得します
lvhit.pt = point;
mItem = SubItemHitTest(&lvhit);
mSubItem = lvhit.iSubItem;
if ( mItem<0 || mSubItem<0 ) return;
//エディットの表示位置を取得します
if ( mSubItem==0 )
GetItemRect(mItem, &rect, LVIR_LABEL);
else
GetSubItemRect(mItem, mSubItem, LVIR_LABEL, &rect);
//アイテムのテキストを取得します
GetItemText(mItem, mSubItem, str);
mLabelEdit.Create(m_hWnd, &rect, _T("SubEdit"),
ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|WS_CHILD|WS_VISIBLE|WS_BORDER);
mLabelEdit.SetFont( GetFont() );
mLabelEdit.SetWindowText(str);
mLabelEdit.SetSelAll();
mLabelEdit.SetFocus();
}
//---------------------------------------------------------------------------
フォーカス消失の通知(WN_KILLFOCUS)でエディットの文字列を取得、 サブアイテムを更新します。使用済みのエディットはDestroyWindow()で削除します。
//フォーカスが移るタイミングで編集終了
void CSubEditList::OnEditKillFocus(UINT uNotifyCode, int nID, CWindow wndCtl)
{
CAtlString str;
//文字列を取得して、エディットを削除
mLabelEdit.GetWindowText(str);
mLabelEdit.DestroyWindow();
SetItemText(mItem, mSubItem, str);
}
//---------------------------------------------------------------------------
以下にアイテム編集用に用意したエディットクラスを示します。 拡張したエディットではエスケープとエンターキーの入力で、 フォーカスを親に戻します。
//---------------------------------------------------------------------------
// 編集用エディット
//---------------------------------------------------------------------------
class CSubEdit : public CWindowImpl<CSubEdit, CEdit>
{
public:
DECLARE_WND_SUPERCLASS(NULL, CEdit::GetWndClassName());
//メッセージマップ
BEGIN_MSG_MAP(CSubEdit)
MSG_WM_KEYDOWN(OnKeyDown)
END_MSG_MAP()
//キーダウンイベント
void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if ( nChar==VK_ESCAPE || nChar==VK_RETURN )
GetParent().SetFocus();
else
SetMsgHandled(FALSE);
}
};
以下にダイアログのクラスを示します。 エスケープとエンターのキーボード入力によってダイアログが終了するのを防ぐために、 PreTranslateMessage()関数内で、VK_ESCAPEとVK_RETURNをフィルタします。
//---------------------------------------------------------------------------
// メインダイアログクラス
//---------------------------------------------------------------------------
class CMainForm : public CDialogImpl<CMainForm>, public CMessageFilter
{
public:
enum { IDD = IDD_MAINDIALOG };
// 各種コントロール
CSubEditList mListView;
// WTLのメッセージマップ
BEGIN_MSG_MAP(CMainWindow)
COMMAND_HANDLER_EX(IDOK, BN_CLICKED, OnOk)
COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel)
MSG_WM_INITDIALOG(OnInitDialog)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
// 各種イベントハンドラ
void OnOk(UINT uNotifyCode, int nID, CWindow wndCtl);
void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl);
BOOL PreTranslateMessage(MSG *pMsg)
{
if ( pMsg->message==WM_KEYDOWN ) {
if ( pMsg->wParam==VK_ESCAPE || pMsg->wParam==VK_RETURN )
return FALSE;
}
return CWindow::IsDialogMessage(pMsg);
}
BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam);
void OnDestroy();
};
作成したCSubEditListクラスを使って、 ダイアログの初期化時に配置したリストコントロールをサブクラス化します。 また、リストの内容も適当に初期化しています。
//ウインドウ開始イベント
BOOL CMainForm::OnInitDialog(CWindow wndFocus, LPARAM lInitParam)
{
CRect rect;
CMessageLoop* loop = _Module.GetMessageLoop();
loop->AddMessageFilter(this);
// リストビューを初期化します
mListView.SubclassWindow( GetDlgItem(IDC_LISTVIEW) );
mListView.GetClientRect(&rect);
mListView.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);
mListView.InsertColumn(0, _T("項目"), LVCFMT_LEFT, rect.Width()/3);
mListView.InsertColumn(1, _T("値1"), LVCFMT_LEFT, rect.Width()/3);
mListView.InsertColumn(2, _T("値2"), LVCFMT_LEFT, rect.Width()/3);
mListView.AddItem(0, 0, _T("てすと1"));
mListView.AddItem(0, 1, _T("てすと2"));
mListView.AddItem(0, 2, _T("てすと3"));
mListView.AddItem(1, 0, _T("てすと4"));
mListView.AddItem(1, 1, _T("てすと5"));
mListView.AddItem(1, 2, _T("てすと6"));
return 0;
}
//---------------------------------------------------------------------------
以上のプログラムを実行すると、右のようになります。
サブアイテム編集1での、
1列目が消える問題が解決されています。今回は編集用エディットを表示しましたが、
代わりにコンボボックスを表示させてもよさそうです。
今回使用したプロジェクトをおいておきます。