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.

Follow

Get every new post delivered to your Inbox.

Join 68 other followers