C#: DataGridViewComboBoxColumn Drop Down Menu Appears All Black


I ran into an issue today using the DataGridView where one of the columns defined as a DataGridViewComboBoxColumn appeared with the drop down menu completely black as shown below.

After some research I found out that there is a documented bug in the DataGridViewComboBoxColumn where this sometimes occurs if you are handling the EditingControlShowing event of the DataGridView. I am handling this event in order to wire up the SelectedIndexChanged event of the ComboBox embedded in the DataGridView cell.

On the bug report, Microsoft states that they will not be fixing this bug but thankfully, Debanjan1 has posted a workaround for this issue. If you simply set the CellStyle.BackColor property to the DataGridView.DefaultCellStyle.BackColor in the EditingControlShowing event, the problem goes away. This is shown below.

private void dataGridViewGLEntries_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    ComboBox cmbBx = e.Control as ComboBox;

    if (cmbBx != null)
    {
        cmbBx.SelectedIndexChanged -= ComboBoxCell_SelectedIndexChanged;
        cmbBx.SelectedIndexChanged += ComboBoxCell_SelectedIndexChanged;

        // Fix the black background on the drop down menu
        e.CellStyle.BackColor = this.dataGridViewGLEntries.DefaultCellStyle.BackColor;
    }
}

C#: DataGridViewComboBoxColumn Displaying Different Values in Drop Down List


You can add a DataGridViewComboBoxColumn to the DataGridView to allow your users the ability to select from a list of items when editing the cell’s contents. I ran into a situation today where I needed to display a description of each item in the drop down list of the ComboBox but after the user selected an item, I only wanted the name of the item to be displayed and not the description as well. The following image shows an example of what I wanted; the drop down list shows a description of each animal but when selected, only the type of the animal is shown.

Unfortunately, the DataGridViewComboBoxColumn has a single DisplayMember property that lets you specify which property of the objects bound to the control is displayed to the user. So, to allow for the desired result, I needed to change the DisplayMember property when the drop down list is visible to show both the type and description and when the drop down list is not visible, to only show the type. The code below allows us to accomplish just this.

The form is setup with a DataGridView and two columns, if which the first is a ComboBoxColumn. I have a simple Animal class as defined below.

public class Animal
{
    public string Type { get; set; }
    public string Description { get; set; }

    public string TypeAndDescription { get { return Type + " - " + Description; } }
}

At the initialization if the form, we simply add a few Animal objects to the column’s Items collection and set the default DisplayMember to Type.

public Form1()
{
    InitializeComponent();

    this.clmAnimal.Items.Add(new Animal() { Type = "Dog", Description = "Fury animal that barks" });
    this.clmAnimal.Items.Add(new Animal() { Type = "Cat", Description = "Fury animal that meows" });
    this.clmAnimal.Items.Add(new Animal() { Type = "Mouse", Description = "Small rodent with a tail" });
    this.clmAnimal.Items.Add(new Animal() { Type = "Rabbit", Description = "Small with two large ears" });            

    this.clmAnimal.DisplayMember = "Type";
    this.clmAnimal.DropDownWidth = 180;
}

From the designer, there is no way to wire up the DataGridViewComboBoxColumn DropDown and DropDownClosed events. To do this, we need to handle the EditingControlShowing event of the DataGridView. This event fires when a control used for editing the value in the DataGridView is shown. This event will be fired when the user clicks on the cell to select an item from the ComboBox. Add the following code to this event.

private void dataGridViewAnimals_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    // Cast the editing control to a ComboBox
    ComboBox cmbBx = e.Control as ComboBox;

    // If the cast was successful, wire up the DropDown and DropDownClosed events
    if (cmbBx != null)
    {
        cmbBx.DropDown -= new EventHandler(ComboBoxCell_DropDown);
        cmbBx.DropDown += new EventHandler(ComboBoxCell_DropDown);

        cmbBx.DropDownClosed -= new EventHandler(ComboBoxCell_DropDownClosed);
        cmbBx.DropDownClosed += new EventHandler(ComboBoxCell_DropDownClosed);
    }
}

Then add the following code to handle the DropDown and DropDownClosed events.

private void ComboBoxCell_DropDown(object sender, EventArgs e)
{
    // When the drop down list appears, change the DisplayMember property of the ComboBox
    // to 'TypeAndDescription' to show the description
    DataGridViewComboBoxEditingControl cmbBx = sender as DataGridViewComboBoxEditingControl;
    if (cmbBx != null)
        cmbBx.DisplayMember = "TypeAndDescription";
}

