Print Support in a Custom DataGrid Control

Environment: Visual Basic .NET

Introduction

In many Windows applications, it could be useful to add some kind of print functionality. The DataGrid included in Windows Forms does not include any support for this. In this article I will show how to use a custom DataGrid I wrote in Visual Basic .NET inheriting from the class System.Windows.Forms.DataGrid to implement an application with a print preview and obviously print support. If you already have a window with a DataGrid, you can go directly to the section Using the Custom DataGrid.

Print support is written to work if the DataSource used in the DataGrid is a DataTable or a DataView. There is no support if the DataGrid is bound to a DataSet or some other kind of object (arrays and so on). Modifications in source code for other DataSource objects would not be very difficult.

The custom DataGrid also has other useful features such as the possibility to print column headers using more then one row (enabled by default), export data in XML and HTML formats, and send data by e-mail.

This code is written in Visual Basic mainly because I was writing it for a true application in VB .NET. There is no reason why you cannot use it in a C# or a J# application. The following code is also written in VB .NET but with some minor changes (mainly because the syntax is different); it will work fine also in other .NET languages.

The Basic Application

The first thing you have to do is to create a new Windows application project with a form hosting a standard DataGrid and a MainMenu object. Then you can add (using the designer or with code if you like) some menus to this form. Normally, the main form can have a Sizable FormBorderStyle. The DataGrid will have the Dock property set to Fill.

Now you can add a Database connection, a DataAdapter, and a DataSet (typed or untyped) that you will use to fill the DataGrid (for the sake of simplicity, this code is omitted here).

In the demo project you will find a test file called Demo.xml used to fill the DataSet with its data. In the sample application in the form constructor, I simply use the method ReadXml to read a table named orders in a DataSet named dsOrders.

Public
Sub New()
  MyBase.New()
  ‘This call is required by the Windows Form Designer.
  InitializeComponent()
  ‘Added Code 
  dsOrders.ReadXml(“..\Demo.xml”)
  objDataGrid.DataSource = dsOrders.Tables(0).DefaultView
End Sub

If you compile and run your project now, you will obtain a basic Windows application showing some data in a DataGrid.

Using the Custom DataGrid

The first thing to do is to add to your project a reference to the library hosting the custom DataGrid (customcontrols.dll) or to add to your solution the existing project with the DataGrid (customcontrols.vbproj). If you choose this second way, you must add a reference in your main project to the second project you just added.

To transform the normal DataGrid in my custom DataGrid, the simplest way is by using the Search and Replace feature in Visual Studio. Open your form in Code View and then use the Replace function (CTRL+H). As source text, insert System.Windows.Forms.DataGrid and as replace text, insert CustomControls.DataGridEx.

There is another way to use my custom DataGrid if you start by scratch with a new form. You can customize the Visual Studio toolbox, adding my custom DataGrid (the class name is DataGridEx and is hosted in customcontrols.dll). Then, instead of dragging in the designer the standard DataGrid, you will drag the DataGridEx control. The result will be the same as in the previous approach.

If you try to compile and run the application now, you will obtain a runtime exception. This is because to obtain print preview support and to support column headers using more then one row you have to create a TableStyle with information about every column (especially the column width). Looking in MSDN, you can find this kind of information but this can be a long and extremely tedious process. To skip this in the form constructor, you can call the method AdjustColumnWidths of the DataGrid (this method evaluates an optimum column width for each column based on the table data). Alternatively, you can invoke the method AdjustColumnWidthToTitles (that evaluates column widths based on column header text). This code can also be placed in the form Load event handler. If you call either of the two methods, you also have some interesting features like the possibility to display boolean columns in colored cells or to use custom cell controls.

Public Sub
New()
  …
  objDataGrid.AdjustColumnWidths(dsOrders.Tables(0))
End Sub

To use custom column header text, you can use the method SetColumnName as in the following example.

Public Sub
New()
  …
  objDataGrid.SetColumnName(dsOrders.Tables(0), “OrderID”, “Order Number”)
  objDataGrid.SetColumnName(dsOrders.Tables(0), “OrderDate”,
“Order Date”)
En
d Sub

To add the Page Setup code, you simply invoke the PageSetup method (a static/shared method) of the PageSetup class contained in the CustomControls namespace.

Private
SubmnuPageSetup_Click(ByVal
sender As
System.Object, _
 ByVal
e As
System.EventArgs) _
  HandlesmnuPageSetup.Click
    CustomControls.PageSetup.PageSetup()
End
Sub

To add Print and Print Preview support, you can simply call the methods Print and PrintPreview of the DataGrid. The following code is more complex because it works also if the DataSource used is a DataTable and not only if it is a DataView.

Private
Sub mnuPrintPreview_Click(ByVal
sender As System.Object, _
 ByVal e As
System.EventArgs) Handles mnuPrintPreview.Click
  Dim obj, obj2 As
Object
  obj = objDataGrid.DataSource
  If TypeOf
(obj) Is DataView Then
   obj2 = CType(obj, DataView).Table
  Else
   obj2 = obj
   obj = Nothing
  End
If
  Me
.objDataGrid.PageSettings = CustomControls.PageSetup.PageSettings
  objDataGrid.PrintPreview(CType(obj,
DataView), CType(obj2, DataTable), _
   CType(Me.BindingContext(objDataGrid.DataSource),
CurrencyManager), _
   25, “Do you wish to continue?”)
