Introduction
Most of the projects that I build with C# are either console based or class libraries. Lately I have been building some graphical applications too. I have some experience building graphical applications using the Java - Swing interface, so I expected the transition to C# would be easy to make. One of the things I found very interesting in Java is the use of layout managers, and the great ability to customize the views of controls on the form.
C# does not make any use of layout managers, but relies on absolute positioning. This �feature� is also available in Java but is of course not preferred, the use of layout managers works much better for me. Also the second strength of Java, it�s customizability, is something that I am missing in .NET. Various things that were easy to implement in Java are a lot harder in C#. In this article, I will show how to build around a rather small problem of a specific Windows Forms control, the DataGrid
.
I found that the DataGrid
component lacks some properties I desperately want. The problem that will be tackled here is the size of the last column. When the size of the table that is being shown is smaller than the ClientSize
of the DataGrid
, the last column will not resize to fit the whole area. The image below shows the problem.
The red arrow �A� is pointing to the area I am after. I want the grid to automatically resize it�s last column, or all columns a little bit, so the DataGrid
fills the indicated space. This can be done, but it�s not exactly elegant. Currently my implementation is only available for DataTable
s as the DataSource
, porting should be pretty easy however. Now for the actual work..
The code
After some searching, I found that the best way to tackle the problem, was to build a customized DataGridTableStyle
. I will call it AutoResizeDataGridTableStyle
, what�s in a name! This class will perform the following things:
- Look pretty, the default style doesn�t.
- Detect changes in the
DataGrid
�s DataSource
.
- Detect changes in the
DataGrid
�s Size
.
Implementing this will not be hard, detecting the changes can be done using the events that the DataGrid
spits out. This will make the AutoResizeDataGridTableStyle
a bit ugly because we need to register the events. This means we can�t just add a default instance of AutoResizeDataGridTableStyle
to the TableStyles
collection of the DataGrid
.
Looking pretty
The main reason for creating a DataGridTableStyle
is to make the DataGrid
look nice. We will add some prettiness to the constructor. The variable OFFSET_GRID
will be explained further on.
public class AutoResizeDataGridTableStyle: DataGridTableStyle
{
private int OFFSET_GRID = 39;
public AutoResizeDataGridTableStyle(): base()
{
BackColor = Color.WhiteSmoke;
AlternatingBackColor = Color.Lavender;
ForeColor = Color.MidnightBlue;
GridLineColor = Color.Gainsboro;
HeaderBackColor = Color.MidnightBlue;
HeaderForeColor = Color.WhiteSmoke;
LinkColor = Color.Teal;
SelectionBackColor = Color.CadetBlue;
SelectionForeColor = Color.WhiteSmoke;
ColumnHeadersVisible = true;
RowHeadersVisible = true;
HeaderFont = new Font("Microsoft Sans Serif", 8);
}
OnDataSourceChanged
We need to detect changes in the DataSource
of the DataGrid
to be able to add DataGridColumnStyles
to the DataGridTableStyle
. The class does not support any advanced handling for these styles, it just builds normal DataGridTextBoxColumns
. As you can see, this method only works for DataTable
s. If all this is gibberish to you, read the manual about DataGrid
, it's not the best but it's a start.
public void OnDataSourceChanged(object sender, EventArgs e)
{
GridColumnStyles.Clear();
if(DataGrid != null && DataGrid.DataSource != null &&
DataGrid.DataSource is DataTable)
{
DataTable currentTable = (DataTable)DataGrid.DataSource;
foreach(DataColumn column in currentTable.Columns)
{
DataGridColumnStyle style = new DataGridTextBoxColumn();
style.HeaderText = column.ColumnName;
style.MappingName = column.ColumnName;
GridColumnStyles.Add(style);
}
}
OnDataGridResize(this,new EventArgs());
}
The method simply runs through each column in the DataTable
, creates a new DataGridTextBoxColumn
for it, and finally resizes the DataGrid
�s columns.
OnDataGridResize
When the DataGrid
changes it�s Size
, the actual columns should resize with the DataGrid
. This is accomplished by listening for the Resize
event from the DataGrid
. When the grid resizes, we will calculate the total column width and verify it�s difference to the ClientSize
of the DataGrid
.
This is where the main problem comes in. Calculating the total column width is possible, but you may have noticed the second arrow (B) on the image above, it points to the before-first column, a column which you cannot access in any way that I am aware of, a column that has width! This will make it next to impossible to calculate the entire table width correctly. We can make an educated guess, but screen resolutions will make this a bit hairy. An educated guess it is, and it is called OFFSET_GRID
. I have actually tested this on my monitor with different resolutions, for me the value is always the same. For you? I don�t know, probably not!
First a method for calculating the total grid width:
private int GetGridColumnWidth()
{
if(GridColumnStyles.Count == 0)
return -1;
int width = 0;
foreach(DataGridColumnStyle columnStyle in GridColumnStyles)
{
width += columnStyle.Width;
}
return width + OFFSET_GRID;
}
Next the method that handles resizing events:
public void OnDataGridResize(object sender, EventArgs e)
{
if(DataGrid != null)
{
int columnWidth;
if( (columnWidth = GetGridColumnWidth()) != -1)
{
int clientWidth = DataGrid.ClientSize.Width;
if(GridColumnStyles.Count > 0)
{
int newWidth =
GridColumnStyles [GridColumnStyles.Count - 1].Width +
clientWidth - columnWidth;
if(newWidth > PreferredColumnWidth)
GridColumnStyles[GridColumnStyles.Count - 1].Width =
newWidth;
else
GridColumnStyles[GridColumnStyles.Count - 1].Width =
PreferredColumnWidth;
}
}
DataGrid.Invalidate(true);
}
}
Call it
Now we are almost at the end of this article, finally I will show how to hook up this class into a DataGrid
, just a few lines of code.
DataGrid gridView = new DataGrid();
AutoResizeDataGridTableStyle style = new AutoResizeDataGridTableStyle();
gridView.DataSourceChanged += new EventHandler(style.OnDataSourceChanged);
gridView.Resize += new EventHandler(style.OnDataGridResize);
style.MappingName = "MyTable";
gridView.TableStyles.Add(style);
Conclusion
After using C# for building WinForms applications, I�m afraid to say I liked Java better. The classes I have used lack the degree of customizability that I would like to see. Workarounds are available, but as the OFFSET_GRID
item shows, it�s not always elegant.