Link to home
Start Free TrialLog in
Avatar of polyanovsky
polyanovsky

asked on

How to disable checkboxes in CListCtrl

URGENT!
I have created checkboxes in CListCtrl using LVS_EX_CHECKBOXES style. How to disable the checkboxes so that the user cannot select them? I need a FULL answer with code sample.
Avatar of DanRollins
DanRollins
Flag of United States of America image

I recall looking into this some time ago and could never find a "simple" (directly-supported) way to disable the individual checkboxes.  As I recall, the checkboxes are really just ImageList pictures, and the underlying code decides when to change the image from a an empty box to one with a checkmark in it... and does not support the idea of disabling certain ones (so that they cannot be checked or unchecked).

In my case, I was able to hide (remove) the line-items that I would have disabled.  If that is not possibly, you may need to go with one of the pre-built "super-grid" type controls that offer disabled checkboxes as a feature.  For instance:
     SuperGrid: Yet Another ListView Control
     http://www.codeguru.com/Cpp/controls/listview/miscellanious/article.php/c1005
... which appears to provide that option.

Another option is to switch to CCheckListBox
      MFC Library Reference
      CCheckListBox Class  
      http://msdn2.microsoft.com/en-us/library/d58s8sak.aspx
Though simpler (not multi-column), it does provide a member function that enables and disables the checkbox on individual items.

-- Dan
Some confirmation of what I said above:

     Checkbox in ctrlList
     http://groups.google.com/group/microsoft.public.vc.mfc/tree/browse_frm/thread/7bd71b912b222260/929713879d0debff

It appears to be pretty easy to prevent the user from setting or clearing the checkbox, but there is some additional effort in making the small checkbox image "look" disabled (grayed).

-- Dan
What about using this one : http://www.codeproject.com/listctrl/xlistctrl.asp <====

Alternately with ur CListCtrl you may make all checkboxes disabled using following code,in msgmap of the parent dialog (you could also use reflection and put it to CListCtrl derivative)

ON_NOTIFY(LVN_ITEMCHANGING, IDC_LISTCTRL, OnItemChanging)

and the handler itself:

void CMyDlg::OnItemChanging(NMHDR* pNMHDR, LRESULT* pResult) {
 if(m_bReadOnly) {
  LPNMLISTVIEW pL = (LPNMLISTVIEW)pNMHDR;
  unsigned state = (pL->uOldState ^ pL->uNewState) & 0xF000;
  if(state) {
   *pResult = true;
   return;
  }
 }
 *pResult = false;
}

It check the change in image attribute of any checkbox and cancels it. The notification is also fired when the change is done programaticaly, but it cannot be cancelled. You also get item id in the structure so you can disable checkboxes selectively.

-MAHESH
Do you want to disable individual check boxs (per item) or all checkboxs ?
With above code you will also get item id in the structure ( LPNMLISTVIEW->NMHDIR struct) ...so you can disable checkboxes selectively.

-MAHESH
Avatar of polyanovsky
polyanovsky

ASKER

I need to disable checkboxes individually and independently of the row (the row will remain enabled - XListCtrl does not seem to support that). Checkboxes must LOOK disabled as well. I am using CListCtrl with report style (multi-column).
To DanRollins:
How to get rid of the tree in CSuperGridCtrl (so that it looks and behavies as a regular report style CListCtrl) and disable individual checkboxes there? Please note that I need a FULL answer. Is not there too much overhead for a simple task of disabling checkboxes?
I would expect a simple solution with image list containing three images ( checked, unchecked, disabled checkboxes). All relevant mouse and keyboard events should be caught and appropriate image displayed.
That SuperGrid control is certainly overkill.  This one might be more targetted for your needs.

     Another Report List Control
     http://www.thecodeproject.com/listctrl/ReportControl.asp

No Expert here has supplied a quick-and-easy answer because there is no LVM_DISABLCHECK message or API support of anything like DisableListViewCheckbox().  The standard ItemState values are Checked, Unchecked, and "not there".  Just read up on the Byzantine bit-flags that are used to refer to the four standard state values:
     http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/listview/macros/listview_setitemstate.asp

What's more, in my experience, I found that there are many interlocking events that occur when the user clicks the checkbox on a CListCtrl with LVS_EX_CHECKBOXES and it is pretty easy to get into a recursive loop in the message-handling and there are the issues that checking/unchecking must work with both the mouse and the keyboard.

It might not be as hard as I recall, but when researching list controls for your question, I see again and again how programmers gave up on trying to add whiz-bang features to LVS_EX_CHECKBOXES-styled controls and did an Owner-draw version from scratch.  

You will certainly need to add a "gray" version of the checkbox to the ImageList and provide a way to put it in place for items in which the checkbox is disabled.   And you will need to provide a means to set that image in places when the state changes normally (selected, not selected, focused, etc).  You will need to add an OnItemChanging() handler (as suggested above) that reproduces normal handling when enabled and the desired handling when disabled.

