Click here to Skip to main content
15,916,463 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I've got a "simple" app (Windows Forms) in which I'd like to have a ComboBox (takes up one line) with dropdown list (display multiple lines), but I'd like to be able to select multiple items (like a ListBox).

I've tried to "fake it" by designing a control with a ComboBox (with no entries) and an invisible ListBox with multiple lines that becomes visible when the ComboBox is clicked. This _almost_ works but other items on the form (Labels, RadioBoxes, etc.) show through the ListBox when it is made visible.

Any help gratefully accepted.
Posted

First, to the issue of other Controls "showing through:" unless you are using a UserControl that implements transparency (and getting a Windows Form UserControl to have any useful transparency is a dubious proposition), the only way any other control can "show through," is if it is placed on top of your UserControl. So, use 'BringToFront() on your UserControl at the right time.

There's a much easier way to achieve what you want, whether as just another Control on the Form, or encapsulated within a UserControl:

At design-time, or in code:

1. add a ListView Control: set its 'View property to 'Details

2. add one ColumnHeader to the ListView's Columns collection, with its Text property set to something like "Pick one or more items" : specify a width for that ColumnHeader about equal to the width of the ListView

3. set the ListView's MultiSelect property to 'true.

4. set the design-time ListView's height property so only text in ColumnHeader1 is visible.

5. add items you want the user to select one or more of to the ListView's Items collection of ListViewItems, assign their 'Text property to whatever's appropriate for your use-case.

In the main Form's code this will prepare you to handle the user's selection, and creating the drop-down and fold-up effects:
C#
private int lvDefaultHeight;

private ListView.SelectedListViewItemCollection lvSelectedItems;

private void YourMainForm_Load(object sender, EventArgs e)
{
    lvDefaultHeight = listView1.Height;
    lvSelectedItems = new ListView.SelectedListViewItemCollection(listView1);
}
To make the ListItems appear when you click on ColumnHeader1 (i.e., to drop-down), handle the ListView's ColumnClick Event:
C#
private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
{
    listView1.Height = lvDefaultHeight * listView1.Items.Count;
}
So, now, at run-time, the user clicked on the ColumnHeader1, the List dropped down, and the user selects one, or any, or all, items. So: how to enable the user to "finish" ? One way is to treat the user's hitting the Return key to indicate that they are done. Handling the ListView KeyDown event is one way:
C#
private void listView1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Return)
    {
        // capture the selection before you restore
        // the ListView to its default height:
        // or the selection will cleared !
        lvSelectedItems = listView1.SelectedItems;

        // for testing only
        foreach (ListViewItem lvi in lvSelectedItems)
        {
            Console.WriteLine(lvi.Text);
        }
        //

        // collapse the ListView to default height
        listView1.Height = lvDefaultHeight;
    }
}
By the way, you could obviously do a lot more "fancy stuff" with this. When I implement something like this, my usual choice is to make it a UserControl, and expose the appropriate Properties, and/or raise Events, for "consumers" of the UserControl, because I am a paid-up member of the cult of Reusability :)
 
Share this answer
 
Comments
rmw256 26-Aug-13 23:32pm    
> ... There's a much easier way to achieve what you want, whether as just another Control on the Form, or encapsulated within a UserControl: ... add a ListView Control: ...

Thank-you. This certainly appears to have possibilities. Looks like I'm about to embark on a study of the (heretofore) elusive ListView control. With this and other suggestions, I think I can see light at the end of the tunnel.

> ... because I am a paid-up member of the cult of Reusability

"Hear, hear!" and "Amen!"
Sergey Alexandrovich Kryukov 26-Aug-13 23:38pm    
It looks informative, up-voted.
I voted 4, not 5, by one minor reason: names like "listView1_ColumnClick" violate (good) Microsoft naming conventions and are bad for maintenance, as they are not semantic. Yes, the designer generates such names, but just because it "doesn't know" your semantic. It does not mean you should actually use such names. What's the refactoring engine for? Besides, not using the designer for events and using anonymous handlers is much beneficial in very many cases.
—SA
The idea is basically right, and I feel you are almost there. All you need is to sort out your alignment. And remember that you often need to isolate your controls in your layout. In particular, you should better put your list box and text box making a combo together on a separate Panel. Always use Dock property. In the combo case, the text box is usually docked on top, and the list box could be docked as Fill.

