ComboBox with the Treeview drop-down control

Source: Internet
Author: User

ComboBox with the Treeview drop-down control

Yes, as the title says, it is a Treeview in the drop-down box, but why do we need such controls? As a matter of fact, I have encountered this kind of requirement many times, for example:

When you encounter Hierarchical Data
Let the user select a node on the tree
Treeview is required, but the interface does not have enough space.
Used for options that are not frequently modified
When a dialog box looks too cumbersome and abrupt

In these cases, a common ComboBox does not meet the requirements. Whether you like it or not, it does not display every data entry and their structure at the same time.

I will explore the challenges of writing such a control in Windows Forms and share the common traps and final solutions around the toolstripdropdown control.

Tell you the truth in the drop-down list

When you consider this issue, most people will immediately tell you that the drop-down box is a form. After all, it shows many forms of Behavior:

Its location can be beyond the range of the parent form customer Zone
Able to process mouse and keyboard input independently
It appears on the top layer, and cannot be hidden in other windows.

However, this is a fundamental mistake. The drop-down box has something very different from the form:

When they are opened, the focus of the parent form will not be transferred.
When they capture mouse/keyboard input, the focus of the parent form will not be transferred.
When interacting with their child controls/items, do not switch the focus

These points indicate that they are different from forms and controls in different ways of working. How can these features be possessed?

Where is the error?

With this in mind, the following "great" method will fail to implement the drop-down box in Windows Forms:

Use a form (ignorance period)

Therefore, you decided to use a form to implement the drop-down box. If you set this form to borderless and do not display icons on the taskbar, you may have set the topmost attribute. You may even set its parent form, regardless of the differences in details. The user clicks the drop-down button and your form is displayed. When the focus is switched to the drop-down box form, a slight blinking occurs. The title bar of the parent form changes color, and the form shadow fades (under aero ). Then, you can select the form from which the drop-down list control is located, and the form is closed. Until you click the parent form again, the title bar changes color, and the form shadow becomes deeper. There will be another embarrassing flash. What is the final result? Additional clicks are required for a very poor user experience.

Still use a form to make it smarter

Those who have tried the above methods (or have a deeper understanding of winforms/Win32 problems) will try their best to solve the initial problem, that is, the drop-down form will grab the focus from the parent form. The secret is that the form class has a protected attribute called showwithoutactivation (in most cases), which skips the form activation when the form is displayed:

C # code?
123 protected override bool ShowWithoutActivation {    get { return true; }}

If showwithoutactivation is ineffective, you can force the ws_ex_noactivate flag of the control style by overwriting the createparams attribute:

C # code?
12345678 protected override CreateParams CreateParams {    get {        const int WS_EX_NOACTIVATE = 0x08000000;        CreateParams p = base.CreateParams;        p.ExStyle |= WS_EX_NOACTIVATE;        return p;    }}

Please note that this will not prevent it from getting focus when the form is visible, that is, if you click the form, this problem will still occur. Therefore, the next challenge to prevent focus from being successfully obtained when the form is displayed is to prevent it from gaining focus at any time. This is the most common among all Win32 interception window message technologies. Specifically, it is set to ma_noactivate in response to the wm_mouseactivate message (sent when the click event is triggered but no focus is obtained) (focus is not changed to process the Click Event ):

C # code?
12345678 protected override void DefWndProc(ref Message m) {    const int WM_MOUSEACTIVATE = 0x21;    const int MA_NOACTIVATE = 0x0003;    if (m.Msg == WM_MOUSEACTIVATE)        m.Result = (IntPtr)MA_NOACTIVATE;    else        base.DefWndProc(ref m);}

In fact, this can work. When you click the drop-down form, the focus will not be transferred. The problem is that the drop-down is not just the form itself. If you click any control on the form, you will find that the previous work is done in white. The message interception technology that can be processed on a form can be used on controls, but some controls (such as Treeview) cannot. For example, selecting a tree node will always shift the focus.

So, after this problem, you left a bunch of subclass controls and the underlying code. What did you get? A good user experience, at least before you click it, is still useless.

Replace it with controls

Finally, you may think that using a control and directly adding it to the parent form may be a smart way to avoid focus issues ......, Yes, it completely solves this problem. Unfortunately, the range of the drop-down box cannot exceed the customer area of the parent form. You may think this is also quite good, as long as your parent form has enough space under the drop-down control position, but this does not fully implement the primary purpose of using a drop-down box. This "solution" is also not available.