private void ComboBoxCell_DropDownClosed(object sender, EventArgs e)
{
    // When the drop down list is closed, change the DisplayMember property of the ComboBox
    // back to 'Type' to hide the description
    DataGridViewComboBoxEditingControl cmbBx = sender as DataGridViewComboBoxEditingControl;
    if (cmbBx != null)
        cmbBx.DisplayMember = "Type";
}

The above code will allows us to display a different value when the ComboBox’s drop down list is displayed than what is displayed after the user selects an item from the list.

C#: ListView – Dynamically Sizing Columns to Fill Whole Control


The DataGridView provides many options when it comes to defining the width of each column. My personal favorite is to set the AutoSizeMode property to Fill and then define a FillWeight for each column. Doing such will automatically resize each column according to its FillWeight property no matter how the user resizes the form. I was looking for such a property when using the ListView control in detail mode but apparently there is no such feature.

According to the MSDN documentation here, setting the column header Width property to -1 will automatically adjust the column width to the longest item in the column and setting the column header Width property to -2 will automatically adjust the width of the column to the size of the column heading. But, there is no Fill setting that will automatically adjust each column proportionality like is done in the DataGridView.

The simplest solution here is to just use a DataGridView and drop the ListView entirely, but in my situation, the ListView rendered the data much more to my liking that I could get the DataGridView to do. Given that no such solution was available, I decided to write one up myself using the ListView.SizeChanged event. The ListView.SizeChanged event is fired every time the size of the ListView changes, obviously, and thus with a bit of logic in this event we can programmatically emulate the auto sizing of the columns as done in the DataGridView.

Disclaimer: The best way to implement this is to create a custom control derived from the ListView class that contains the following logic. For simplicity, I have just used the stock ListView control and the SizeChanged event.

At design time when you define the ListView columns, enter the desired FillWeight of the column as an integer in the Tag property. Originally I tried just using the Width property but ran into problems recovering the correct value when the calculated width went to zero. If we set the FillWeight in the Tag property, it will stay constant for the life of the control (if you can ensure it isn’t changed at runtime). Again, ideally if you created a custom control, you could create a custom FillWeight property instead of using the Tag property,

Then, place the code that follows in the SizeChanged event. This code calculates the percentage of space each column should occupy and then sets the width of the column appropriately depending on the visible space the ListView control occupies. When resizing the ListView, this code is called multiple times. In an effort to reduce the number of calculations performed, we use the Resizing flag.


private bool Resizing = false;

private void ListView_SizeChanged(object sender, EventArgs e)
{
    // Don't allow overlapping of SizeChanged calls
    if (!Resizing)
    {
        // Set the resizing flag
        Resizing = true;

        ListView listView = sender as ListView;
        if (listView != null)
        {                               
            float totalColumnWidth = 0;

            // Get the sum of all column tags
            for (int i = 0; i < listView.Columns.Count; i++)
                totalColumnWidth += Convert.ToInt32(listView.Columns[i].Tag);

            // Calculate the percentage of space each column should 
            // occupy in reference to the other columns and then set the 
            // width of the column to that percentage of the visible space.
            for (int i = 0; i < listView.Columns.Count; i++)
            {
                float colPercentage = (Convert.ToInt32(listView.Columns[i].Tag) / totalColumnWidth);
                listView.Columns[i].Width = (int)(colPercentage * listView.ClientRectangle.Width);             
            }
        }
    }

    // Clear the resizing flag
    Resizing = false;
}

In the example below, there are three columns with FillWeights of 1, 2, and 1 respectively. Thus, the first and third column should occupy 25% of the ListView’s visible space and the second should occupy 50%. The results of the automatic sizing are shown for various form widths below.

Note that if you have multiple ListView controls on a form for which you want to use this logic, you can just hook up each SizeChanged event with the same code as shown below. For this reason, I casted the sender object to a ListView instead of just referencing it directly.

public Form1()
{
    InitializeComponent();
    this.listView1.SizeChanged += new EventHandler(ListView_SizeChanged);
    this.listView2.SizeChanged += new EventHandler(ListView_SizeChanged);
    this.listView3.SizeChanged += new EventHandler(ListView_SizeChanged);    
}     

C#: Programmatically Centering a Control (Extension Method)


I have recently been working on ensuring that one of our Windows Forms applications renders correctly under all DPI settings. One of the things I needed to do was center a control in its parent container at runtime. I came up with the following three extension methods that are available to anything that extends the System.Windows.Forms.Control class. The following code will center the control based upon its parent control. Thus if the control is placed directly on the form, it will be centered in the form. On the other hand if the control is inside a GroupBox or similar container, the control will be centered relative to the bounds of the container.

