我可以拥有一个不确定设置的 TreeListView 仅以编程方式断言(并拒绝用户使用)吗?

Can I have a TreeListView with the indeterminate setting asserted programmatically only (and denied to the user)?

我正在使用 Phillip Piper 的 ObjectListView 包装器,特别是 TreeListView

我的TreeListView:

我将 TriStateCheckboxes 设置为 True,因为我希望我的 TreeListView 能够在 TreeListView’s 复选框中显示 indeterminate 符号。但是,我不希望用户能够明确地将复选框设置为 indeterminate 值。相反,当用户单击复选框时,我希望它仅在选中和未选中之间切换。如果一个分支复选框及其所有子复选框都被选中,并且子复选框过渡为选中和未选中的混合状态,我希望分支的复选框显示 indeterminate 符号。

换句话说,不确定状态只能以编程方式断言,然后仅在分支的子项的复选框不仅全部选中或不完全全部未选中时才在分支上断言。

意识到三态复选框循环:选中、不确定、未选中,我尝试在用户单击选中的复选框时将不确定状态强制为未选中状态,但这没有用,即复选框状态为执行我的 ObjTreeListViewPreview_ItemChecked 代码后,TreeListView 保持不变。

注意:class ClsTreeListViewPreview 派生自 TreeListView

private void ObjTreeListViewPreview_ItemChecked(object sender, ItemCheckedEventArgs e)
{

    ClsTreeListViewPreview objTreeListViewPreview = (ClsTreeListViewPreview)sender;

    if (objTreeListViewPreview.MouseMoveHitTest.Item.CheckState == CheckState.Indeterminate)
    {
        objTreeListViewPreview.MouseMoveHitTest.Item.CheckState = CheckState.Unchecked;
    }

}

tristatehierarchical 复选框的 TreeListView 可以满足我的要求吗?如果需要,哪些委托和方法是必需的?

我有一个 Winforms TreeListViewtristate checkboxes 在拒绝用户使用 indeterminate 设置的地方工作。只有当用户将复选框的 child objects 设置为选中和未选中的混合时,indeterminate 设置才会出现在复选框中。

HierarchicalCheckboxes 未使用。所有复选框设置都是手动管理的。

它使用 CheckStatePutter、ItemChecked 事件处理程序和 HeaderCheckBoxChanging 事件处理程序(当使用 header 复选框时)来手动管理复选框设置

演示代码如下

代码是 self-contained(ObjectListView.dll 除外)。创建一个空窗体的Winforms项目和copy/paste下面的两个类

主窗体

using System.Windows.Forms;

namespace NovaSysEng
{
    public partial class ClsFormMain : Form
    {
        public ClsFormMain()
        {
            InitializeComponent();

            ClsTriStateTreeListView objFooTree = new ClsTriStateTreeListView(showHeaderCheckBox: true)
            {
                Location = new System.Drawing.Point(0, 0),
                Dock = DockStyle.Fill
            };

            // The model supports the creation of a tree hierarchy whose depth is not known until run time.
            // Each item requires an id and a parentId just like in a DataTreeListView.
            // https://whosebug.com/questions/9409021/id-parentid-list-to-hierarchical-list

            objFooTree.AddItem(id: 1, parentId: 0, name: "A", isChecked: false, foo: "foo A");
            objFooTree.AddItem(id: 2, parentId: 1, name: "A.1", isChecked: false, foo: "foo A.1");
            objFooTree.AddItem(id: 3, parentId: 1, name: "A.2", isChecked: false, foo: "foo A.2");
            objFooTree.AddItem(id: 4, parentId: 1, name: "A.3", isChecked: false, foo: "foo A.3");
            objFooTree.AddItem(id: 5, parentId: 0, name: "B", isChecked: false, foo: "foo B");
            objFooTree.AddItem(id: 6, parentId: 5, name: "B.1", isChecked: false, foo: "foo B.1");
            objFooTree.AddItem(id: 7, parentId: 5, name: "B.2", isChecked: false, foo: "foo B.2");
            objFooTree.AddItem(id: 8, parentId: 5, name: "B.3", isChecked: false, foo: "foo B.3");
            objFooTree.AddItem(id: 9, parentId: 8, name: "B.3.1", isChecked: false, foo: "foo B.3.1");

            SuspendLayout();
            Controls.Add(objFooTree);
            objFooTree.Load();
            objFooTree.ExpandAll();
            objFooTree.AutoResizeColumns();
            ResumeLayout();
        }
    }
}

Class ClsTriStateTreeListView

