Saturday, October 25, 2008

DataGridView prevent digital colum from inputting non-digital character

When you input some non-digital character into a cell of a digital column in DataGridView, it will raise a DataError dialog with rather long error message. That is not satisfy because it doesn’t tell us what is wrong directly. Some people think of overriding the OnDataError method to check by ourself and give out friendly message. That’s a great idea. But I have a better solution which can prevent user from typing non-digital character into a cell of a digital column. So let’s begin our tour.

My solution also need to create custom DataGridView derive from System.Windows.Forms.DataGridView. One of the most important event of DataGridView is EditingControlShowing. It occurs when a control for editing a cell is showing. I can get the TextBox control of a cell and to use the same way as preventing TextBox from inputting non-digital character.

Code of MyDataGridView
public class MyDataGridView : DataGridView
{
public MyDataGridView()
{
this.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(MyDataGridView_EditingControlShowing);
}

void MyDataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (this.CurrentCell.ValueType == typeof(int))
{
TextBox textBoxCell = e.Control as TextBox;
if (textBoxCell != null)
{
textBoxCell.KeyPress += new KeyPressEventHandler(textBoxCell_KeyPress);
}
}
}

void textBoxCell_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsDigit(e.KeyChar))
{
e.Handled = true;
}
else
{
e.Handled = false;
}
}
}
First I need to judge if the editing cell is a digital cell, so I use “if (this.CurrentCell.ValueType == typeof(int))”. If it is a digital cell, then I need to handle the KeyPress event of TextBox to hang the non-digital character typing.

Let’s test the solution in a Windows Forms application.

First drag a MyDataGridView from the ToolBox to the form. You can create a DataTable as its datasource and add one digital column and a string column to the datasource.

Code like this
public partial class Form1 : Form
{
private DataTable sourceTable;

public Form1()
{
InitializeComponent();

sourceTable = new DataTable();
sourceTable.Columns.Add("IntColumn", typeof(int));
sourceTable.Columns.Add("StringColumn", typeof(string));

sourceTable.Rows.Add(1, "test1");
sourceTable.Rows.Add(2, "test2");
sourceTable.Rows.Add(3, "test3");

myDataGridView1.DataSource = sourceTable;
}
}
After that run the application, You will find that it is not allowed to type any non-digital character into the cell of “IntColumn”.

Make a runtime moveable control

It is amazing to move the controls on a form to everwhere of the form. Some one might think of handling the MouseDown event of a control and to reset its Location property to implement this goal. I used to hold the same thought. But the fact told me that this solution was not satisfy. The control could not be moved smoothly. To make the solution perfectly, I came to use Win32 API at the end.

In this solution, I chose to use two API, ReleaseCapture and SendMessage which located in User32.dll. Before I used these API, I should first using the namespace System.Runtime.InteropServices. The System.Runtime.InteropServices namespace provides a wide variety of members that support COM interop and platform invoke services.

Here is the code of a Runtime moveable Panel
public class MoveablePanel : Panel
{
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

private bool _enableRuntimeMoveable;

public MoveablePanel()
{
_enableRuntimeMoveable = false;
this.MouseDown += new MouseEventHandler(MoveablePanel_MouseDown);
}

void MoveablePanel_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _enableRuntimeMoveable == true)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
}

public bool EnableRuntimeMoveable
{
get { return _enableRuntimeMoveable; }
set { _enableRuntimeMoveable = value; }
}
}
In this example, I have handled the MouseDown event to call ReleaseCapture and SendMessage method. The first parameter of SendMessage method is the Hanle of the control that will be move. The WM_NCLBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. Also I have created a property named EnableRuntimeMoveable in the MoveablePanel, when you want this panel moveable, you can set the value to true. Otherwise you can set it to false then it is a normal panel.

Let’s create a moveable Panel and test it.
public partial class Form1 : Form
{
private MoveablePanel moveablePanel;

public Form1()
{
InitializeComponent();

moveablePanel = new MoveablePanel();
moveablePanel.Location = new Point(10, 10);
moveablePanel.BorderStyle = BorderStyle.Fixed3D;
moveablePanel.BackColor = Color.YellowGreen;
moveablePanel.EnableRuntimeMoveable = true;
this.Controls.Add(moveablePanel);
}
}
In order to see the panel clearly, I have set its background color to yellow green. Then you can enjoy moving it. You can create your own runtime moveable control such as TextBox, ComboBox, DataGridView etc. in this way.

Friday, October 24, 2008

How to draw a color list ComboBox


In Windows Forms project, ComboBox is one of the most common controls that we have ever used. Microsoft .Net control library offers a lot of events and methods that let you custom your own control’s UI as well as its function. Now I want to introduce you a way to custom a color ComboBox which can show you a colorful dropdown list.

In this example, first you should set the DrawMode property of your ComboBox to DrawMode.OwnerDrawFixed and DropDownStyle property to ComboBoxStyle.DropDownList. Then you can handle the DrawItem event to draw each item base on the ColorList (a list which contain all color you want to draw).

Here is the code of your color list ComboBox
public class ColorComboBox : System.Windows.Forms.ComboBox
{
private List< Color > _colorList;

public ColorComboBox()
{
_colorList = new List< Color >();
this.DrawMode = DrawMode.OwnerDrawFixed;
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.DrawItem += new DrawItemEventHandler(ColorComboBox_DrawItem);
}

public ColorComboBox(List< Color > colorList)
{
_colorList = colorList;
this.DrawMode = DrawMode.OwnerDrawFixed;
this.DropDownStyle = ComboBoxStyle.DropDownList;

for (int i = 0; i < colorList.Count; i++)
{
this.Items.Add("");
}
this.SelectedIndex = 0;
this.DrawItem += new DrawItemEventHandler(ColorComboBox_DrawItem);
}

void ColorComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index >= 0)
{
Graphics g = e.Graphics;
Brush brush = new SolidBrush(_colorList[e.Index]);
Rectangle rec = e.Bounds;
g.FillRectangle(brush, rec);
}
}

public List< Color > ColorList
{
get { return _colorList; }
set
{
_colorList = value;
for (int i = 0; i < _colorList.Count; i++)
{
this.Items.Add("");
}
this.SelectedIndex = 0;
}
}
}

Let us test it on a Windows Form application
public partial class Form1 : Form
{
private ColorComboBox colorCmb;

public Form1()
{
InitializeComponent();

List< Color > colorList = new List< Color >();
colorList.Add(Color.Black);
colorList.Add(Color.Blue);
colorList.Add(Color.Yellow);
colorList.Add(Color.Green);
colorList.Add(Color.Red);
colorList.Add(Color.Pink);

colorCmb = new ColorComboBox();
colorCmb.ColorList = colorList;
colorCmb.Location = new Point(50, 50);
this.Controls.Add(colorCmb);
}
}

Then you can run your code to see the effect. Click the dropdown button of that ComboBox, you can see the color that we have put in the ColorList. Wish you can enjoy this.