none
CheckedListBoxの複数選択 RRS feed

  • 質問

  • いつもお世話になります。

    VS2010でSDIプログラムのダイアログでCheckedListBoxを使い複数のItemにチェックを付けることはできたのですが、Itemの文字列は1つしか選択状態になりません。

    どうすれば複数選択状態になるのでしょうか?

    2016年3月30日 7:44

回答

  • チェックボックスのON/OFFと文字列反転を同期させたいということですかね
    以下のようにしてみてはどうでしょう
    (ダイアログ形式でプロジェクト作成してしまったのでOnInitDialogは適当な初期化メソッドに置き換えてください)

    // 初期化処理
    BOOL CTestDlg::OnInitDialog()
    {
        CDialog::OnInitDialog();
    
        // 省略
    
        m_listBox.SetCheckStyle( BS_3STATE );
    
        m_listBox.AddString("ONE");
        m_listBox.AddString("TWO");
        m_listBox.AddString("THREE");
        m_listBox.AddString("FOUR");
        m_listBox.AddString("FIVE");
        
        return TRUE;  
    }
    
    // 選択変更イベント
    BOOL CTestDlg::OnLbnSelchangeList()
    {
        int selectedIndex = m_listBox.GetCurSel();
        int checkState = m_listBox.GetCheck(selectedIndex);
        // 反転
        m_listBox.SetCheck(selectedIndex, !checkState);
    }


    • 編集済み せれ 2016年3月31日 4:43
    • 回答としてマーク 村尾DOS 2016年3月31日 6:18
    2016年3月31日 4:14
  • 既に回答済みとなっておりますが、気になったことがあったので投稿します。

    CCheckListBox でチェック状態と選択状態の同期を実装すると、リスト項目の文字の部分をクリックする分には問題がないのですが、左側のチェックボックスの四角部分をクリックすると、リストボックスで選択状態のすべての項目についてチェック状態が変更されてしまうので同期が合わなくなってしまうと思います。

    この問題を解決するには、CCheckListBox の派生クラスを作って、メッセージハンドラで既定の動きから処理を変えてやれば実装できるとは思いますが、コントロールの派生クラスを作らないで済む CTreeCtrl を使う方法がありましたので紹介します。

    以下 CTreeCtrl での実装手順になります。

    1.ダイアログにTree Controlを配置
    2.配置したTree Controlを右クリックして「変数の追加」から変数の型を"CTreeCtrl"、変数名を"m_TreeCtrl"として変数を追加
    3.また同様に、Tree Controlを右クリックして「イベントハンドラーの追加」から、TVN_SELCHANGINGとTVN_CLICKのイベントハンドラーを追加する
    4.OnInitDialogに下記のコードを追加

    // ツリービューの設定を変更	
    SetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE, GetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE) & ~CS_DBLCLKS); // ダブルクリックイベントは不必要
    m_TreeCtrl.ModifyStyle(0, TVS_CHECKBOXES); // チェックボックス
    
    // ツリービューにアイテムを追加
    m_TreeCtrl.InsertItem(TEXT("aaa"), 0, 0);
    m_TreeCtrl.InsertItem(TEXT("bbb"), 0, 0);
    m_TreeCtrl.InsertItem(TEXT("ccc"), 0, 0);
    
    // いくつかの項目を選択状態に設定
    SetTreeItemCheck(1, TRUE);
    SetTreeItemCheck(3, TRUE);
    SetTreeItemCheck(4, TRUE);

    5.追加されたイベントハンドラ OnTvnSelchangingTree1 は下記のように記述

    void CMFCApplication1Dlg::OnTvnSelchangingTree1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	*pResult = 1;
    }

    6.追加されたイベントハンドラ OnNMClickTree1 は下記のように記述

    void CMFCApplication1Dlg::OnNMClickTree1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	POINT pt = { 0 };
    	GetCursorPos(&pt);
    	m_TreeCtrl.ScreenToClient(&pt);
    	TVHITTESTINFO hti = { 0 };
    	hti.pt.x = pt.x;
    	hti.pt.y = pt.y;
    	if (m_TreeCtrl.HitTest(&hti) && (hti.flags & TVHT_ONITEMSTATEICON || hti.flags & TVHT_ONITEMLABEL || hti.flags & TVHT_ONITEMRIGHT))
    	{
    		if (m_TreeCtrl.GetCheck(hti.hItem))
    		{
    			m_TreeCtrl.SetCheck(hti.hItem, 0);
    			m_TreeCtrl.SetItemState(hti.hItem, 0, TVIS_SELECTED);
    		}
    		else
    		{
    			m_TreeCtrl.SetCheck(hti.hItem, 1);
    			m_TreeCtrl.SetItemState(hti.hItem, TVIS_SELECTED, TVIS_SELECTED);
    		}
    		*pResult = 1;
    	}
    }


    7.また、TreeCtrlの項目に対してチェック状態の取得・設定を行うために下記のような関数をクラスに追加します

    BOOL CMFCApplication1Dlg::GetTreeItemCheck(int nIndex) const
    {
    	HTREEITEM hItem = m_TreeCtrl.GetRootItem();
    	for (int i = 0; i< nIndex; ++i)
    		hItem = m_TreeCtrl.GetNextSiblingItem(hItem);
    	return m_TreeCtrl.GetCheck(hItem);
    }
    
    void CMFCApplication1Dlg::SetTreeItemCheck(int nIndex, BOOL nCheck)
    {
    	HTREEITEM hItem = m_TreeCtrl.GetRootItem();
    	for (int i = 0; i< nIndex; ++i)
    		hItem = m_TreeCtrl.GetNextSiblingItem(hItem);
    	m_TreeCtrl.SetCheck(hItem, nCheck);
    	m_TreeCtrl.SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
    }

    以上です。参考になりましたら幸いです。

    2016年4月1日 14:32