public static class Positioning
{
    /// <summary>
    /// Centers the control both horizontially and vertically 
    /// according to the parent control that contains it.
    /// </summary>
    /// <param name="control"></param>
    public static void Center(this Control control)
    {
        control.CenterHorizontally();
        control.CenterVertically();
    }

    /// <summary>
    /// Centers the control horizontially according 
    /// to the parent control that contains it.
    /// </summary>
    public static void CenterHorizontally(this Control control)
    {
        Rectangle parentRect = control.Parent.ClientRectangle;
        control.Left = (parentRect.Width - control.Width) / 2;
    }

    /// <summary>
    /// Centers the control vertically according 
    /// to the parent control that contains it.
    /// </summary>
    public static void CenterVertically(this Control control)
    {
        Rectangle parentRect = control.Parent.ClientRectangle;
        control.Top = (parentRect.Height - control.Height) / 2;
    }
}

// Usage
// -----
// private void Form1_Load(object sender, EventArgs e)
// {
//     this.button1.CenterVertically();
//     this.button1.CenterHorizontally();
//     this.button1.Center();
// }

C#: Programmatically Get the Current DPI Setting


Frequently Windows 7 ships configured to use 120 DPI rather than the previously standard 96 DPI. For Windows Forms developers this can cause a few problems with the layout and appearance of forms. If you need to make changes programmatically depending on the current DPI setting, the first thing you will need to do is figure out what the DPI setting is at runtime. One way to do this is to create a Graphics object and check the DpiX or DpiY property as shown below.

int currentDPI = 0;

using (Graphics g = this.CreateGraphics())
{
    currentDPI = (int)g.DpiX;    
}

Once you get the current DPI setting (96, 120, 144, or 192) you can make the necessary changes to the appearance of your form.

C#: Yield Statement Used to Reduce Accessor Code


Often in Windows Forms development I run into the situation where I need to show a list of items to a user from which they can select multiple items. Usually I will accomplish this using a dialog form with a CheckedListBox. The user simply checks the box next to each item they want to select and then clicks OK. For example, here is one that allows a user to override various form letter merge fields.

Before today, I would include an accessor method in the form class similar to the following:

public List<string> SelectedFields
{
    get
    {
        List<string> selectedFields = new List<string>();

        foreach (var item in this.listBxMergeFields.CheckedItems)
            selectedFields.Add(item.ToString());

        return selectedFields;
    }
}

I always thought it was a pain to have to instantiate a new List object and populate it when I wasn’t performing any operations on the List other than adding items. Enter the yield statement. It turns out that the following code can be used to solve my little annoyance.

public IEnumerable<string> SelectedFields
{
    get
    {
        foreach (var item in this.listBxMergeFields.CheckedItems)
            yield return item.ToString();
    }
}

When used in this fasion, the yield statement will return the accumulated values that result from the expression to its right. Pretty handy way to tidy up the code if you ask me! For more information and uses, check the MSDN documentation here.

(I admit this is a relatively trivial way of using this as we are just enumerating over one collection to create another, but the concept can be applied to other situations.)

C#: Panel Resets Scroll Position after Focus is Lost and Regained


In an application that I have written there is a dashboard on the main form that is composed of a large docked scrollable¬†panel that contains various other panels each showing various pieces of information. I have the AutoScroll property of the panel set to True so that if there are more modules/panels on the dashboard than can be visible on the screen, the user can scroll down to view them. When the user navigates to another window or another application, then returns to my application, and clicks one of the smaller panels on the dashboard, the large docked panel’s scroll position jumps up to the top. This was quite annoying as every time the user returned to my application they had to scroll back down to the module they were looking at previously.

Many of the solutions I found suggested manually setting the Panel.AutoScrollPosition when the form regains focus. The problem with this is that I would have had to capture the Click or MouseDown¬†events of every single control on the form. I figured there had to be a better way to do this so I kept searching. I finally found a post on a forum that indicated that the issue was caused by the fact that the Panel.ScrollToControl¬†method is called when my application regains focus. The ScrollToControl was scrolling to what the Panel control deemed to be the “activeControl” and thus caused the panel to jump to the top. So, to solve my problem, all I did was create a class that extended the Panel class and overrode the ScrollToControl method so that it returned the point associated with the currently visible portion of the panel.

public class CustomPanel : System.Windows.Forms.Panel
{
    protected override System.Drawing.Point ScrollToControl(System.Windows.Forms.Control activeControl)
    {
        // Returning the current location prevents the panel from
        // scrolling to the active control when the panel loses and regains focus
        return this.DisplayRectangle.Location;
    }
}

I presume that this might have side effects elsewhere but I haven’t found any so far in my situation.

Follow

Get every new post delivered to your Inbox.

Join 69 other followers