Search code examples
c#winformscheckboxobjectlistview

Why won't checkmarks appear in an TreeListView checkbox when CheckedAspectName name is set (and how do I programmatically set checkboxes)?


My question is based on the ObjectListView gettingstarted code (GettingStartedTree project ) referred to in the Getting Started section of the ObjectListView sourceforge online docs.

My goal is to add checkboxes to the Title column of the TreeListView in the GettingStartedTree project.

I was able to add checkboxes simply by setting treeListView1.CheckBoxes to true and setting treeListView1.CheckedAspectName to Title (see After making changes below) as described in the instructions. Problem: However, when I run the program and click on a checkbox, a checkmark does not appear in the checkbox. I expect that a user should be able to "check" a checkbox on the UI.

Note: If I leave treeListView1.CheckBoxes set to true and set treeListView1.CheckedAspectName to null, then a checkmark does appear in the checkbox.

Am I configuring the TreeListView correctly?

Before making any changes

treeListView1:

CheckBoxes = false
CheckedAspectName = null

OLVColumn Collection, olvColumn1(Title):

Name = olvColumn1
AspectName = Title
CheckBoxes = false (Title column)

enter image description here

After making changes

treeListView1:

CheckBoxes = true
CheckedAspectName = Title

OLVColumn Collection, olvColumn1(Title):

Name = olvColumn1
AspectName = Title
CheckBoxes = false (Title column)

enter image description here