That's why I recommend trying to use some pre-built control, like ones I and others here have suggested.

-- Dan
Re-reading what I wrote:
I realized that there might be one possible quick-fix:  Setting the checkbox state to zero would remove the checkbox image altogether (or sho it says here):
     http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/listview/ex_styles.asp

If that would be a valid option for you, I'll look into it.

-- Dan
To Dan:
In
http://www.thecodeproject.com/listctrl/ReportControl.asp
it is not clear how to disable individual checkboxes and what is more important - they do not look disabled. The author uses LVS_EX_CHECKBOXES style with all disadvantages of this approach.
Removing checkboxes is not an option as well ( I need  disabled checkboxes).
If you could provide a full answer with code sample for Owner-draw version from scratch I would accept this answer.
You are correct.  That control lets you disable all of the checkboxes, but not individual ones.  And it doesn't change the look of the checkbox (dim/grey it out) when disabled.

I'll check into this further and see what I can find.

-- Dan
OK, it turns out to be not as hard as I thought.  Here are the steps:
1) In the resource editor, create two new bitmaps, exactly 16x16
    (key:  0=white, 1=black, 8=gray)
   IDB_GrayNoCheck:
     0000000000000000
     0011111111111110
     0011111111111110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011888888888110
     0011111111111110
     0011111111111110
     0000000000000000

   IDB_GrayCheck:
     0000000000000000
     0011111111111110
     0011111111111110
     0011888888888110
     0011888888888110
     0011888888818110
     0011888888118110
     0011818881118110
     0011811811188110
     0011811111888110
     0011881118888110
     0011888188888110
     0011888888888110
     0011111111111110
     0011111111111110
     0000000000000000

2) Create a function to make an item's checkbox disabled.  I chose to set a value into the ItemData.  If you are already using the ItemdData (eg, to point to a struct) then you will need to add that flag to the struct and make appropriate changes to access it when needed:

#define IDF_CheckboxDisabled 1   // itemdata flags
#define IDF_CheckboxEnabled  0

void SetDisabled( CListCtrl* pList, int nItem, BOOL fChecked )
{
      pList->SetItemData( nItem, IDF_CheckboxDisabled ); // flag for OnItemchanging...
      LVITEM lvi;
      lvi.stateMask = LVIS_STATEIMAGEMASK;
      lvi.state = INDEXTOSTATEIMAGEMASK( fChecked? 4 : 3);
      ::SendMessage(pList->m_hWnd, LVM_SETITEMSTATE, nItem, (LPARAM)&lvi);
}

3) Use the ClassWizard to add a handler for LVN_ITEMCHANGING (for the control, we'll call IDC_LIST1).  Make it so:

// state 0x1000 = cleared
// state 0x2000 = checked
// state 0x3000 = disabled, cleared
// state 0x4000 = disabled, checked
//
void CD21Dlg::OnItemchangingList1(NMHDR* pNMHDR, LRESULT* pResult)
{
      NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
      
      if ( pNMListView->uChanged== LVIF_STATE ) {
            int n= m_ctlList.GetItemData( pNMListView->iItem );
            if ( n != 0 ) {     // it is disabled disabled
                  if ( (pNMListView->uNewState == 0x3000 ) || (pNMListView->uNewState == 0x4000) ) {
                        *pResult= 0;   // allow the change
                        return;
                  }
                   //---- thes allow item to be selected (highlighted)
                  if ( pNMListView->uOldState == pNMListView->uNewState ) {
                        *pResult= 0;   // allow the change
                        return;
                  }
                  if ( (pNMListView->uOldState != 0) && (pNMListView->uNewState != 0) ) {
                        *pResult = 1;   // do not allow any change
                        MessageBeep(-1);
                        return;
                  }
            }
            //--------------- else, do normal check/uncheck action
            if ( pNMListView->uOldState == 0x2000 ) {
                  m_ctlList.SetCheck(pNMListView->iItem, FALSE);
                  *pResult= 1;
                  return;
            }
            if ( pNMListView->uOldState == 0x1000 ) {
                  m_ctlList.SetCheck(pNMListView->iItem, TRUE);
                  *pResult= 1;
                  return;
            }
      }
      *pResult = 0;
}

4) Here is the code to setup the list and populate it and set a disabled checkbox:

