C# Programming Tutorial - Programming Advanced Winforms in C#

01
of 10

Using Controls in Winforms - Advanced

WinForm with ComboBox

In this C# programming tutorial, I'll be concentrating on the advanced controls such as ComboBoxes, Grids, and ListViews and showing you the way you'll most likely use them. I'm not touching data and binding until a later tutorial.Let's begin with a simple control, a ComboBox.

ComboBox Winform Control

A "combo" is so called because it's a combination of a TextBox and a ListBox. It provides a variety of text editing methods all rolled up in one small control. A DateTimePicker control is just an advanced Combo with a panel that can pop up. But we'll stick to the basic ComboBox for now.

At the heart of a Combo is an items collection and the simplest way to populate this is drop a combo on the screen, select properties (if you can't see the properties windows, click View on the top Menu and then Properties Window), find items and click the ellipses button. You can then type in the strings, compile the program and pull the combo down to see choices.

  • One
  • Two
  • Three

Now stop the program and add a few more numbers: four, five.. up to ten. When you run it you'll only see 8 because that's the default value of MaxDropDownItems. Feel Free to set it to 20 or 3 and then run it to see what it does.

It's annoying that when it opens it says comboBox1 and you can edit it. That's not what we want. Find the DropDownStyle property and change DropDown to DropDownList.(It's a Combo!). Now there's no text and it's not editable. You can select one of the numbers but it always opens blank. How do we select a number to start with? Well it's not a property you can set at design time but adding this line will do that.

comboBox1.SelectedIndex =0;

Add that line in the Form1() constructor. You have to view the code for the form (in the Solution Explorer, right click on From1.cs and click View Code. Find InitializeComponent(); and add that line immediately after this.

If you set the DropDownStyle property for the combo to Simple and run the program you'll get nothing. It won't select or click or respond. Why? Because at design time you must grab the lower stretch handle and make the whole control taller.

Source Code Examples

  • Download the examples (zip code)

On the next page : Winforms ComboBoxes Continued

02
of 10

Looking at ComboBoxes Continued

Working with a ComboBox

In example 2, I've renamed the ComboBox to combo, changed the combo DropDownStyle back to DropDown so it can be edited and added an Add button called btnAdd. I've double clicked the add button to create an event btnAdd_Click() event handler and added this event line.

private void btnAdd_Click(object sender, System.EventArgs e)
{
  combo.Items.Add(combo.Text) ;
}

Now when you run the program, type in a new number, say Eleven and click add. The event handler takes the text you typed in (in combo.Text) and adds it to the Combo's items collection. Click on the Combo and we now have a new entry Eleven. That's how you add a new string to a Combo. To remove one is slightly more complicated as you have to find the index of the string you wish to remove then remove it. The method RemoveAt shown below is a collection method to do this. you just have to specify which item in the Removeindex parameter.

combo.Items.RemoveAt( RemoveIndex ) ;

will remove the string at position RemoveIndex. If there are n items in the combo then the valid values are 0 to n-1. For 10 items, values 0..9.

In the btnRemove_Click method, it looks for the string in the text box using

int RemoveIndex = combo.FindStringExact( RemoveText ) ;

If this doesn't find the text it returns -1 otherwise it returns the 0 based index of the string in the combo list. There's also an overloaded method of FindStringExact which lets you specify where you start the search from, so you can skip the first one etc if you have duplicates. This could be handy for removing duplicates in a list.

Clicking btnAddMany_Click() clears the text from combo then clears the contents of the combo Items collection then calls combo.AddRange( to add the strings from the values array. After doing this, it sets the combo's SelectedIndex to 0. This shows the first element in the combo. If you are doing addition or deletion of items in a ComboBox then it's best to keep track of which item is selected. Setting SelectedIndex to -1 hides the selected items.

The Add Lots button clears the list and adds 10,000 numbers. I have added combo.BeginUpdate() and combo,EndUpdate() calls around the loop to prevent any flicker from Windows trying to update the control. On my three year old PC it takes just over a second to add 100,000 numbers into the combo.

On the next page Looking at ListViews

03
of 10

Working with ListViews in C# Winforms

The sample ListView and controls

This is a handy control for displaying tabular data without the complexity of a grid. You can display items as large or small icons, as a list of icons in a vertical list or most usefully as a list of items and subitems in a grid and that's what we'll do here.

After dropping a ListView on a form click the columns property and add 4 columns. These will be TownName, X, Y and Pop. Set the text for each ColumnHeader. If you can't see the headings on the ListView (after you've added all 4), set the ListView's View Property to Details. If you view the code for this example then browse down to where it says Windows Form Designer code and expand the region you see the code that creates the ListView. It's useful to see how the system works and you can copy this code and use it yourself.

You can set the width for each column manually by moving the cursor over the header and dragging it. Or you can do it in the code visible after you expanded the form designer region. You should see code like this:

this.Population.Text = "Population";
this.Population.Width = 77;

For the population column, Changes in the code are reflected in the designer and vice versa. Note that even if you set the Locked property to true this only affects the designer and at run-time you can resize columns.

ListViews also come with a number of dynamic properties. Click the (Dynamic Properties) and tick the property you want. When you set a property to be dynamic, it creates an XML .config file and adds it to Solution Explorer.

Making changes at design time is one thing but we really need to do it when the program is running. A ListView is made up of 0 or more items. Each item (a ListViewItem) has a text property and a SubItems collection. The first column displays the Item text, the next column displays SubItem[0].text then SubItem[1].text and so on.

I've added a button to add a row and an edit box for the Town Name. Enter any name in the box and click Add Row. This adds a new row to the ListView with the town name put in the first column and the next three columns (SubItems[0..2] ) are populated with random numbers (converted to strings) by adding those strings to them.

Random R= new Random() ;
ListViewItem LVI = list.Items.Add(tbName.Text) ;
LVI.SubItems.Add( R.Next(100).ToString()) ; // 0..99
LVI.SubItems.Add( R.Next(100).ToString()) ;
LVI.SubItems.Add((( 10+R.Next(10))*50).ToString());

On the next page : Updating a ListView

04
of 10

Updating a ListView Programmatically

Right Clicking the ListView control

By default when a ListViewItem is created it has 0 subitems so these have to be added. So not only do you have to add ListItems to a ListView but you have to add ListItem.SubItems to the ListItem.

Removing ListView Items programmatically

To remove items from the list we need to first select the item to be removed. you could just elect an item then click a Remove Item button but i find that a bit crude and my own preference is to add a popup menu for the ListView so you can right click, and select Remove Item. First drop a ContextMenuStrip on the form. It will appear at the bottom below the form. I renamed it to PopupMenu. This is shared by all controls that need it. In this case we'll just use it on the ListView so select that and assign it to the ContextMenuStrip property. Note, example 3 was created with a ContextMenu which has now been replaced by a ContextMenuStrip. Just edit the code and change the old ContextMenu to ContextMenuStrip.

Now set the ListView Multiselect property to false. We only want to select one item at a time though if you wish to remove more in one go it's similar except you have to loop through in reverse. (If you loop in normal order and delete items then the subsequent items are out of sync with the selected indexes).

The right click menu doesn't work yet as we have no menu items to display on it. So right click PopupMenu (below the form) and you'll see Context Menu appear at the top of the form where the normal Menu editor appears. Click it and where it says Type Here, type Remove Item. The properties window will show a MenuItem so rename that to mniRemove. Double click this menu item and you should get menuItem1_Click event handler code function. Add this code so it looks like this.

If you lose sight of the Remove Item, just click the PopupMenu control on its own under the form in the form Designer. That will bring it back into view.

private void menuItem1_Click(object sender, System.EventArgs e)
{
    ListViewItem L = list.SelectedItems[0];
    if (L != null)
    {
        list.Items.Remove(L) ;
    }
}

However if you run it and don't add an item and select it, when you right click and get the menu and click Remove Item, it will give an exception because there is no selected item. That's bad programming, so here's how you fix it. Double click the pop-up event and add this line of code.

private void PopupMenu_Popup(object sender, System.EventArgs e)
{
  mniRemove.Enabled = (list.SelectedItems.Count > 0) ;
}

It only enables the Remove Item menu entry when there is a selected row.


On the next page : Using The DataGridView
05
of 10

How To Use a DataGridView

The Sample DataGridView and other controls

A DataGridView is both the most complex and the most useful component provided for free with C#. It works with both data sources (i.e. data from a database) and without (ie data that you add programmatically). For the rest of this tutorial I'll show using it without Data Sources, For simpler display needs you may find a plain ListView more suitable.

What Can a DataGridView do?

If you've used an older DataGrid control then this is just one of those on steroids: it gives you more built in column types, can work with internal as well as external data, more customization of display (and events) and gives more control over cell handling with freezing rows and columns.

When you're designing forms with grid data, it's most usual to specify different column types. You might have checkboxes in one column, readonly or editable text in another, and of courses numbers. These column types are also usually aligned diferently with numbers generally right aligned so the decimal points line up. At the column level you can choose from Button, checkbox, ComboBox,Image, TextBox and Links. if those aren't enough you can defibe your own custom types.

The easiest way to add columns is by designing in the IDE. As we've seen before this just writes code for you and when you've done it a few times you may prefer to add the code yourself. Once you've done this a few times it provides you with insights into how to do it programmatically.

Let's start by adding some columns, Drop a DataGridView on the form and click the little arrow in the top right hand corner. Then click Add Column. Do this three times. It will pop up an Add Column dialog where you set the name of the column, the text to display at the column top and lets you choose its type. The first column is YourName and and it's the default TextBox (dataGridViewTextBoxColumn). Set the Header Text to yourname as well. Make the second column Age and use a ComboBox. The third column is Allowed and is a CheckBox Column.

After adding all three you should see a row of three columns with a combo in the middle one (Age) and a checkbox in the Allowed column. If you click the DataGridView then in the properties inspector you should locate columns and click (collection). This pops up a dialog where you can set properties for each column such as individual cell colors, tooltip text, width, minimum width etc. If you compile and run you'll notice you can change column widths and run-time. In the property inspector for the main DataGridView you can set AllowUser to resizeColumns to false to prevent that.


On the next page: Adding rows to the DataGridView
06
of 10

Adding rows to the DataGridView Programmatically

Setting the Event Handler for the Leave event

We're going to add rows to the DataGridView control in code and ex3.cs in the examples file has this code. Starting by adding a TextEdit box, a ComboBox and a button to the form with the DataGridView on it. Set the DataGridView property AllowUserto AddRows to false. I use labels as well and called the combobox cbAges, the button btnAddRow and the TextBox tbName. I've also added a Close Button for the form and double clicked it to generate a btnClose_Click event handler skeleton. Adding the word Close() there makes that work.

By default the Add Row button enabled property is set false on start. We don't want to add any rows to the DataGridView unless there is Text in both the Name TextEdit box and the ComboBox. I created the method CheckAddButton and then generated a Leave event handler for the Name Text edit box by double clicking next to the word Leave in the Properties when it was displaying the events. The Properties box shows this in the picture above. By default the Properties box shows properties but you can see event handlers by clicking the lightning button.

private void CheckAddButton()
{
    btnAddRow.Enabled = (tbName.Text.Length > 0 && cbAges.Text.Length > 0) ;
}

You could use have used the TextChanged event instead, though this will call the CheckAddButton() method for every keypress rather than when teh control is leaved i.e. when another control gains focus. On the Ages Combo I used the TextChanged event but selected the tbName_Leave event handler instead of doubleclicking to create a new event handler.

Not all events are compatible because some events provide extra parameters but if you can see a previously generated handler then yes you can use it. It's mostly a matter of preference, you can have a separate event handler for every control that you are using or share event handlers (as I did) when they have a common event signature, i.e. the parameters are the same.

I renamed the DataGridView component to dGView for brevity and double clicked the AddRow to generate an event handler skeleton. This code below adds a new blank row, obtains that rows index (it's RowCount-1 as it's just been added and RowCount is 0 based) and then accesses that row via its index and sets the values in the cells on that row for the columns YourName and Age.

dGView.Rows.Add() ;
int RowIndex = dGView.RowCount - 1;
DataGridViewRow R= dGView.Rows[RowIndex];
R.Cells["YourName"].Value = tbName.Text;
R.Cells["Age"].Value = cbAges.Text;

On the next page: Container Controls

07
of 10

Using Containers with Controls

Overlapping Panel and GroupBox

When designing a form, you should think in terms of containers and controls and which groups of controls should be kept together. In Western cultures anyway, people read from Top Left to Bottom Right so make it easier to read that way.

A container is any of the controls that can contain other controls. Those found in the Toolbox include the Panel, FlowLayoutpanel, SplitContainer, TabControl and TableLayoutPanel. If you can't see the toolbox, use the View menu and you'll find it. Containers hold controls together and if you move or resize the container it will affect the positioning of the controls. Just move controls over the container in the Form Designer and it will recognize that the Container is now in charge.

Panels and GroupBoxes

A panel is one of the commonest containers and has the advantage that it has no border and so is effectively invisible. you can set a border or change its color but it's handy if you want to make a set of controls invisible. Just make the panel invisible by setting its Visible property= false and all the controls it contains vanish. More importantly though, as I believe that surprising users (with visible/invisible panels etc), you can toggle the Enabled property and all controls it contains will also be enabled/disabled.

A Panel is similar to a GroupBox but a GroupBox can't scroll but can display a caption and has a border by default. Panels can have borders but by default don't. I use GroupBoxes because they look nicer and this is important because:

  • Bolton's Law - Users will usually rate nice looking software with bugs higher than plain looking software without bugs!

Panels are handy for grouping containers as well, so you might have two or more GroupBoxes on a Panel.

Here is a tip for working with containers. Drop a Split Container on a form. Click the left panel then the right one. Now try and remove the SplitContainer from the form. It's difficult until you right click on one of the panels and then click Select SplitContainer1. Once it's all selected you can delete it. Another way that applies to all controls and containers is hit the Esc Key to select the parent.

Containers can nest inside each other as well. Just drag a small one on top of a larger one and you'll see a thin vertical line briefly appear to show that one is now inside the other. When you drag the parent container the child is moved with it. Example 5 shows this. By default the light brown panel is not inside the container so when you click the move button the GroupBox is moved but the panel isn't. Now drag the panel over the GroupBox so it is completely inside the Groupbox. When you compile and Run this time, clicking the Move button moves both together.

On the next page: Using TableLayoutPanels

08
of 10

Using TableLayoutPanels

Using a TableLayoutPanel

A TableLayoutpanel is an interesting container. It's a table structure organized like a 2D grid of cells where each cell contains just one control. You can't have more than one control in a cell. You can specify how the table grows when more controls are added or even if it doesn't grow, It seems modeled on an HTML table because cells can span columns or rows. Even the anchoring behavior of child controls in the container depends up on Margin and Padding settings. We'll see more about anchors on the next page.

In example Ex6.cs, I've started with a basic Two Column Table and specified via the Control and Row Styles dialog box (select the control and click the little right pointing triangle located near the top right to see a list of tasks and click the last one) that the left column is 40% and the right column 60% of the width. It lets you specify column widths in absolute pixel terms, in percentage or you can just let it AutoSize. A quicker way to get to this dialog is just click the Collection next to Columns in the Properties Window.

I've added an AddRow button and left the GrowStyle property with its default AddRows value. When the table gets full it adds another row. Alternatively you can set its values to AddColumns and FixedSize so it can't grow anymore. In Ex6, when you click the Add Controls button, it calls the AddLabel() method three times and AddCheckBox() once. Each method creates an instance of the control and then calls tblPanel.Controls.Add() After the 2nd control is added the third controls causes the table to grow. The picture shows it after teh Add Control button has been clicked once.

In case you're wondering where the default values come from in the AddCheckbox() and AddLabel() methods that I call, the control was originally manually added to the table in the designer and then the code to create it and initialize it was copied from within this region. You'll find the initialization code in the InitializeComponent method call once you click the + to the left of the Region below:

Windows Form Designer generated code
Then I copied and pasted the component creation code plus the code that initialized it. After that the control was manually deleted from the table. This is a handy technique when you want to create controls dynamically. You can leave the code for assigning the name property in, as having multiple dynamically created controls in the table doesn't appear to cause problems.

On the next page: Some Common Properties you should know

09
of 10

Common Control Properties you should know

Using Anchors

You can select multiple controls at the same time by holding down the shift key when you select the second and subsequent controls, even controls of different types. The Properties window shows just those properties common to both, so you can set them all to the same size, color and text fields etc. Even the same event handlers can be assigned to multiple controls.

Anchors Aweigh

Depending on the use, some forms will often end up being resized by the user. Nothing looks worse than resizing a form and seeing controls stay in the same position. All controls have anchors that let you "attach" them to the 4 edges so that the control moves or stretches when an attached edge is moved. This leads to the following behavior when a form is stretched from the right edge:

  1. Control Attached to Left but not right. - It Doesn't move or stretch (bad!)
  2. Control attached to both left and right edges. It stretches when the form is stretched.
  3. Control attached to right edge. It moves when the form is stretched.

For buttons like Close which are traditionally in the bottom right, behavior 3 is what is needed. ListViews and DataGridViews are best with 2 if the number of columns is enough to overflow the form and needs scrolling). The Top and Left anchors are the default. The Property Window includes a nifty little editor that looks like the England Flag. Just click any of the bars (two horizontal and two vertical) to set or clear the appropriate anchor, as shown in the picture above.

Tagging Along

One property that doesn't get much mention is the Tag property and yet it can be incredibly useful. In the Properties Window you can only assign text but in your code you can have any value that descends from Object.

I've used Tag to hold an entire object while only showing a few of its properties in a ListView. For instance you might only want to show a Customer Name and number in a Customer Summary list. But right click on the selected customer and then open a form with all the customer's details. This is easy if when you build up the customer list by reading all customer's details in memory and assigning a reference to the Customer Class Object in the Tag. All controls have a Tag.


On the next page: How to work with TabControls
10
of 10

Working With TabTabControls

Tbe Two Tabs TabControl

A TabControl is a handy way to save form space by having multiple tabs. Each tab can have an icon or text and you can select any tab and display its controls. The TabControl is a container but it only contains TabPages. Each TabPage is also a container that can have normal controls added to it.

In example x7.cs, I've created a two tab page panel with the first tab called Controls having three buttons and a checkbox on it. The second tab page is labeled Logs and used to display all the logged actions which includes clicking a button or toggling a check box. A method called Log() is called to log every button click etc. It adds the supplied string to a ListBox.

I've also added two right click popup menus items to the TabControl in the usual way. First add a ContextMenuStrip to the form and set it in the ContextStripMenu property of the TabControl. The two menu choices are Add New Page and Remove This Page. However I've restricted the Page removal so only newly added tab pages can be removed and not the original two.

Adding a New Tab Page

This is easy, just create a new tab page, give it a Text caption for the Tab then add it to the TabPages collection of the Tabs TabControl

TabPage newPage = new TabPage();
newPage.Text = "New Page";
Tabs.TabPages.Add(newPage);

In the ex7.cs code I've also created a label and added that to the TabPage. The code was obtained by adding it in the Form designer to create the code then copying it.

Removing a page is just a matter of calling TabPages.RemoveAt(), using the Tabs.SelectedIndex to get the currently selected Tab.

Conclusion

In this tutorial we've seen how some of the more sophisticated controls work and how to use them. In the next tutorial I'm going to continue with the GUI theme and look at the background worker thread and show how to use it.