How to count checked items

8 posts, 1 answers
  1. Brendan
    Brendan avatar
    63 posts
    Member since:
    Dec 2011

    Posted 09 Jun 2017 Link to this post

    I have a gridview with a gridviewcheckboxcolumn and an enabled headercheckbox as well.  I have Excel type filters enabled along with grouping.  What I want to do is get a running count of how many items are checked (say out of 1,000).

    Currently I am using a loop to get the checked cells by iterating through the RadGrid.MasterTemplate.DataView.ItemCount and checking each row checkbox value.  This works ok, but where I have a problem is with the header check/uncheck all button.  When I check the header button it will trigger the CellValueChanged event every time a cell changes.  For 1,000 records this gets pretty slow.

    So my questions are;

    1. Is there a CheckedCount method instead of iterating?  would have to work on Excel filtered grid
    2. Is there a way to 'wait' for the header toggle to finish so that the CellValueChanged could be set not to trigger until done?
    3. Is there a simpler, better way to get a count of Checked rows in a grid?
    Private Sub grdSales_CellValueChanged(sender As Object, e As GridViewCellEventArgs) Handles grdSales.CellValueChanged
        'count checked rows, enable process, set label
        If e.ColumnIndex > 0 Then Return    'only want checked col [0]
        Dim iCnt As Integer = fCountCheckedRows()
        btnProcess.Enabled = iCnt > 0
        lblCheckedRecords.Text = CStr(lblCheckedRecords.Tag) & iCnt
    End Sub
     
    Private Function fCountCheckedRows() As Integer
        Dim iCount As Integer = 0
        For i = 0 To grdSales.MasterTemplate.DataView.ItemCount - 1
            If Not IsDBNull(grdSales.Rows(i).Cells(0).Value) Then
                If grdSales.Rows(i).Cells(0).Value = True Then iCount += 1
            End If
        Next
        Return iCount
    End Function
  2. Dess | Tech Support Engineer, Sr.
    Admin
    Dess | Tech Support Engineer, Sr.  avatar
    3530 posts

    Posted 12 Jun 2017 Link to this post

    Hello Brendan, 

    Thank you for writing.  

    As you have already found out, the appropriate way to count all checked rows is to iterate the rows and determine their checked state. The HeaderCellToggleStateChanged event is the appropriate way to iterate all rows and get which are the checked ones. Here is a sample code snippet:
    Private Sub HeaderCellToggleStateChanged(sender As Object, e As GridViewHeaderCellEventArgs)
        GetCheckedRows()
    End Sub
     
    Private Sub GetCheckedRows()
        Dim iCount As Integer = 0
        For i = 0 To Me.RadGridView1.MasterTemplate.DataView.ItemCount - 1
            If Not IsDBNull(Me.RadGridView1.Rows(i).Cells(0).Value) Then
                If Me.RadGridView1.Rows(i).Cells(2).Value = True Then iCount += 1
            End If
        Next
        RadMessageBox.Show("Checked: " & iCount)
    End Sub

    I hope this information helps. Should you have further questions I would be glad to help.

    Regards,
    Dess
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  3. Brendan
    Brendan avatar
    63 posts
    Member since:
    Dec 2011

    Posted 12 Jun 2017 in reply to Dess | Tech Support Engineer, Sr. Link to this post

    Thanks.  I moved the Counter to the HeaderCellToggledChanged , but the problem is the change detection occurs before the rows are checked.  For example

    1. no rows in a grid are checked,
    2. I check the header checkbox which triggers the HeaderCellToggledChanged event.  
    3. This starts the counter function which gets the checked value of each row, BUT the check has nbot been registered yet, so non of the rows are reported as checked.
    4. After the HeaderCellToggledChanged is exited the rowcount is 0 and then all items are checked
    5. If I uncheck the header, the rowcount is reported as All, but none are checked (as exoected) when the HeaderCellToggledChanged  is completed.

    Somehow I need to run the CountCheckedRows after the HeaderCellToggledChanged has actually checked the rows

  4. Dess | Tech Support Engineer, Sr.
    Admin
    Dess | Tech Support Engineer, Sr.  avatar
    3530 posts

    Posted 13 Jun 2017 Link to this post

    Hello Brendan, 

    Thank you for writing back. 

    Indeed, in the HeaderCellToggleStateChanged event, the rows in the grid are not changed yet. However, you can use the inversed value of the header cell. Here is a sample code snippet:
    Private Sub HeaderCellToggleStateChanged(sender As Object, e As GridViewHeaderCellEventArgs)
        If e.State = Enumerations.ToggleState.On Then
            GetCheckedRows(ToggleState.Off)
        Else
            GetCheckedRows(ToggleState.Off)
        End If
    End Sub
    Private Sub GetCheckedRows(toggleState As ToggleState)
        Dim iCount As Integer = 0
        For i = 0 To Me.RadGridView1.MasterTemplate.DataView.ItemCount - 1
            If Not IsDBNull(Me.RadGridView1.Rows(i).Cells(0).Value) Then
                If Me.RadGridView1.Rows(i).Cells(2).Value = toggleState Then iCount += 1
            End If
        Next
        RadMessageBox.Show("Checked: " & iCount)
    End Sub

    I hope this information helps. If you have any additional questions, please let me know. 

    Regards,
    Dess
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  5. Brendan
    Brendan avatar
    63 posts
    Member since:
    Dec 2011

    Posted 13 Jun 2017 in reply to Dess | Tech Support Engineer, Sr. Link to this post

    Thanks for the help Dess, but I think the the fix is too cumbersome.  If I have some rows checked and then click the header toggle, the count will be wrong because some are checked others are not, but all will be checked after the HeaderCellState has finished.  

    In addition I want to keep track of the checked count each time a single row is checked, and am using the CellChange event for that.  The problem is that the HeaderCellToggle causes the CellChange to fire which calls the iterative RowCount almost recursively since the count is performed on every row change.  

    Ideally there would be a HeaderCellToggleStateChanging event where I could set a boolean so that the CellChange would not fire the RowCount and a HeaderCellToggleStateChanged event where the rows were changed within the event.  Since that doesn't seem to be possible and an intrinsic count property is not available, I don't think the idea of keeping track of checked rows will work.  Unless you know how to address the issues I've laid out.

    I'm sure I'm not the first person looking to keep count of checked rows, so maybe I'm missing something?

    Thanks, Brendan

  6. Dess | Tech Support Engineer, Sr.
    Admin
    Dess | Tech Support Engineer, Sr.  avatar
    3530 posts

    Posted 14 Jun 2017 Link to this post

    Hello Brendan, 

    Thank you for writing back. 

    It is absolutely normal that the CellValueChanged event is fired for each cell when you toggle the header checkbox because the header checkbox actually updates all cells belonging to this column. I have prepared a sample code snippet which achieves the desired behavior:
    Sub New()
     
        InitializeComponent()
     
        Dim dt As New DataTable
        dt.Columns.Add("Id", GetType(Integer))
        dt.Columns.Add("Name", GetType(String))
        dt.Columns.Add("IsActive", GetType(Boolean))
        For index = 1 To 1000
            dt.Rows.Add(index, "Row" & index, False)
        Next
        Me.RadGridView1.DataSource = dt
        Me.RadGridView1.AutoSizeColumnsMode = Telerik.WinControls.UI.GridViewAutoSizeColumnsMode.Fill
     
        Dim checkBoxColumn As GridViewCheckBoxColumn = TryCast(Me.RadGridView1.Columns.Last(), GridViewCheckBoxColumn)
        checkBoxColumn.EnableHeaderCheckBox = True
     
        AddHandler Me.RadGridView1.CellValueChanged, AddressOf CellValueChanged
     
        AddHandler Me.RadGridView1.MouseDown, AddressOf GridMouseDown
        AddHandler Me.RadGridView1.MouseUp, AddressOf GridMouseUp
    End Sub
     
    Private Sub GridMouseDown(sender As Object, e As MouseEventArgs)
        Dim checkBox As RadCheckBoxElement = TryCast(Me.RadGridView1.ElementTree.GetElementAtPoint(e.Location), RadCheckBoxElement)
        If checkBox IsNot Nothing Then
            Dim headerCell As GridCheckBoxHeaderCellElement = TryCast(checkBox.Parent, GridCheckBoxHeaderCellElement)
            If headerCell isnot Nothing  Then
                RemoveHandler Me.RadGridView1.CellValueChanged, AddressOf CellValueChanged
            End If
        End If
    End Sub
     
    Private Sub GridMouseUp(sender As Object, e As MouseEventArgs)
        Dim checkBox As RadCheckBoxElement = TryCast(Me.RadGridView1.ElementTree.GetElementAtPoint(e.Location), RadCheckBoxElement)
        If checkBox IsNot Nothing Then
            Dim headerCell As GridCheckBoxHeaderCellElement = TryCast(checkBox.Parent, GridCheckBoxHeaderCellElement)
            If headerCell IsNot Nothing Then
                GetCheckedRows()
                AddHandler Me.RadGridView1.CellValueChanged, AddressOf CellValueChanged
            End If
        End If
    End Sub
     
    Private Sub GetCheckedRows()
        Dim iCount As Integer = 0
        For i = 0 To Me.RadGridView1.MasterTemplate.DataView.ItemCount - 1
            If Not IsDBNull(Me.RadGridView1.Rows(i).Cells(0).Value) Then
                If Me.RadGridView1.Rows(i).Cells(2).Value = True Then iCount += 1
            End If
        Next
        RadMessageBox.Show("Checked: " & iCount)
    End Sub
     
    Private Sub CellValueChanged(sender As Object, e As GridViewCellEventArgs)
        GetCheckedRows()
    End Sub

    I hope this information helps. If you have any additional questions, please let me know. 

    Regards,
    Dess
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  7. Brendan
    Brendan avatar
    63 posts
    Member since:
    Dec 2011

    Posted 14 Jun 2017 in reply to Dess | Tech Support Engineer, Sr. Link to this post

    Thanks Tess for the detailed reply.  I found a simpler way to do this, though I may have found a bug in the grid.  Here is the revised code.  Since the HeaderToggle either checks All or No rows, I just return that number. For an individual row click I use the CellEndEdit event which fires after the checkbox update.

    Where I believe a bug may exist is when using filters.  For example I have a grid with 15 rows.  I check the HeaderCell to check all rows - works fine.  I then apply an Excel filter to keep say 5 rows - they remain checked.  If uncheck/recheck a single row, the HeaderCellToggleStateChanged may fire - randomly at that.  Maybe 3 of the 5 rows being checked will fire the HeaderEvent, I'm not sure why, but I see it in debug.

    However, it all works out with my code since I do not iterate rows on the HeaderCellToggleStateChanged event.  On top of that the CellEndEdit fires after the HeaderCellToggleStateChanged event, so I get a correct checked row count in the end.

    I hope this helps someone with counting checked rows in a grid :-)

    Private Function fCountCheckedRows() As Integer
        Dim iCount As Integer = 0
        Dim iRows As Integer = RadGrid.MasterTemplate.DataView.ItemCount
        For i = 0 To iRows - 1
            If CBool(RadGrid.MasterTemplate.DataView.Item(i).Cells(0).Value) = True Then iCount += 1
        Next
        Return iCount
    End Function
     
    Private Sub RadGrid_HeaderCellToggleStateChanged(sender As Object, e As GridViewHeaderCellEventArgs) Handles RadGrid.HeaderCellToggleStateChanged
        If e.State = Enumerations.ToggleState.On Then   'Simply return row count
            lblCheckedRecords.Text = CStr(lblCheckedRecords.Tag) & RadGrid.MasterTemplate.DataView.ItemCount
        Else    'unchecked, report 0
            lblCheckedRecords.Text = CStr(lblCheckedRecords.Tag) & "0"
        End If
    End Sub
     
    Private Sub RadGrid_CellEndEdit(sender As Object, e As GridViewCellEventArgs) Handles RadGrid.CellEndEdit
        If e.Column.Index = 0 Then   'checkbox column
            pSetCheckedRowsLabel()
        End If
    End Sub
     
    Private Sub RadGrid_FilterChanged(sender As Object, e As GridViewCollectionChangedEventArgs) Handles RadGrid.FilterChanged
        pSetCheckedRowsLabel()
    End Sub
     
    Private Sub pSetCheckedRowsLabel()
        Dim iCnt As Integer = fCountCheckedRows()
        lblCheckedRecords.Text = CStr(lblCheckedRecords.Tag) & iCnt.ToString
    End Sub
  8. Answer
    Dess | Tech Support Engineer, Sr.
    Admin
    Dess | Tech Support Engineer, Sr.  avatar
    3530 posts

    Posted 15 Jun 2017 Link to this post

    Hello Brendan, 

    Thank you for writing back. 

    I am glad that you have found a suitable solution for your scenario. Note that the header checkbox should display a valid state considering the available rows. If you filter some of the rows, they won't be visible. As a result, the toggle state of the header may need to be updated. That is why the HeaderCellToggleStateChanged will be fired in this case. Feel free to use your code which handles the custom scenario that you have.

    I hope this information helps. If you have any additional questions, please let me know. 

    Regards,
    Dess
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
Back to Top