Dirk Bertels

The greatest malfunction of spirit
is to believe things (Louis Pasteur)

C++/CLI DataGridView Printing Mechanism

Introduction

Following code is based on a codeproject article Printing of DataGridView. I added the following alterations:

  • Converted the C# code to C++/CLI
  • Resolved some typecasting conflicts
  • Simplified the column width calculation algorithm
  • Added more code comments and formatted the code to make it more readable
  • Made the 'number lines of text per row' user variable
  • Added DateTime conversion mechanism
  • Added connection to an OLE database instead of server-side SQL.

Assume you have a form with the following components:

  • a DataGridView object with pre-formatted columns that represent your database's columns
  • a Get patient Details button that will retreive the data from your database and display it in the DatGridView
  • a Print Preview button
  • a Print button that will spawn a PrintDialog.
  • a PrintDocument component with 2 events attached: BeginPrint and PrintPage
  • a MaskedTextBox enabling the user to enter 2 numeric digits only

As stated, the original project used a serverside SQL database, the code of which is still present but commented out. This version uses a clientside MS Access database (mdb) file.


Code

First the initialisation section. All code generated by the Form Designer has been omitted. Note that the database here has 6 columns, one of which is a DataTime, the other ones are text:

#pragma once

namespace PrintDataGrid 
{
	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;
	// using namespace System::Data::SqlClient;
	using namespace System::Data::OleDb;	// for Access database
	using namespace System::Text;

	public ref class Form1 : public System::Windows::Forms::Form
	{
	private:
		// determines number lines of text each row should allow:
		static int iRowHeight = 0;
		String^ strConnection;		// DB connection string
		StringFormat^ strFormat;	// to format the grid rows.
		ArrayList^ arrColumnLefts;	// left coordinates of columns
		ArrayList^ arrColumnWidths;	// column widths
		int iCellHeight;			// datagridview cell dimensions
		int iTotalWidth;
		int iRow;					// counter
		bool bFirstPage;			// to check whether we are printing first page
		bool bNewPage;				// to check whether we are printing a new page
		int iHeaderHeight;			// header height

	private: System::Windows::Forms::DataGridView^  dataGridView1;
	private: System::Windows::Forms::Button^  button1;
	private: System::Windows::Forms::Button^  button2;
	private: System::Windows::Forms::Button^  button3;
	private: System::Windows::Forms::Label^  label1;
	private: System::Windows::Forms::MaskedTextBox^  maskedTextBox1;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  Last_Name;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  Initials;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  First_Name;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  Mobile;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  Email;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  DOB;
	private: System::Windows::Forms::DataGridViewTextBoxColumn^  Address;
	private: System::Drawing::Printing::PrintDocument^  printDocument1;
			 
	public:
		Form1(void)
		{
			InitializeComponent();
			InitializeVariables();
		}

	protected:
		~Form1()
		{
			if (components){
				delete components;
			}
		}
		
	private:
		void InitializeVariables()
		{   
		    // For SQL server side database:
		    // strConnection = "data source=localhost;Integrated Security=SSPI;Initial Catalog=yourDatabase;"
		    // For client side OLE database:
			strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=yourPath\\yourDatabase.mdb";   
			arrColumnLefts = gcnew ArrayList();
			arrColumnWidths = gcnew ArrayList();
			iCellHeight = 0; 
			iTotalWidth = 0; 
			iRow = 0;
			bFirstPage = false; 
			bNewPage = false;
			iHeaderHeight = 0; 
		}
		
	private:
			/// Required designer variable.
			System::ComponentModel::Container ^components;
	
	#pragma region Windows Form Designer generated code
			/// 
			/// Required method for Designer support - do not modify
			/// the contents of this method with the code editor.
			/// 
			void InitializeComponent(void)
			{
			   ...
			}
  

The actual event handlers and methods:

// Patient Details button clicked
private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) 
{
	 Cursor->Current = Cursors::WaitCursor;
	 
	 // SQL:
	 //SqlConnection ^ sqlConnection;
	 //SqlCommand ^ sqlCommand;
	 //SqlDataReader ^ sqlReader;
	 
	 // OLE DB:
	 OleDbConnection^ oleDbConnection;
	 OleDbCommand ^ oleDbCommand;
	 OleDbDataReader^ oleDbDataReader;
		 
	 try{
		 String^ strQuery = "SELECT Last_Name, Initials, First_Name, Mobile, Email, DOB, Address FROM VI_Patient";
		 
		 // SQL:
		 //sqlConnection = gcnew SqlConnection(strConnection);
		 //sqlConnection.Open();
		 //sqlCommand = gcnew SqlCommand(strQuery, sqlConnection);
		 //sqlReader = sqlCommand->ExecuteReader();
		 //while (sqlReader->Read()){
		 
		 // OLE DB:
		 oleDbConnection = gcnew OleDbConnection(strConnection);
		 oleDbConnection->Open();
		 oleDbCommand = gcnew OleDbCommand(strQuery, oleDbConnection);
		 oleDbDataReader = oleDbCommand->ExecuteReader();
		 while(oleDbDataReader->Read()){
			 //Reformat dateTime
			 String ^ thisDate = String::Empty;
			 if (oleDbDataReader->GetFieldType(5)->ToString() =="System.DateTime"){
				 if (oleDbDataReader[5] != System::DBNull::Value){
					DateTime^ dt = (DateTime^)oleDbDataReader[5];
					thisDate = dt->ToString("dd MMM yyyy");
				}
			 }

			//
			 array<System::Object^>^ row = { oleDbDataReader[0], oleDbDataReader[1], oleDbDataReader[2], 
				 oleDbDataReader[3], oleDbDataReader[4], thisDate, oleDbDataReader[6] };
			 this->dataGridView1->Rows->Add(row);
		 }
     }
	 catch(Exception^ e){
		 MessageBox::Show(e->Message, "Error", MessageBoxButtons::OK, MessageBoxIcon::Error);
		 return;
	 }
	 finally{
		 Cursor->Current = Cursors::Default;
		 oleDbConnection->Close();
		 if(oleDbDataReader != nullptr){
			 oleDbDataReader->Close();
			 oleDbDataReader = nullptr;
		 }
		  if(oleDbCommand != nullptr){
			  oleDbCommand = nullptr;
		 }
	 }
 }


// Print button clicked
private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) 
{
	iRowHeight = Convert::ToInt32(maskedTextBox1->Text);
	if(iRowHeight > 0){
		 // open the print dialog
		 PrintDialog^ printDialog = gcnew PrintDialog();
		 printDialog->Document = printDocument1;
		 printDialog->UseEXDialog = true;

		 // get the document
		 if(System::Windows::Forms::DialogResult::OK == printDialog->ShowDialog()){
			 printDocument1->DocumentName = "Patient Page Print";
			 printDocument1->Print();
		 }
	}
   	else{
		MessageBox::Show("row height should be at least 1", "Warning", MessageBoxButtons::OK, MessageBoxIcon::Warning);
	}
 }