Advanced, toolstripdropdown

As a matter of fact, as we observe the behavior of the drop-down list, we will find that they are more similar to menus rather than forms. Menus can independently process keyboard and mouse input without competing for focus from their parent forms. So let's take a look at the contextmenustrip control, an obvious example in the Framework library. This is a inherited control. In all other aspects, it acts like a component. But more importantly, this is not a form, thus avoiding the focus issue. In fact, compared to other elements in focus processing, menus represent the third category:

Form: gets the focus from the parent form, and the focus system is independent of the control of the parent form.
Controls: gets focus from their parent controls, but belongs to the parent form, and is the same focus system as other controls.
Menu: Share the focus system with its parent control and child control.

This behavior accurately represents our needs in the drop-down box, but contextmenustrip has a very clear purpose. After observing its inheritance chain, you can find toolstrip, toolstripdropdown, and toolstripdropdownmenu. The first one does not represent a menu, so it is useless for us. The second is the base class of the drop-down box, which is a good start. It is worth mentioning that toolstripdropdown does not support scrolling (and it can be used as a derived class in the future). However, in the method I used to implement the drop-down box, automatic scrolling is not necessary.

The derived classes (tags, buttons, etc.) of any toolstripitem can be added to the drop-down list box, including toolstripcontrolhost (although I did not select to simply put a Treeview control into the drop-down box, in this way, there will still be some weird focus issues, with indirect overhead not mentioned ). The drop-down list must contain at least one item, even if it does not occupy space. As a derived class of a control class, it can be re-painted by rewriting the onpaint method, and simultaneously respond to mouse and keyboard events. Combined with focus-free behavior, it provides the tools needed to meet all the actions we expected in the create drop-down box.

Implementation

The full-featured Treeview is not required in the drop-down control, so we implement it based on a similar (but simpler) data model.

The dropdownbase class includes the basic functions of the Editable part, which are supported during rendering, event processing and design, and droppeddown state management. The data model is centered around the combotreenode class similar to treenode. It and its collection type combotreenodecollection constitute a circular relationship for creating a tree. The combotreebox class is the main implementation class of the dropdownbase class. It owns and controls data models and visualizes selected nodes. It also has the combotreedropdown -- actual drop-down list (derived class of toolstripdropdown class ).

In this implementation, the data model and view are completely separated, and nodes can be defined and manipulated separately in the control/drop-down box.

Model -- combotreenode class and combotreenodecollection collection

The combotreenode class is an atomic class that is used to save the node name, text, status, and relationships with other nodes in the model tree. The combotreenodecollection set indicates the subtree and is associated with a parent node. The only exception to this rule is that the child tree that has not been added to the control and the root set belongs to the control. Regardless of the relationship between the class and the combotreebox class, additional attributes and behaviors are attached to the combotreebox control.

The combotreenodecollection set implements ilist <combotreenode>, because the order of nodes is very important. An internal Object List <combotreenode> is used as a backup storage field. The collection is responsible for assigning the parent node of each node to ensure that the tree's collectionchanged event (from the inotifycollectionchanged Interface) is recursively triggered:

C # code?
123456789 public void Add(ComboTreeNode item) {    innerList.Add(item);    item.Parent = node;    // changes in the subtree will fire the event on the parent    item.Nodes.CollectionChanged += this.CollectionChanged;    OnCollectionChanged(        new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)    );}

It also implements non-generic ilist to provide compatibility with the collectioneditor class used by the Windows Form Designer.

View-drop-down box itself

The combotreedropdown class displays and provides and models. visually, nodes can be expressed as a series of rows. Their upper bound is determined by their depth in the tree, and their visibility is determined by whether their parent nodes are extended. The visibility of each node is determined by recursively checking the expanded attribute of the parent node of the node. This node is visible only when the root node of the tree is not folded to all nodes in its path:

C # code?
1234567891011 internal bool IsNodeVisible(ComboTreeNode node) {    bool displayed = true;    ComboTreeNode parent = node;    while ((parent = parent.Parent) != null) {        if (!parent.Expanded) {            displayed = false;            break;        }   }   return displayed;}

Rendering

A set of simple and reasonable rules specify how to draw connection lines between nodes, in fact, it is feasible to separate different arrangement combinations and cache the result bitmap separately to save time and memory (these independent arrays can be expressed by bitmapinfo struct ). Expanding or folding a node changes the superset of visible nodes. Add/delete nodes from the tree. Changing the size and font of the parent control will cause the cache set to be rebuilt.

Scroll

Scroll is implemented manually. Based on a very simple principle: the adjacent subset of visible nodes depends on the maximum height of the drop-down box and is used as an offset.

The range of the scroll bar is equal to the total number of visible items-the maximum number of subset items that can be displayed in the drop-down list box.
The position of the scroll bar can be expressed as the percentage of the offset in the relative range.
The size of the scroll bar slider is equal to the percentage of entries in the scroll area to the total number of visible items.

To actually render the drop-down box, determine the position of the nodes in the visible rolling range relative to the top. The nodeinfo object is used to indicate the visual attributes of each node. Each node has two painting operations: indent, node icon, handle, connection line, and node text, which are expressed in a cached bitmap. If the number of nodes in the scroll range is smaller than the total number of visible nodes, the scroll bar is drawn at the same time.

When responding to a mouse event, you also need to draw the boundary of each node and the scroll bar (saved in scrollbarinfo. If the mouse pointer falls within the range of an item, the associated combotreenode object can be determined. Click the Add/Remove handle next to the node to collapse or expand it. clicking any position on the node changes the selection and closes the drop-down box. (It is worth noting that some mouse features, such as dragging a scroll bar and holding the button to automatically repeat the behavior will be a bit complicated and not within the scope of this article. However, the most important question to be asked is that the mousewheel event in the drop-down box can be handled by the parent control .)

Highlight project vs selected node

Each time the drop-down list is displayed, It is scrolled to (highlighted) the node corresponding to the selectednode attribute of the parent control. The highlighted project and the selected node are two concepts, just like the normal ComboBox drop-down box. These rules apply:

Move the mouse over a project to highlight it, but the selected node will not change unless you click this item.
Using the upper/lower key navigation on the keyboard will change both the highlighted item and the selected node.
Scrolling (including the scroll wheel, keyboard (Previous Page/next page), or using a scroll bar) does not change the highlighted item or the selected node.

Controller-parent Control

Combotreebox is the Controller in this implementation. In addition, it exposes operations for managing data models and access to views-encapsulating all concepts into a reusable windows form control. Therefore, it combines the ComboBox and Treeview controls:

Assign a name to each node to access the node through the set.
Use imageindex/imagekey to specify an icon for each node.
Save the expansion status of each node.
The selectednode attribute is used to obtain/set the user's selection in the tree.
The pathseparator and path attributes indicate the path strings of the selected node.
The beginupdate ()/endupdate () method implements batch addition.
The nodes of the expandall ()/collapseall () method management tree are all collapsed/all expanded.
Sort the tree (perform recursive sorting using the default or custom comparator ).

Provides the following additional functions:

The recursive enumerator (allnodes) traverses the entire tree.
Determine whether a path is composed Based on the node name or text.
Use the path attribute to set the selected node.
Expandedimageindex/expandedimagekey: displays a different icon for nodes that can be expanded.

The following functions of the Treeview control are not implemented in combotreebox:

Scroll horizontally (not noticeable in the drop-down box ).
Check box next to a node (multiple choice ).
Customize the appearance of an element or use the event redraw control.
Selectedimageindex/selectedimagekey attribute (in fact, I think it is useless ).
Node prompts, drag-and-drop functions, and other functions beyond the drop-down list box.
A sorted attribute is used to create an automatic sorting tree. Instead, it can be sorted as needed.

Conclusion

Combotreebox is an example of a custom drop-down list box and a solution for creating a Windows form. The toolstripdropdown component makes this all possible, even if (in this example) You need to manually implement the content in the drop-down box. This is also a useful exercise for writing custom controls to keep the original appearance, behavior, and input processing. I'm particularly satisfied with the elegant scrolling mechanism and the use of Bitmap caching, these enable me to successfully fill hundreds of thousands of nodes in the drop-down box without causing performance loss (even if such a scale is considered unsuitable for the drop-down box ).

By demonstrating how to implement a custom drop-down box, I hope it can serve as a reference and discover more of its usefulness.

Download

Source code
(Reference System. Design and windowsbase Based on. NET 3.5)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.