void CD21Dlg::OnButton1()
{
      //------------------------------------- setup the clms      
      m_ctlList.SetExtendedStyle( LVS_EX_CHECKBOXES );

      m_ctlList.InsertColumn(0,"clm a");
      m_ctlList.InsertColumn(1,"clm b");

      m_ctlList.SetColumnWidth(0,50);
      m_ctlList.SetColumnWidth(1,50);

      //---------------------- setup the grayed (disabled) images
      CImageList* pIL= m_ctlList.GetImageList( LVSIL_STATE  );
      int n= pIL->GetImageCount();

      CBitmap bm1, bm2;
      bm1.LoadBitmap(IDB_GrayNoChk );
      pIL->Add(&bm1, RGB(255, 255, 255)); // #3

      bm2.LoadBitmap(IDB_GrayChk );
      pIL->Add(&bm2, RGB(255, 255, 255)); // #4
      int n2= pIL->GetImageCount();

      //---------------------------------------- Populate the list
      m_ctlList.InsertItem(0,"item 0");
      m_ctlList.InsertItem(1,"item 1");
      m_ctlList.InsertItem(2,"item 2");

      m_ctlList.SetCheck( 0,TRUE); // a normal check, checked

      SetDisabled( &m_ctlList, 1, TRUE ) ; // disable an item (item #1)
}

=--=-==-=--==-=-
Note that the bit where I modify the CImageList fails unless at least one column exists in the list.
Other than that, the logic is pretty straightforward:

When we get a request to change the state, we check the ItemData to see if this item is disabled, if so, we only allow chnages to states 0x3000 and 0x4000.  Any other try to change it (eg, what happens when the user clicks it) causes a MessageBeep to indicates No Can Do!  If the ItemData says it's not a disabled checkbox, then we need the special handling to cycle 0x1000 to 0x2000 and vice-versa because since there are now four images, it would normally cycle through all four of them.

One other note:  
The value of pNMListView->lparam is the same as obtained from GetItemData, so you can use that if you want.

That's all there is to it.  It would make sense to add these features to a class derived from CListCtrl and it would not be hard to do so.  But it is not really necessary, and I've already spent far too much time on this Q :-)  But it was kinda fun....

-- Dan
Dan,
Calling m_ctlList.SetCheck(pNMListView->iItem, TRUE);
from void CD21Dlg::OnItemchangingList1 causes stack overflow.
I couldn't get it to malfunction in my testing.  What were the circumstances when it happened?  It might cause a problem if called for a disabled checkbox (though I don't see why it should).  Did you add any other message handlers?

If I can reproduce the problem I can fix it.  I'll try it on this other computer...
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Dan:
I could not find a better solution than "busy flag" as well. Finally, I used the folloing logic inOnItemChanging fn:

     NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
     static BOOL bSetCheck=TRUE;
     if ( pNMListView->uChanged== LVIF_STATE )
       {
             //can not move between disabled states
             if ( (pNMListView->uOldState == 0x3000&&pNMListView->uNewState == 0x4000)||
                   (pNMListView->uOldState == 0x4000&&pNMListView->uNewState == 0x3000))
             {
                    *pResult = 1;   // do not allow any change
                    MessageBeep(-1);
                    return;
         }

             //---- thes allow item to be selected (highlighted)
           if ( pNMListView->uOldState == pNMListView->uNewState )
               {
                *pResult= 0;   // allow the change
                return;
           }

             int n= m_ctlList.GetItemData( pNMListView->iItem );
          if ( n != 0 ) { //checkbox disabled                     
               if ( (pNMListView->uNewState == 0x3000 ) || (pNMListView->uNewState == 0x4000) )
                     {
                    *pResult= 0;   // allow the change
                    return;
               }
                //---- thes allow item to be selected (highlighted)
/*               if ( pNMListView->uOldState == pNMListView->uNewState )
                     {
                    *pResult= 0;   // allow the change
                    return;
               }*/
               if ( (pNMListView->uOldState != 0) && (pNMListView->uNewState != 0) )
                     {
                    *pResult = 1;   // do not allow any change
                    MessageBeep(-1);
                    return;
               }
          }
          //--------------- else, do normal check/uncheck action
          if ( pNMListView->uOldState == 0x2000&&bSetCheck)
              {
                     bSetCheck=FALSE;
               m_listIdentity.SetCheck(pNMListView->iItem, FALSE);
                     bSetCheck=TRUE;
               *pResult= 1;
               return;
          }
          if ( pNMListView->uOldState == 0x1000&&bSetCheck )
              {
                    bSetCheck=FALSE;
               m_listIdentity.SetCheck(pNMListView->iItem, TRUE);
                     bSetCheck=TRUE;
               *pResult= 1;
               return;
          }
     }
     *pResult = 0;

Thank you very much for your help, I accept your latest answer
I went to considerable effort here... including drawing the bitmaps for you.  There was one flaw in the code that I wrote, but I fixed it as soon as it was recognized.

It really hurts me that you insulted me with a grade of C -- the lowest grade that was available.  
Please see:
    What's the right grade to give?
    https://www.experts-exchange.com/help.jsp#hi73