// Initialize the printing. 
// Set page alignments and calculate total width
private: System::Void printDocument1_BeginPrint(System::Object^  sender, System::Drawing::Printing::PrintEventArgs^  e) 
{
	 try{
		strFormat = gcnew StringFormat();
		// horizontal alignment
		strFormat->Alignment = StringAlignment::Near;
		// vertical alignment
		strFormat->LineAlignment = StringAlignment::Center;
		// text needs trimming to the nearest character, and an ellipsis is inserted at the end of a trimmed line
		strFormat->Trimming = StringTrimming::EllipsisCharacter;

		arrColumnLefts->Clear();
		arrColumnWidths->Clear();
		iCellHeight = 0;
		iRow = 0;
		bFirstPage = true;
		bNewPage = true;

		// calculating total width
		iTotalWidth = 0;
		for each(DataGridViewColumn^ dgvGridCol in dataGridView1->Columns){
			iTotalWidth += dgvGridCol->Width;
		}
	}
	catch(Exception^ e){
		 MessageBox::Show(e->Message, "Error", MessageBoxButtons::OK, MessageBoxIcon::Error);
	}
}


private: System::Void printDocument1_PrintPage(System::Object^  sender, System::Drawing::Printing::PrintPageEventArgs^  e) 
{
	 try{
		 // e->MarginBounds gives printed page dimensions within the margins
		 int iLeftMargin = e->MarginBounds.Left;
		 int iTopMargin = e->MarginBounds.Top;
		 // flag to indicate more pages need printing:
		 bool bMorePagesToPrint = false;
		 int iTmpWidth = 0;

		 // for the first page, set cell width and header height
		 if(bFirstPage){
			 for each (DataGridViewColumn ^ GridCol in dataGridView1->Columns){
				 // set column widths relative to the margin bounds:
				 iTmpWidth = CalculateColWidth(GridCol->Width, e->MarginBounds.Width);
				 // MeasureString measure string when drawn with specified font
				 iHeaderHeight = (int)(e->Graphics->MeasureString(
					 GridCol->HeaderText, 
					 GridCol->InheritedStyle->Font, 
					 iTmpWidth).Height) + 11;
				 // save width and height of headers
				 arrColumnLefts->Add(iLeftMargin);
				 arrColumnWidths->Add(iTmpWidth);
				 // move the margin
				 iLeftMargin += iTmpWidth;
			 }
		 }

		 // loop through each line of the DataGridView
		 while(iRow < dataGridView1->Rows->Count){
			 DataGridViewRow ^ gridRow = dataGridView1->Rows[iRow];
			 // set cell height
			 iCellHeight = (gridRow->Height * (iRowHeight)) + 7;	// 7 is margin
			 int iCount = 0;
			 // set some flags when page needs changing:
			 if(iTopMargin + iCellHeight >= e->MarginBounds.Height + e->MarginBounds.Top){
				 bNewPage = true;
				 bFirstPage = false;
				 bMorePagesToPrint = true;
				 break;
			 }
			 else{
				 if(bNewPage){
					 // draw the header 
					 e->Graphics->DrawString(
						 "Patient Details Summary", 
						 gcnew System::Drawing::Font(dataGridView1->Font, FontStyle::Bold),
						 Brushes::Black, 
						 (float)e->MarginBounds.Left, 
						 (float)e->MarginBounds.Top - e->Graphics->MeasureString(
							"Patient Details Summary",
							gcnew System::Drawing::Font(dataGridView1->Font, FontStyle::Bold),
							e->MarginBounds.Width).Height - (float)13.0
					 );

					 // draw today's dateTime
					 String ^ strDate = DateTime::Now.ToLongDateString() + " " + DateTime::Now.ToShortTimeString();
					 e->Graphics->DrawString(
						 strDate, 
						 gcnew System::Drawing::Font(dataGridView1->Font, FontStyle::Bold),
						 Brushes::Black, 
						 e->MarginBounds.Left + (e->MarginBounds.Width - e->Graphics->MeasureString(
							strDate,
							gcnew System::Drawing::Font(dataGridView1->Font, FontStyle::Bold),
							e->MarginBounds.Width).Width), 
						e->MarginBounds.Top - e->Graphics->MeasureString(
							"Patient Details Summary",
							gcnew System::Drawing::Font(dataGridView1->Font, FontStyle::Bold),
							e->MarginBounds.Width).Height - 13
					 );

					 // Draw the DataGridView's header row
					 iTopMargin = e->MarginBounds.Top;
					 for each(DataGridViewColumn ^ gridCol in dataGridView1->Columns){
					 	 // Fill the header rectangle
						 e->Graphics->FillRectangle(
							 gcnew SolidBrush(Color::LightGray),
							 Rectangle(
								(int)arrColumnLefts[iCount],
								iTopMargin,
								(int)arrColumnWidths[iCount],
								iHeaderHeight
							)
						 );
						 // Draw the header rectangle
						 e->Graphics->DrawRectangle(
							Pens::Black,
							Rectangle(
								(int)arrColumnLefts[iCount],
								iTopMargin,
								(int)arrColumnWidths[iCount],
								iHeaderHeight
							)
						 );
						 // Draw the header text
						 e->Graphics->DrawString(
							gridCol->HeaderText,
							gridCol->InheritedStyle->Font,
							gcnew SolidBrush(
								gridCol->InheritedStyle->ForeColor),
							Rectangle(
								(int)arrColumnLefts[iCount],
								iTopMargin,
								(int)arrColumnWidths[iCount],
								iHeaderHeight
							),
							strFormat
						  );

						 iCount++;
					 }
					 bNewPage = false;
					 iTopMargin += iHeaderHeight;
				 } // END if(bNewPage)
				 
				 iCount = 0;

				 // Draw text of subsequent rows on this page
				 for each(DataGridViewCell ^ cel in gridRow->Cells){
					if(cel->Value != nullptr){
						 e->Graphics->DrawString(
							cel->Value->ToString(),
							cel->InheritedStyle->Font,
							gcnew SolidBrush(cel->InheritedStyle->ForeColor),
							Rectangle(
								(int)arrColumnLefts[iCount],
								 iTopMargin,
								(int)arrColumnWidths[iCount],
								 iCellHeight
							),
							strFormat
						);
					}

					// Draw cell borders
					e->Graphics->DrawRectangle(
						 Pens::Black,
						 Rectangle(
							(int)arrColumnLefts[iCount],
							iTopMargin,
							(int)arrColumnWidths[iCount],
							iCellHeight
							)
					);
					iCount++;
				 }
			} // END else
			 iRow++;
			 iTopMargin += iCellHeight;
		} // END while

		 // if more lines are present, // if more lines are present, invoke printDocument1_PrintPage() again
		 if (bMorePagesToPrint){
			 e->HasMorePages = true;
		 }
		 else{
			 e->HasMorePages = false;
		 }
	 } // END try

	 catch(Exception ^ e){
		MessageBox::Show(e->Message, "Error", MessageBoxButtons::OK, MessageBoxIcon::Error);
	 }
}



// Print Preview button clicked
private: System::Void button3_Click(System::Object^  sender, System::EventArgs^  e) 
{
	iRowHeight = Convert::ToInt32(maskedTextBox1->Text);
	if(iRowHeight > 0){
		 // open the print preview dialog - this will do all the required work
		 PrintPreviewDialog ^ objPPdialog = gcnew PrintPreviewDialog();
		 objPPdialog->Document = printDocument1;
		 objPPdialog->ShowDialog();
	}
	else{
		MessageBox::Show("row height should be at least 1", "Warning", MessageBoxButtons::OK, MessageBoxIcon::Warning);
	}
 }


 private: int CalculateColWidth(int gridColWidth, int marginWidth)
 {

	 int thisWidth = 1;
	 // Math::Floor returns the largest integer less than or equal to the specified double.
	 thisWidth = (int)(System::Math::Floor(
		 (double)(
			(double)gridColWidth / (double)iTotalWidth * (double)marginWidth
			)
		)
	);
	return thisWidth;
}


The code has been tested in Visual Studio 2008. When using a pdf printer such as Cute Pdf, you can create presentable pdf files of your database tables. Again, this code is mainly the work of a previous article in C# written by Life with .NET, I only converted it to C++/CLI and made a few alterations.


Comments