すべての返信

  • 単一選択オプション(=LVS_SINGLESEL)を設定していなければ

    SetSel()  ( CLRなら SetSelected() かな)

    で複数選択状態にできると思うのですが、どうでしょう。
    あと、Ctrlキー+左クリック Shifキー+左クリックなどして複数選択できなければ
    単一選択になっていると考えられます。Spy++で見てみるのも吉かも。

    2016年3月30日 8:06
  • CheckedListBoxには3つの種類の「選んでいるかどうか」を表す状態があり、それぞれ独立しています。以下の3つです。

    • フォーカス(アイテムの周囲に点線の枠を表示するなどして表現するもの。1つのコントロール内でfocusedなのは最大でも1つ)
    • 選択(アイテムの背景を青にするなどして表現するもの。設定によって単一または複数選択可能)
    • チェック(チェックボックスのチェック状態で表現するもの)

    で、このうち「選択」を複数にしたい、ということでいいですか?

    であれば、該当のコントロール作成時に、スタイルにLBS_MULTIPLESELを追加すればいいです。

    2016年3月30日 8:09
  • .NET Framework の CheckedListBox でしょうか?

    もしそうでしたら、

    https://msdn.microsoft.com/ja-jp/library/system.windows.forms.checkedlistbox.selectionmode(v=vs.110).aspx

    上記のページに「CheckedListBoxのオブジェクトは、複数選択がサポートされていません。」と記述されておりました。

    ご確認ください。
    2016年3月30日 8:09
  • 皆様、アドバイス有難うございます。

    表現がまずくて申し訳ありません。

    MFCのCListBoxをCCheckListBoxに変更し、Owner Draw:固定 Selection:マルチに設定したものです。

    現状ではチェックボックスOn/Offと文字列の選択状態が一致しません。

    やりたいことはとのOn/Offで文字列の反転状態を同期させ、出来れば文字列の反転/非反転でチェックボックスの状態を変更したいのです。

    Selection:なしにすればチェックボックスの不安定さは無くなりますので、ファーカス枠を消せればこれでも良いかと思っています。

    2016年3月31日 1:48
  • チェックボックスのON/OFFと文字列反転を同期させたいということですかね
    以下のようにしてみてはどうでしょう
    (ダイアログ形式でプロジェクト作成してしまったのでOnInitDialogは適当な初期化メソッドに置き換えてください)

    // 初期化処理
    BOOL CTestDlg::OnInitDialog()
    {
        CDialog::OnInitDialog();
    
        // 省略
    
        m_listBox.SetCheckStyle( BS_3STATE );
    
        m_listBox.AddString("ONE");
        m_listBox.AddString("TWO");
        m_listBox.AddString("THREE");
        m_listBox.AddString("FOUR");
        m_listBox.AddString("FIVE");
        
        return TRUE;  
    }
    
    // 選択変更イベント
    BOOL CTestDlg::OnLbnSelchangeList()
    {
        int selectedIndex = m_listBox.GetCurSel();
        int checkState = m_listBox.GetCheck(selectedIndex);
        // 反転
        m_listBox.SetCheck(selectedIndex, !checkState);
    }


    • 編集済み せれ 2016年3月31日 4:43
    • 回答としてマーク 村尾DOS 2016年3月31日 6:18
    2016年3月31日 4:14
  • おかげで解決しました。

    有難うございました。

    2016年3月31日 4:48
  • 既に回答済みとなっておりますが、気になったことがあったので投稿します。

    CCheckListBox でチェック状態と選択状態の同期を実装すると、リスト項目の文字の部分をクリックする分には問題がないのですが、左側のチェックボックスの四角部分をクリックすると、リストボックスで選択状態のすべての項目についてチェック状態が変更されてしまうので同期が合わなくなってしまうと思います。

    この問題を解決するには、CCheckListBox の派生クラスを作って、メッセージハンドラで既定の動きから処理を変えてやれば実装できるとは思いますが、コントロールの派生クラスを作らないで済む CTreeCtrl を使う方法がありましたので紹介します。

    以下 CTreeCtrl での実装手順になります。

    1.ダイアログにTree Controlを配置
    2.配置したTree Controlを右クリックして「変数の追加」から変数の型を"CTreeCtrl"、変数名を"m_TreeCtrl"として変数を追加
    3.また同様に、Tree Controlを右クリックして「イベントハンドラーの追加」から、TVN_SELCHANGINGとTVN_CLICKのイベントハンドラーを追加する
    4.OnInitDialogに下記のコードを追加

    // ツリービューの設定を変更	
    SetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE, GetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE) & ~CS_DBLCLKS); // ダブルクリックイベントは不必要
    m_TreeCtrl.ModifyStyle(0, TVS_CHECKBOXES); // チェックボックス
    
    // ツリービューにアイテムを追加
    m_TreeCtrl.InsertItem(TEXT("aaa"), 0, 0);
    m_TreeCtrl.InsertItem(TEXT("bbb"), 0, 0);
    m_TreeCtrl.InsertItem(TEXT("ccc"), 0, 0);
    
    // いくつかの項目を選択状態に設定
    SetTreeItemCheck(1, TRUE);
    SetTreeItemCheck(3, TRUE);
    SetTreeItemCheck(4, TRUE);

    5.追加されたイベントハンドラ OnTvnSelchangingTree1 は下記のように記述

    void CMFCApplication1Dlg::OnTvnSelchangingTree1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	*pResult = 1;
    }

    6.追加されたイベントハンドラ OnNMClickTree1 は下記のように記述

    void CMFCApplication1Dlg::OnNMClickTree1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	POINT pt = { 0 };
    	GetCursorPos(&pt);
    	m_TreeCtrl.ScreenToClient(&pt);
    	TVHITTESTINFO hti = { 0 };
    	hti.pt.x = pt.x;
    	hti.pt.y = pt.y;
    	if (m_TreeCtrl.HitTest(&hti) && (hti.flags & TVHT_ONITEMSTATEICON || hti.flags & TVHT_ONITEMLABEL || hti.flags & TVHT_ONITEMRIGHT))
    	{
    		if (m_TreeCtrl.GetCheck(hti.hItem))
    		{
    			m_TreeCtrl.SetCheck(hti.hItem, 0);
    			m_TreeCtrl.SetItemState(hti.hItem, 0, TVIS_SELECTED);
    		}
    		else
    		{
    			m_TreeCtrl.SetCheck(hti.hItem, 1);
    			m_TreeCtrl.SetItemState(hti.hItem, TVIS_SELECTED, TVIS_SELECTED);
    		}
    		*pResult = 1;
    	}
    }


    7.また、TreeCtrlの項目に対してチェック状態の取得・設定を行うために下記のような関数をクラスに追加します

    BOOL CMFCApplication1Dlg::GetTreeItemCheck(int nIndex) const
    {
    	HTREEITEM hItem = m_TreeCtrl.GetRootItem();
    	for (int i = 0; i< nIndex; ++i)
    		hItem = m_TreeCtrl.GetNextSiblingItem(hItem);
    	return m_TreeCtrl.GetCheck(hItem);
    }
    
    void CMFCApplication1Dlg::SetTreeItemCheck(int nIndex, BOOL nCheck)
    {
    	HTREEITEM hItem = m_TreeCtrl.GetRootItem();
    	for (int i = 0; i< nIndex; ++i)
    		hItem = m_TreeCtrl.GetNextSiblingItem(hItem);
    	m_TreeCtrl.SetCheck(hItem, nCheck);
    	m_TreeCtrl.SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
    }

    以上です。参考になりましたら幸いです。

    2016年4月1日 14:32
  • アドバイス有り難うございます。

    ビルドは成功しましたが、ダイアログを開こうとすると「サポートされていない操作を実行しようとしました。」と言うメッセージがでます。

    	SetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE, GetClassLong(m_TreeCtrl.GetSafeHwnd(), GCL_STYLE) & ~CS_DBLCLKS); // ダブルクリックイベントは不必要
    

    ここで止まっています。

    どうすれば良いでしょうか?

    お手数をお掛けしますが、よろしくお願いします。

    2016年4月4日 0:24
  • HWND CDataExchange::PrepareCtrl(int nIDC)
    {
    	ASSERT(nIDC != 0);
    	ASSERT(nIDC != -1); // not allowed
    	HWND hWndCtrl;
    	COleControlSite* pSite = NULL;
    	m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl);
    	if (hWndCtrl == NULL)
    	{
    		// Could be a windowless OCX
    		pSite = m_pDlgWnd->GetOleControlSite(nIDC);
    		if (pSite == NULL)
    		{
    			TRACE(traceAppMsg, 0, "Error: no data exchange control with ID 0x%04X.\n", nIDC);
    			ASSERT(FALSE);  ← ここで止まっています。
    			AfxThrowNotSupportedException();
    		}
    	}
    	m_idLastControl = nIDC;
    	m_bEditLastControl = FALSE; // not an edit item by default
    
    	return hWndCtrl;
    }
    
    
    すみません。dlgdata.cppで止まっていました。
    2016年4月4日 1:30
  • うーん。すみませんが、ちょっと原因が分からないです・・・(CDialog::OnInitDialog()を呼ぶ前にm_TreeCtrlにアクセスしている?)

    こちらで作成した VS2012 のプロジェクトをアップロードしてみますので、

    お手数をおかけしますが、プログラムを比較していただけますでしょうか?

    http://work.vc/CheckListBoxByTreeCtrl.zip

    よろしくお願いいたします。

    2016年4月4日 1:51
  • お陰様で解決しました。

    ご教示頂いたコードには問題なく、原因はCheckListBoxの残骸でした。

    実は、List Controlで試したのですが上手く行かず、Tree Controlが使えるとは知りませんでした。勉強になりました。

    色々と有難うございました。

    2016年4月5日 1:05