using BrightIdeasSoftware;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace NovaSysEng
{
    class ClsTriStateTreeListView : BrightIdeasSoftware.TreeListView
    {
        private readonly bool showHeaderCheckBox = false;

        private List<ClsItem> itemList;
        readonly OLVColumn olvColumnName;
        readonly OLVColumn olvColumnFoo;

        /// <summary>
        /// CTOR
        /// </summary>
        public ClsTriStateTreeListView() : this(false)
        {
        }

        /// <summary>
        /// CTOR
        /// </summary>
        /// <param name="showHeaderCheckBox"></param>
        public ClsTriStateTreeListView(bool showHeaderCheckBox) : base()
        {
            this.showHeaderCheckBox = showHeaderCheckBox;

            CheckBoxes = true;
            View = View.Details;
            UseAlternatingBackColors = true;
            MultiSelect = false;
            CheckedAspectName = "";
            GridLines = true;
            OwnerDraw = true;
            ShowGroups = false;
            TriStateCheckBoxes = true;
            VirtualMode = true;
            HierarchicalCheckboxes = false;

            ItemChecked += FooTreeListView_ItemChecked;
            HeaderCheckBoxChanging += ClsFooTreeListView_HeaderCheckBoxChanging;

            itemList = new List<ClsItem>();

            olvColumnName = new BrightIdeasSoftware.OLVColumn
            {
                AspectName = "Name",
                Text = "Name",
                HeaderCheckBoxUpdatesRowCheckBoxes = false
            };

            AllColumns.Add(olvColumnName);
            Columns.Add(olvColumnName);

            olvColumnFoo = new BrightIdeasSoftware.OLVColumn
            {
                AspectName = "Foo",
                Text = "Foo",
            };

            AllColumns.Add(olvColumnFoo);
            Columns.Add(olvColumnFoo);

            // Don't allow the user to put a checkbox in the indeterminate state.
            // When clicked, a checkbox cycles through 3 states in the order: checked, indeterminate, unchecked
            // So, if the checkbox state is Indeterminate, force it to unchecked.
            // An exception is when a checkbox has the Indeterminate setting and the model IgnoreIndeterminate
            // property is true. This is for the case when we want to set the checkbox to Indeterminate
            // programattically and have it stick.
            CheckStatePutter = delegate (Object rowObject, CheckState newValue)
            {
                if (newValue == CheckState.Indeterminate && ((ClsItem)rowObject).IgnoreIndeterminate)
                {
                    return CheckState.Indeterminate;
                }
                else if (newValue == CheckState.Indeterminate)
                {
                    return CheckState.Unchecked;
                }
                else
                {
                    return newValue;
                }
            };

            CanExpandGetter = delegate (Object x)
            {
                return ((ClsItem)x).Children.Count > 0;
            };

            ChildrenGetter = delegate (Object x)
            {
                return ((ClsItem)x).Children;
            };
        }

        /// <summary>
        /// If the user clicks on the HeaderCheckBox, set all descendants accordingly
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClsFooTreeListView_HeaderCheckBoxChanging(object sender, HeaderCheckBoxChangingEventArgs e)
        {
            ItemChecked -= FooTreeListView_ItemChecked;

            foreach (ClsItem topLevelItem in this.Objects)
            {
                if (e.NewCheckState == CheckState.Checked)
                {
                    CheckObject(topLevelItem);
                    CheckDescendantCheckboxes(topLevelItem);
                }
                else
                {
                    UncheckObject(topLevelItem);
                    UncheckDescendantCheckboxes(topLevelItem);
                }
            }

            ItemChecked += FooTreeListView_ItemChecked;
        }

        /// <summary>
        /// Set the model IgnoreIndeterminate property to true then set the checkbox to Indeterminate
        /// using the CheckIndeterminateObject method. CheckStatePutter will be invoked and the 
        /// Indeterminate setting will be ignored for this particular case. Finally, set the model
        /// IgnoreIndeterminate property to false so an Indeterminate checkbox setting will otherwise
        /// be change to an Unchecked setting.
        /// </summary>
        /// <param name="rowObject"></param>
        public override void CheckIndeterminateObject(object rowObject)
        {
            ((ClsItem)rowObject).IgnoreIndeterminate = true;
            base.CheckIndeterminateObject(rowObject);
            ((ClsItem)rowObject).IgnoreIndeterminate = false;
        }

        private void UncheckDescendantCheckboxes(ClsItem objTreeItemModel)
        {
            foreach (ClsItem child in GetChildren(objTreeItemModel))
            {
                UncheckObject(child);
                UncheckDescendantCheckboxes(child);
            }
        }
        private void CheckDescendantCheckboxes(ClsItem objTreeItemModel)
        {
            foreach (ClsItem child in GetChildren(objTreeItemModel))
            {
                CheckObject(child);
                CheckDescendantCheckboxes(child);
            }
        }

        private void FooTreeListView_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            Console.WriteLine("In FooTreeListView_ItemChecked");

            ItemChecked -= FooTreeListView_ItemChecked;

            ClsItem objTreeItemModel = (ClsItem)((OLVListItem)e.Item).RowObject;

            //set descendant checkboxes
            if (IsChecked(objTreeItemModel))
            {
                CheckDescendantCheckboxes(objTreeItemModel);
            }
            else
            {
                UncheckDescendantCheckboxes(objTreeItemModel);
            }

            ClsItem objTreeItemModelParent = (ClsItem)GetParent(objTreeItemModel);

            //Set ancestor checkboxes 
            while (objTreeItemModelParent != null)
            {
                bool allChildrenChecked = true;
                bool allChildrenUnchecked = true;

                // If all 1st generation children of the parent are checked, set parent to checked
                // If all 1st generation children of the parent are unchecked, set parent to unchecked
                // Otherwise, set parent to indeterminte
                foreach (ClsItem child in GetChildren(objTreeItemModelParent))
                {
                    if (IsChecked(child))
                    {
                        allChildrenUnchecked = false;
                    }

                    if (!IsChecked(child))
                    {
                        allChildrenChecked = false;
                    }

                    if (IsCheckedIndeterminate(child))
                    {
                        allChildrenUnchecked = false;
                        allChildrenChecked = false;
                        break;
                    }
                }

                if (allChildrenChecked)
                {
                    CheckObject(objTreeItemModelParent);
                }
                else if (allChildrenUnchecked)
                {
                    UncheckObject(objTreeItemModelParent);
                }
                else
                {
                    CheckIndeterminateObject(objTreeItemModelParent);
                }

                objTreeItemModelParent = (ClsItem)GetParent(objTreeItemModelParent);

            } // while


            if (showHeaderCheckBox)
            {
                bool allChecked = true;
                bool allUnchecked = true;

                foreach (ClsItem topLevelItem in this.Objects)
                {
                    if (IsChecked(topLevelItem))
                    {
                        allUnchecked = false;
                    }

                    if (!IsChecked(topLevelItem))
                    {
                        allChecked = false;
                    }

                    if (IsCheckedIndeterminate(topLevelItem))
                    {
                        allUnchecked = false;
                        allChecked = false;
                        break;
                    }
                }

                HeaderCheckBoxChanging -= ClsFooTreeListView_HeaderCheckBoxChanging;

                if (allChecked)
                {
                    CheckHeaderCheckBox(olvColumnName);
                }
                else if (allUnchecked)
                {
                    UncheckHeaderCheckBox(olvColumnName);
                }
                else
                {
                    CheckIndeterminateHeaderCheckBox(olvColumnName);
                }

                HeaderCheckBoxChanging += ClsFooTreeListView_HeaderCheckBoxChanging;

            } // if (showHeaderCheckBox)

            ItemChecked += FooTreeListView_ItemChecked;

        } // private void FooTreeListView_ItemChecked(object sender, ItemCheckedEventArgs e)

        public void AddItem(int id, int parentId, string name, bool? isChecked, string foo)
        {
            itemList.Add(new ClsItem(id, parentId, name, isChecked, foo));
        }

        /// <summary>
        /// After using the AddItem method to add tree items, call this method to load the model into the TreeListView.
        /// https://whosebug.com/questions/9409021/id-parentid-list-to-hierarchical-list
        /// </summary>
        public void Load()
        {
            itemList.ForEach(item => item.Children = itemList.Where(child => child.ParentId == item.Id).ToList());
            List<ClsItem> topItems = itemList.Where(item => item.ParentId == 0).ToList();
            ((OLVColumn)this.Columns[0]).HeaderCheckBox = this.showHeaderCheckBox;
            Roots = topItems;
        }

        /// <summary>
        /// This model supports the creation of a tree hierarchy whose depth is not known until run time
        /// https://whosebug.com/questions/9409021/id-parentid-list-to-hierarchical-list
        /// </summary>
        private class ClsItem
        {
            int id;
            int parentId;
            string name;
            bool? isChecked;
            string foo;
            List<ClsItem> children = new List<ClsItem>();
            bool ignoreIndeterminate;

            public ClsItem(int id, int parentId, string name, bool? isChecked, string foo)
            {
                this.id = id;
                this.parentId = parentId;
                this.name = name;
                this.isChecked = isChecked;
                this.foo = foo;
            }

            public bool? IsChecked { get => isChecked; set => isChecked = value; }
            public string Name { get => name; set => name = value; }
            public string Foo { get => foo; set => foo = value; }
            public int Id { get => id; set => id = value; }
            public int ParentId { get => parentId; set => parentId = value; }
            public List<ClsItem> Children { get => children; set => children = value; }
            public bool IgnoreIndeterminate { get => ignoreIndeterminate; set => ignoreIndeterminate = value; }
        }
    }
}