Printing a Ruler using C# and GDI+

If you misplaced your ruler, here's an application that will create one for you on your printer! Unfortunately, you'll still need a ruler the first time using it so that you can calibrate the measurement for your particular printer, but once you know the calibration value, you are all set with a fairly accurate ruler.  Below is the simple design of our ruler.  The ruler itself is drawn in a Form.  The only other class used is the Calibration Dialog used to enter and retrieve a calibration value:
 
Figure 1 - Part of the ruler
 

This design was reverse engineered using the WithClass 2000 UML Design Tool for C#

The ruler is created by simply determining the resolution in pixels per inch.  This information can be retrieved from the graphics object itself:
  1. void SizeFormToRuler() {  
  2.  // get resolution, most screens are 96 dpi, but you never know...  
  3.  Graphics g = this.CreateGraphics();  
  4.  this.HRes = g.DpiX; // Horizontal Resolution  
  5.  this.VRes = g.DpiY; // Vertical Resolution  
  6.  Width = (int) HRes;  
  7.  Height = (int) VRes * 11;  
  8.  Left = 250;  
  9.  Top = 5;  
  10. }
The actual tick marks and numbers are drawn in the Form_Paint event handler method. This method establishes a Ruler Height in inches and then calls the method to draw the ruler:
  1. private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) {  
  2.  Graphics g = e.Graphics;  
  3.  RulerHeight = 11;  
  4.  DrawRuler(g);  
  5. }
The DrawRuler method is the heart of the application. It draws the tick marks and numbers and spaces them according to the number of pixels in an inch (+ a calibration value to make the inch measurement accurate). The Modulus Function is used to determine when its appropriate to draw a particular tick mark. For example when i modulus PixelsPerInch is equal to 0, then we've found an inch marker.
  1. private void DrawRuler(Graphics g) {  
  2.  g.DrawRectangle(Pens.Black, ClientRectangle);  
  3.  g.FillRectangle(Brushes.White, 0, 0, Width, Height);  
  4.  float PixelsPerInch = VRes + (float) Calibration;  
  5.  int count = 1;  
  6.  //cycle through every pixel in the ruler at 1/16 pixel intervals  
  7.  // Mark the appropriate tick values  
  8.  for (float i = 1; i < (float) PixelsPerInch * RulerHeight + 10; i += 0.0625 f) {  
  9.   // use the modulus function to determine what type of tick to use  
  10.   if ((i % (PixelsPerInch)) == 0) {  
  11.    g.DrawLine(Pens.Black, 0, i, 25, i);  
  12.    // Draw a Number at every inch mark  
  13.    g.DrawString(count.ToString(), TheFont, Brushes.Black, 25, i, new StringFormat));  
  14.   count++;  
  15.  } else if (((i * 2) % ((PixelsPerInch))) == 0) {  
  16.   g.DrawLine(Pens.Black, 0, i, 20, i);  
  17.  } else if (((i * 4) % ((PixelsPerInch))) == 0) {  
  18.   g.DrawLine(Pens.Black, 0, i, 15, i);  
  19.  } else if (((i * 8) % ((PixelsPerInch))) == 0) {  
  20.   g.DrawLine(Pens.Black, 0, i, 10, i);  
  21.  } else if (((i * 16) % ((PixelsPerInch))) == 0) {  
  22.   g.DrawLine(Pens.Black, 0, i, 5, i);  
  23.  }  
  24. }  
  25. }
The calibration value is retrieved by using the calibration dialog.  This dialog brings up a NumericUpDown control for choosing the number of pixels to offset the inch.  The offset can be a negative or positive number of pixels depending upon how your printer is off.  I found my printer was off by 4 pixels too small, so my calibration was +4.  Below is the calibration dialog:
 
Fig 3  - Ruler Calibration Dialog
 
The Calibration Dialog uses ShowDialog to display the form and retrieves the Calibration Value in the CalibrationValue property of the dialog:
  1. private void CalibrationMenu_Click(object sender, System.EventArgs e) {  
  2.  // Create Calibration Dialog  
  3.  CalibrationForm dlg = new CalibrationForm();  
  4.  // Set the initial Calibration Value  
  5.  dlg.CalibrationValue = Calibration;  
  6.  // Show the dialog and retrieve the Calibration Value  
  7.  if (dlg.ShowDialog() == DialogResult.OK) {  
  8.   Calibration = dlg.CalibrationValue;  
  9.   WriteCalibration(); // write the value out to a file to remember for next time  
  10.  }  
  11.  Invalidate();  
  12. }
Printing the ruler is accomplished using 3 different print controls: The PrintDocument, The PrintDialog, and the PrintPreviewDialog control. Once you've dropped these controls on your form and assigned the PrintDocument Instance to the corresponding Document properties in the PrintDialog and the PrintPreviewDialog, it's fairly easy to set up printing. Below is the routine for PrintPreview. As you can see there is not really much to it.
  1. private void PrintPreviewMenu_Click(object sender, System.EventArgs e) {  
  2.  this.printPreviewDialog1.ShowDialog();  
  3. }  
  4.   
  5. Printing is not much more complicated.Simply open the PrintDialog and once the user has assigned properties, call the Print method on the PrintDocument:  
  6.   
  7.  private void PrintMenu_Click(object sender, System.EventArgs e) {  
  8.   if (printDialog1.ShowDialog() == DialogResult.OK) {  
  9.    printDocument1.Print();  
  10.   }  
  11.  }  
The actual printing occurs in the PrintDocument's PrintPage Event Handler. This is where you put all the GDI routines for drawing the ruler. You can actually use the same drawing routines in your print handler as you do for your Paint Handler. Your just passing in a Printer Graphics Object instead of a Screen Graphics object:
  1. private void printDocument1_PrintPage(object sender,  
  2. System.Drawing.Printing.PrintPageEventArgs e)  
  3. {  
  4. Graphics g = e.Graphics;  
  5. PageSettings ps = e.PageSettings;  
  6. // Set the ruler height based on the Page Settings of the printer  
  7. // That way you can have a larger ruler for a larger piece of paper  
  8. RulerHeight = (int)(ps.Bounds.Height/100.0);  
  9. DrawRuler(g);  
  10. }
We've also added persistence to this application to remember the calibration value. The two routines below read and write the calibration value using the StreamReader and StreamWriter classes:
  1. void ReadCalibration() {  
  2.  // Determine the path of the file from the application itself  
  3.  string calfile = Application.StartupPath + @ "\cal.txt";  
  4.  // If the calibration file exists, read in the initial calibration value  
  5.  if (File.Exists(calfile)) {  
  6.   StreamReader tr = new StreamReader(calfile);  
  7.   string num = tr.ReadLine();  
  8.   Calibration = Convert.ToInt32(num);  
  9.   tr.Close();  
  10.  }  
  11. }  
  12. void WriteCalibration() {  
  13.  string calfile = Application.StartupPath + @ "\cal.txt";  
  14.  treamWriter tw = new StreamWriter(calfile);  
  15.  tw.Flush();  
  16.  // Write out the calibration value  
  17.  tw.WriteLine(Calibration.ToString());  
  18.  tw.Close();  
  19. }
Improvements

This control could be improved by adding a metric ruler option.  This is easy enough to accomplish simply by figuring out the pixels per centimeter and then using modulus 10 to get the millimeters.  Also a large ruler can be created by printing to multiple pages.  I'm sure you can think of some other ideas with a little measured thought.


Similar Articles