End
Sub

Private Sub
mnuPrint_Click(ByVal sender As
System.Object, _
 ByVal e As
System.EventArgs) Handles mnuPrint.Click
  Dim obj, obj2 As
Object
  obj = objDataGrid.DataSource
  If TypeOf
(obj) Is DataView Then
   obj2 = CType(obj, DataView).Table
  Else
   obj2 = obj
   obj = Nothing
  End
If
  Me
.objDataGrid.PageSettings = CustomControls.PageSetup.PageSettings
  objDataGrid.Print(CType(obj, DataView),
CType(obj2, DataTable), _
   CType(Me.BindingContext(objDataGrid.DataSource),
CurrencyManager))
End
Sub

Adding Custom Columns

To add custom columns in a DataGrid, you have to create your own ColumnStyle class inheriting from the class System.Windows.Forms.DataGridColumnStyle (or other classes inheriting from it).

In the CustomControls library, you can find two sample classes usable in your applications. The first one is DataGridBoolColumnEx (a normal boolean column with a background painted in a custom color if the value in its cell is true) and the second is DataGridPushPinColumn (a boolean column with custom painting). In the sample application, after calling AdjustColumnWidths, I added a call to a private function called AddOtherColumns. This function in the first 11 rows simply obtains a reference to the table loaded from the XML file and add two new boolean columns initializing their values. Then two new ColumnStyles are created and initialized supplying the column header, the column name in the DataTable, and the column width in the DataGrid. Finally, these column styles are added to the table style used by the DataGrid.

Public
Sub New()
MyBase.New()

objDataGrid.AdjustColumnWidths(dsOrders.Tables(0))
AddOtherColumns()

End Sub

Private
Sub AddOtherColumns()
  Dim myType As
System.Type
  myType = System.Type.GetType(“System.Boolean”)
  Dim tbl As
DataTable
  tbl = dsOrders.Tables(0)
  tbl.Columns.Add(New
System.Data.DataColumn(“BoolColumn”, myType))
  tbl.Columns.Add(New
System.Data.DataColumn(“PinnedColumn”, myType))
  Dim i As Integer
  For
i = 0 To tbl.Rows.Count – 1
    tbl.Rows(i).Item(“BoolColumn”) = IIf(i Mod
3 <> 0, True, False)
    tbl.Rows(i).Item(“PinnedColumn”) = IIf(i Mod
3 = 0, True, False)
  Next
  Dim
tst As DataGridTableStyle
  tst = objDataGrid.GetTblStyle(tbl)
  Dim cs As
New CustomControls.DataGridBoolColumnEx(Color.Blue)
  cs.HeaderText = “Sample Boolean Column”
  cs.MappingName = “BoolColumn”
  cs.Width = 50
  tst.GridColumnStyles.Add(cs)
  Dim cs2 As
New CustomControls.DataGridPushPinColumn()
  cs2.HeaderText = “Pinned Column”
  cs2.MappingName = “PinnedColumn”
  cs2.Width = 100
  tst.GridColumnStyles.Add(cs2)
End Sub

The following two images show the form when the application is started and when the print preview is invoked.

Low-Level Details

As mentioned earlier, the class DataGridEx inherits from the standard .NET DataGrid. It offers some more functionality and these new features are spread among classes contained in a DLL called CustomControls.
The DataGridEx class offers many new methods. Mainly there are methods used for Print support, to extend basic functionality, and to work with table styles.

I will not explain every method and its functionality but I will cite only the most important and useful ones. If you look at the code, I think you will find many other features.

There is a method called HitCellTest usable to raise an event called CellHitTest. This event is fired based on mouse position and tells the user what the cell under the mouse pointer is. A property called MouseOverNotificationEnabled is usable to enable or disable the notification of the current cell based on mouse pointer position. If the notification is enabled, every second the mouse position is checked and an event called MouseOverNotification is eventually fired (this can be useful to display custom tooltips based on mouse position).

When you call the method AdjustColumnWidthToTitles, a new TableStyle is created and for each column in the table passed as parameter a ColumnStyle is created. The column width is evaluated based on the header font. The method AdjustColumnWidths simply calls AdjustColumnWidthToTitles and then makes a cycle on the table for each row and each column, eventually enlarging the column width. These methods create column styles based on the datatype hosted in the columns. They try to create extended column styles (the ones I wrote), so if there is, for example, a boolean column, a DataGridBoolColumnEx is created.

The class PrintPreviewDialogEx inherits from the standard Print Preview dialog. The code in this class is quite interesting because the standard PrintPreviewDialog has a private Toolbar. If I simply inherit from this class, I cannot obtain a standard reference to this toolbar, so I couldn’t add any more toolbar buttons. The solution to this problem was to use the features of .NET Reflection to obtain a reference to the private variable. The true print preview dialog usable by users is TablePrintPreviewDialog. It inherits from PrintPreviewDialogEx and adds some functionality as the export of data in XML or HTML and the possibility to send the data by e-mail.

Please e-mail me for upgrades, questions, bugs found, and so forth. Thanks.

Downloads

Download zipped demo project files – 46 Kb.
Download zipped library source files – 34 Kb.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read