—SA
 
Share this answer
 
Comments
rmw256 25-Aug-13 16:45pm    
First, thank-you for your reply. I do appreciate it.

"... All you need is to sort out your alignment ..."

I'm sorry, I'm not sure what you mean by this.

"... on a separate panel ..."

So having them on this (separate) "UserControl" is insufficient? I need a Panel in my UserControl?

"Always use Dock property."

I tried to "start" building my UserControl with a Panel (Dock = Fill) and then on top of that added a ComboBox (Dock = Top) and a ListBox (Dock = Fill). Unfortunately, with "Dock = Fill", the ListBox filled the Panel, overlaying the ComboBox. What am I missing?
Sergey Alexandrovich Kryukov 25-Aug-13 16:51pm    
Not sure. You did not say anything about using control. I only say that your functionality is wrong only in one aspect: layout. Even if this is just the user control, no controls should go underneath.

The situation when your fill overlays the other control is usual. It simply means that you confused parent-child relationships. Make sure that docked controls have the same parent. If you use designer (the usual source of all kinds of mistakes which people don't do in code), you need to click on parent (say, panel) first, put a new control on it, dock it, then click on parent again (the step which is easy to miss and this way screw up) and add another child control. The last to dock should be docked as Fill.

—SA
rmw256 25-Aug-13 18:05pm    
I really do appreciate your taking the time to help me and I'm sorry if I'm still seeming a bit dense. My experience with Windows Forms et al is limited, so I'm clearly missing something.

"Make sure that docked controls have the same parent."

When I run my app and check the "Parent" property of the ComboBox and the ListBox, they both have the same Parent (the UserControl).

Here is what I currently have (leaving out stuff that appears to be irrelevant):

The "definition" of my dropdown list control:

public partial class CDropDownListBoxControl : UserControl
{
...
}

And the contents of the "InitializeComponent()":

private void InitializeComponent()
{
this.dropdownCB = new System.Windows.Forms.ComboBox();
this.dropdownLB = new System.Windows.Forms.ListBox();
this.SuspendLayout();
...
this.dropdownCB.Dock = System.Windows.Forms.DockStyle.Top;
...
this.dropdownLB.Dock = System.Windows.Forms.DockStyle.Fill;
...
this.Controls.Add(this.dropdownLB);
this.Controls.Add(this.dropdownCB);
...
this.ResumeLayout(false);
}

So to the best of my knowledge, I've got things set up correctly.

I'm not sure if it's relevant, but it appears that if my dropdown list control has a smaller TabStop value than nearby controls (e.g. RadioButtons), the nearby controls "show through" the dropdown list. If the dropdown list control has a _larger_ TabStop than the nearby controls, the nearby controls do _not_ show through.
Sergey Alexandrovich Kryukov 25-Aug-13 21:37pm    
When I wrote about "same parent", I meant your problem when you reported than your filling control overlays the control docked on top. What's the code you are showing now? It is the auto-generated code by the designer, of your user control? Now, using tab is normally disabled, and the change is selected child controls is done via other keys and mouse events. In other words, combo box creates and illusion that it has no sub-controls (but if you examine it with raw Windows API, you can see that they have separate HWNDs).

Now, "show through" should not depend on tab stops. If you see such thing, you might have changed z-order, which shows some overlapped control on top. If something is seen through, it means that you can see the space between controls, and something is placed underneath. You should not do both of these things. It looks like your problem is also the placement of the instance of the user control itself. Nothing should be placed under controls except panels and forms.

Also, I think I misunderstood your idea. I though you are going to create and "advanced" combo box by combining a list box and a text box (this is more or less usual technique). But I fail to understand why would you use combination of combo with list box.

As to the layout problem, I sometimes advise to use one simple technique: during development, use different distinct contrast background colors for all controls and the form. Then you will immediately see the problem in the layout and avoid confusion.

—SA
rmw256 26-Aug-13 23:25pm    
Ah, z-order. Thank-you. That solves the "bleed-through" problem.

> ... But I fail to understand why would you use combination of combo with list box.

The use of ComboBox is simply because it displays with that lovely, little "dropdown" arrow.

> ... during development, use different distinct contrast background colors for all controls and the form. ...

Another thank-you. A tactic I will be sure to take advantage of in the future.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900