Solution

  • Update

    After posting this answer, I came across a TreeViewList property named HierarchicalCheckboxes that I hadn't noticed before and that I don't see discussed/described anywhere in the ObjectListView sourceforge docs. I now believe this is what Grammarian was referring to in his comment under Patricks answer. In my code (in this answer), this property is set false, so I assume I'm NOT using HierarchicalCheckboxes. I thought hierarchical checkboxes were checkboxes in a model that has a hierarchical structure like the model in my code in this answer.

    I guess/assume HierarchicalCheckboxes is associated with the write-up on Hierarchy-aware checkboxes , although I'm not sure. ObjectListView is a great control. I just wish the ObjectListView sourceforge docs did a better job differentiating the explaining the collection of lists required to draw the treeview portion of a TreeListView along with adding and managing checkboxes to such a model both with with and without the use of the hierarchical checkboxes feature (enabled by the property HierarchicalCheckboxes). I guess all the pieces are in the docs, but its spread out and a bit disjointed.

    End of Update

    For the sake of clarity I was kind of forced to answer this myself.....

    Reminder: all this is based on the gettingstartedcode described in the original question.

    First, the answer to my initial problem was to clear treeview1.CheckedAspectName as in the original example code. Doing so resulted in a checkmark appearing in the checkbox (as expected) when the checkbox is clicked.

    Grammarian kindly described how to programmatically check/uncheck a checkbox for (I assume) a non-hierarchical checkbox scenario, i.e., add a bool? or bool property to the model and add the property's name to the TreeListView's CheckedAspectName property.

    I came to the conclusion that this won't work for hierarchical checkboxes because Grammarian said the following and loading the bool? or bool property's name into CheckedAspectName is required based on his explanation.

    If you are using hierarchical checkboxes, you can't use CheckedAspectName or anything else that eventually installs a CheckStateGetter

    So, ignoring the advice that setting the CheckedAspectName property for hierarchical checkboxes won't work, I implemented a bool? isChecked property at all levels in my hierarchical model. It works fine, i.e., I can now programmatically check a checkbox at any level in the hierarchy via the model.

    Code for a simple example follows (I need to write the code to set the tri-state checkbox for parent rows based on the checkedness or uncheckedness of child rows.). The column "# To Keep" is designed to be relevant only for the level-2 items in the hierarchy

    The form has only a FooTreeListView : BrightIdeasSoftware.TreeListView control on it

    enter image description here

    FooTreeListView.cs

    using System;
    using System.Collections.Generic;
    
    namespace ObjectListView_TreeListView
    {
        class FooTreeListView : BrightIdeasSoftware.TreeListView
        {
            private List<Categories> categoriesList;
    
            private readonly string[] categoryDescriptors = { "Cat A", "Cat B", "Cat C", "Cat D" };
    
            internal List<Categories> CategoriesList { get => categoriesList; set => categoriesList = value; }
    
            public enum CategoryEnum
            {
                CategoryA = 0,
                CategoryB = 1,
                CategoryC = 2,
                CategoryD = 3
            }
    
            public FooTreeListView() : base()
            {
                CategoriesList = new List<Categories>();
                CategoriesList.Clear();
    
                CanExpandGetter = delegate (Object x)
                {
                    if (x is Categories && ((Categories)x).ItemList.Count > 0)
                    {
                        return true;
                    }
    
                    if (x is Categories.Item && ((Categories.Item)x).ActionList.Count > 0)
                    {return true;
                    }
    
                    return false;
    
                };
    
                ChildrenGetter = delegate (Object x)
                {
                    if (x is Categories)
                        return ((Categories)x).ItemList;
    
                    if (x is Categories.Item)
                        return ((Categories.Item)x).ActionList;
    
                    throw new ArgumentException("Should be Categories or Categories.Item");
                };
    
                //Load the 4 top-level categories into the tree
                CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryA],false));
                CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryB], false));
                CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryC], false));
                CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryD], false));
            }
    
            internal class Categories
            {
                private string action;
                private bool? isChecked;
                public string Action { get { return action; } set { action = value; } }
                public bool? IsChecked { get => isChecked; set => isChecked = value; }
    
                private List<Item> itemList;
                internal List<Item> ItemList { get => itemList; set => itemList = value; }
    
                public Categories(string action, bool? isChecked)
                {
                    this.action = action;
                    this.isChecked = isChecked;
                    ItemList = new List<Item>();
                }
    
                internal class Item
                {
                    private string action;
                    private bool? isChecked;
                    private int numberToKeep = 0;
                    private List<ItemAction> actionList;
    
                    public string Action { get { return action; } set { action = value; } }
                    public int NumberToKeep { get => numberToKeep; set => numberToKeep = value; }
                    public bool? IsChecked { get => isChecked; set => isChecked = value; }
    
                    internal List<ItemAction> ActionList { get => actionList; set => actionList = value; }
    
                    internal Item(string action, bool? isChecked, int numberToKeep)
                    {
                        this.action = action;
                        this.isChecked = isChecked;
                        this.NumberToKeep = numberToKeep;
                        ActionList = new List<ItemAction>();
                    }
    
                    internal class ItemAction
                    {
                        private string action;
                        private bool? isChecked;
                        public string Action { get { return action; } set { action = value; } }
                        public bool? IsChecked { get { return isChecked; } set { isChecked = value; } }
    
                        internal ItemAction(string action, bool? isChecked)
                        {
                            this.action = action;
                            this.isChecked = isChecked;
                        }
                    }
                }
            }
    
            public void AddCategoryItemName(CategoryEnum category, string itemName, bool? isChecked, int numberToKeep)
            {
                CategoriesList[(int)category].ItemList.Add(new Categories.Item(itemName, isChecked, numberToKeep));
            }
    
            public void AddItemAction(CategoryEnum category, string itemName, string action, Boolean isChecked)
            {
                Categories.Item itemMatch = CategoriesList[(int)category].ItemList.Find(x => x.Action.Equals(itemName));
    
                if (itemMatch != null)
                {
                    itemMatch.ActionList.Add(new Categories.Item.ItemAction(action, isChecked));
                }
                else
                {
                    throw new ArgumentException(String.Format("Can't find treeviewlist item '{0}'->'{1}'", categoryDescriptors[(int)category], itemName));
                }
            }
    
            public void AddItemAction(CategoryEnum category, string itemName, string action)
            {
                Categories.Item itemMatch = CategoriesList[(int)category].ItemList.Find(x => x.Action.Equals(itemName));
    
                if (itemMatch != null)
                {
                    itemMatch.ActionList.Add(new Categories.Item.ItemAction(action, false));
                }
                else
                {
                    throw new ArgumentException(String.Format("Can't find treeviewlist item '{0}'->'{1}'", categoryDescriptors[(int)category], itemName));
                }
            }
    
            public void LoadTree()
            {
                Roots = CategoriesList;
                ExpandAll();
            }
        }
    }
    

    Form1.cs

    using System.Windows.Forms;
    using static ObjectListView_TreeListView.FooTreeListView;
    
    namespace ObjectListView_TreeListView
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                SuspendLayout();
    
                xenSnapshotsTreeListView1.AddCategoryItemName(CategoryEnum.CategoryA, "Item A", true, 0);
                xenSnapshotsTreeListView1.AddCategoryItemName(CategoryEnum.CategoryA, "Item B", false, 1);
    
                xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item A", "Item A foo", true);
                xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item A", "Item A bar", false);
    
                xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item B", "Item B foo");
                xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item B", "Item B bar", true);
    
                xenSnapshotsTreeListView1.LoadTree();
    
                ResumeLayout();
            }
        }
    }
    

    Form1.Designer.cs

    namespace ObjectListView_TreeListView
    {
        partial class Form1
        {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;
    
            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }
    
            #region Windows Form Designer generated code
    
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.components = new System.ComponentModel.Container();
                this.xenSnapshotsTreeListView1 = new ObjectListView_TreeListView.FooTreeListView();
                this.olvColumnAction = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
                this.olvColumnNumbSsToKeep = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
                ((System.ComponentModel.ISupportInitialize)(this.xenSnapshotsTreeListView1)).BeginInit();
                this.SuspendLayout();
                // 
                // xenSnapshotsTreeListView1
                // 
                this.xenSnapshotsTreeListView1.AllColumns.Add(this.olvColumnAction);
                this.xenSnapshotsTreeListView1.AllColumns.Add(this.olvColumnNumbSsToKeep);
                this.xenSnapshotsTreeListView1.CellEditUseWholeCell = false;
                this.xenSnapshotsTreeListView1.CheckBoxes = true;
                this.xenSnapshotsTreeListView1.CheckedAspectName = "IsChecked";
                this.xenSnapshotsTreeListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
                this.olvColumnAction,
                this.olvColumnNumbSsToKeep});
                this.xenSnapshotsTreeListView1.Cursor = System.Windows.Forms.Cursors.Default;
                this.xenSnapshotsTreeListView1.Dock = System.Windows.Forms.DockStyle.Fill;
                this.xenSnapshotsTreeListView1.GridLines = true;
                this.xenSnapshotsTreeListView1.Location = new System.Drawing.Point(0, 0);
                this.xenSnapshotsTreeListView1.MultiSelect = false;
                this.xenSnapshotsTreeListView1.Name = "xenSnapshotsTreeListView1";
                this.xenSnapshotsTreeListView1.ShowGroups = false;
                this.xenSnapshotsTreeListView1.ShowImagesOnSubItems = true;
                this.xenSnapshotsTreeListView1.Size = new System.Drawing.Size(800, 450);
                this.xenSnapshotsTreeListView1.TabIndex = 0;
                this.xenSnapshotsTreeListView1.UseAlternatingBackColors = true;
                this.xenSnapshotsTreeListView1.UseCompatibleStateImageBehavior = false;
                this.xenSnapshotsTreeListView1.View = System.Windows.Forms.View.Details;
                this.xenSnapshotsTreeListView1.VirtualMode = true;
                // 
                // olvColumnAction
                // 
                this.olvColumnAction.AspectName = "Action";
                this.olvColumnAction.Text = "Action";
                this.olvColumnAction.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
                this.olvColumnAction.Width = 200;
                // 
                // olvColumnNumbSsToKeep
                // 
                this.olvColumnNumbSsToKeep.AspectName = "NumberToKeep";
                this.olvColumnNumbSsToKeep.Text = "# To Keep";
                this.olvColumnNumbSsToKeep.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
                this.olvColumnNumbSsToKeep.Width = 65;
                // 
                // Form1
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(800, 450);
                this.Controls.Add(this.xenSnapshotsTreeListView1);
                this.Name = "Form1";
                this.Text = "Form1";
                ((System.ComponentModel.ISupportInitialize)(this.xenSnapshotsTreeListView1)).EndInit();
                this.ResumeLayout(false);
    
            }
    
            #endregion
    
            private FooTreeListView xenSnapshotsTreeListView1;
            private BrightIdeasSoftware.OLVColumn olvColumnAction;
            private BrightIdeasSoftware.OLVColumn olvColumnNumbSsToKeep;
        }
    }