[Last Call] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

How to disable checkboxes in CListCtrl

Posted on 2006-05-15
17
Medium Priority
?
8,637 Views
Last Modified: 2013-11-20
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.
0
Comment
Question by:polyanovsky
  • 9
  • 5
  • 2
  • +1
17 Comments
 
LVL 49

Expert Comment

by:DanRollins
ID: 16686338
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16686362
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
0
 
LVL 22

Expert Comment

by:mahesh1402
ID: 16687855
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
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 45

Expert Comment

by:AndyAinscow
ID: 16688449
Do you want to disable individual check boxs (per item) or all checkboxs ?
0
 
LVL 22

Expert Comment

by:mahesh1402
ID: 16689420
With above code you will also get item id in the structure ( LPNMLISTVIEW->NMHDIR struct) ...so you can disable checkboxes selectively.

-MAHESH
0
 

Author Comment

by:polyanovsky
ID: 16690743
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).
0
 

Author Comment

by:polyanovsky
ID: 16690895
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.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16692918
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16692968
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
0
 

Author Comment

by:polyanovsky
ID: 16751416
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.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16755787
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16756962
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
0
 

Author Comment

by:polyanovsky
ID: 16761275
Dan,
Calling m_ctlList.SetCheck(pNMListView->iItem, TRUE);
from void CD21Dlg::OnItemchangingList1 causes stack overflow.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16764064
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...
0
 
LVL 49

Accepted Solution

by:
DanRollins earned 1000 total points
ID: 16764364
You are right... immediate stack overflow...

I guess there was some other part of the solution when I did this yesterday (I'm not at the same box, so I can't check until tomorrow).  But there is a workaround for such reentrancy problems (and this does work).

Add this to the top of your OnItemChanging fn:

      static BOOL fBusy= FALSE;
      if ( fBusy) {
            *pResult = 0;
            return;
      }

And change the code near the bottom of that fn like so:

      if ( pNMListView->uOldState == 0x2000 ) {
            fBusy= TRUE;
            m_ctlList.SetCheck(pNMListView->iItem, FALSE);
            fBusy= FALSE;
            *pResult= 1;
            return;
      }
      if ( pNMListView->uOldState == 0x1000 ) {
            fBusy= TRUE;
            m_ctlList.SetCheck(pNMListView->iItem, TRUE);
            fBusy= FALSE;
            *pResult= 1;
            return;
      }

==-=-=-=-=-=-=-=-=-=-
I don't particularly like to put those kind of "busy flags" in the code, so I'll check to see what's different on the other system and let you know what I've found out tomorrow.

-- Dan
0
 

Author Comment

by:polyanovsky
ID: 16800617
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16801409
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?
    http://www.experts-exchange.com/help.jsp#hi73
0

Featured Post

[Webinar] Cloud and Mobile-First Strategy

Maybe you’ve fully adopted the cloud since the beginning. Or maybe you started with on-prem resources but are pursuing a “cloud and mobile first” strategy. Getting to that end state has its challenges. Discover how to build out a 100% cloud and mobile IT strategy in this webinar.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
Have you tried to learn about Unicode, UTF-8, and multibyte text encoding and all the articles are just too "academic" or too technical? This article aims to make the whole topic easy for just about anyone to understand.
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Despite its rising prevalence in the business world, "the cloud" is still misunderstood. Some companies still believe common misconceptions about lack of security in cloud solutions and many misuses of cloud storage options still occur every day. …
Suggested Courses

834 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question