Top > WINDOWS >CListViewCtrlのサブアイテム編集2

CListViewCtrlのサブアイテム編集2

 サブアイテム編集1では、 項目編集で生成されるエディットを移動する方法でサブアイテムの編集を行いました。 この方法では、右の画像のようにサブアイテムの編集中、1列目の項目が消えてしまいます。

これを解決する方法の一つとしてオーナードローがあります。オーナードローは、 コントロールの表示をコントロール自身が描画する手段です。 しかしながらオーナードローでは、 項目の選択状態やシステムカラーを考慮した描画を行おうとした場合、 それらすべてをプログラマが用意しなければならないため、非常に大変です。

そこで、システムが用意するエディットを使わずに、 自前でエディットを生成、表示する方法をとってみました。


リソースの作成

 リソースエディタでダイアログを作成し、そこにList Controlを配置します。 ダイアログおよびリストコントロールの設定は以下の通りです。 今回、アイテム編集機能は自前で用意するので、Edit LabelsはFalseです。

コントロールCaptionID
ダイアログIDIDD_MAINDIALOG
リストコントロールIDIDC_LISTVIEW
リストコントロールEdit LabelsFalse
リストコントロールViewレポート

CListViewCtrlの拡張

以下にサブアイテム編集リストビュー(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_MULTILINE2行以上の表示ができるようにします。

//ダブルクリックで項目編集開始
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列目が消える問題が解決されています。今回は編集用エディットを表示しましたが、 代わりにコンボボックスを表示させてもよさそうです。 今回使用したプロジェクトをおいておきます。