|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
PrefaceThis article was written well before Visual Studio 2008. Visual Studio 2008 has a IntroductionThis article describes how to send the image of a This code is fairly limited but can print most Forms containing standard controls. Within the
Adding support for other controls is fairly easy. Add a method and code to draw the control. Add a couple of lines in the BackgroundThis solution came out of a work requirement. I needed to be able to print an entire Form. I saw many solutions that use the API to grab a screenshot, but I had to use managed code to perform the same task. Nothing I found helped. Using the CodeTo print a Form follow these steps:
In the included test app, a button kicks off the printing process: try
{
this.Cursor = Cursors.WaitCursor;
PrintForm pf = new PrintForm(this);
pf.Print();
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message);
}
finally
{
this.Cursor = Cursors.Default;
}
The two lines of code in the try block are all that is required to print a Form. All the work is contained in the single class public PrintForm()
{
}
public PrintForm(Form frm)
{
_frm = frm;
}
The 2nd constructor sets the Class-level variable to the Form that you want to print. There are 2 class-level variables that are needed. They are declared as: private Form _frm = null;
private int _iCurrentPageIndex = 0;
There are two methods for printing the Form.
The latter contains the code which sets up and initiates the printing process. This is the method which will be examined. public void Print(Form frm)
{
if (frm == null)
return;
// Setup a PrintDocument
PrintDocument pd = new PrintDocument();
pd.BeginPrint += new PrintEventHandler(this.PrintDocument_BeginPrint);
pd.PrintPage += new PrintPageEventHandler(this.PrintDocument_PrintPage);
// Setup & show the PrintPreviewDialog
PrintPreviewDialog ppd = new PrintPreviewDialog();
ppd.Document = pd;
ppd.ShowDialog();
}
The first block of code creates a The second block of code creates a private void PrintDocument_BeginPrint(object sender, PrintEventArgs e)
{
_iCurrentPageIndex = 0;
}
This is the first of two methods that get hooked up in the private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
// Set the page margins
Rectangle rPageMargins = new Rectangle(e.MarginBounds.Location,
e.MarginBounds.Size);
// Generate the offset origins for the printing window
Point[] ptOffsets = GeneratePrintingOffsets(rPageMargins);
// Make sure nothing gets printed in the margins
e.Graphics.SetClip(rPageMargins);
// Draw the rest of the Form using the calculated offsets
Point ptOffset = new Point(-ptOffsets[_iCurrentPageIndex].X,
-ptOffsets[_iCurrentPageIndex].Y);
ptOffset.Offset(rPageMargins.X, rPageMargins.Y);
DrawForm(e.Graphics, ptOffset);
// Determine if there are more pages
e.HasMorePages = (_iCurrentPageIndex < ptOffsets.Length - 1);
_iCurrentPageIndex++;
}
This is the second of two methods that get hooked up in the Print method. This method is called before each page is printed. It provides a First, we setup a Rectangle object to the print margins we want. The Next, pagination is calculated using the Next, Then, draw the entire Form using the calculated offsets. Effectively, this divides the Form into rectangles the size of the printing area. This method is described below. Next, the Finally, don't forget to increment the page index. private Point[] GeneratePrintingOffsets(Rectangle rMargins)
{
// Setup the array of Points
int x = (int)Math.Ceiling((double)(_frm.Width) /
(double)(rMargins.Width));
int y = (int)Math.Ceiling((double)(_frm.Height) /
(double)(rMargins.Height));
Point[] arrPoint = new Point[x * y];
// Fill the array
for (int i = 0; i < y; i++)
for (int j = 0; j < x; j++)
arrPoint[i * x + j] = new Point(j * rMargins.Width,
i * rMargins.Height);
return arrPoint;
}
This method calculates the offsets for each page based on the page margins. It essentially divides the Form into rectangles of the specified size. The array of points stores the upper-left corner of each rectangle. To print each page, the Form is drawn so that this offset is located at the origin of the page margins. private void DrawForm(Graphics g, Point ptOffset)
{
// Calculate the Title Bar rectangle
int iBarHeight = (int)g.MeasureString(_frm.Text, _frm.Font).Height;
Rectangle rTitleBar = new Rectangle(ptOffset.X, ptOffset.Y,
_frm.Width, iBarHeight + 2);
// Draw the rest of the Form under the Title Bar
ptOffset.Offset(0, rTitleBar.Height);
g.FillRectangle(new SolidBrush(_frm.BackColor), ptOffset.X,
ptOffset.Y, _frm.Width, _frm.Height);
// Draw the rest of the controls
DrawControl(_frm, ptOffset, g);
// Draw the Form's Title Bar
Bitmap bmp = Bitmap.FromHicon(_frm.Icon.Handle);
g.FillRectangle(new SolidBrush(SystemColors.ActiveCaption), rTitleBar);
g.DrawImage(bmp,
rTitleBar.X,
rTitleBar.Y,
rTitleBar.Height,
rTitleBar.Height);
g.DrawString(_frm.Text,
_frm.Font,
new SolidBrush(SystemColors.ActiveCaptionText),
rTitleBar.X + rTitleBar.Height, // adding the width of the icon
rTitleBar.Y + (rTitleBar.Height / 2) -
(g.MeasureString(_frm.Text, _frm.Font).Height) / 2);
// Draw the Title Bar buttons
Size s = new Size(16, 14); // size determined from graphics program
ControlPaint.DrawCaptionButton(g,
ptOffset.X + _frm.Width - s.Width,
ptOffset.Y + (rTitleBar.Height / 2) -
(s.Height / 2) - rTitleBar.Height,
s.Width,
s.Height,
CaptionButton.Close,
ButtonState.Normal);
ControlPaint.DrawCaptionButton(g,
ptOffset.X + _frm.Width - (s.Width * 2) - 1,
ptOffset.Y + (rTitleBar.Height / 2) - (s.Height / 2)
- rTitleBar.Height,
s.Width,
s.Height,
(_frm.WindowState == FormWindowState.Maximized ?
CaptionButton.Restore : CaptionButton.Maximize),
ButtonState.Normal);
ControlPaint.DrawCaptionButton(g,
ptOffset.X + _frm.Width - (s.Width * 3 - 1),
ptOffset.Y + (rTitleBar.Height / 2) - (s.Height / 2) -
rTitleBar.Height,
s.Width,
s.Height,
CaptionButton.Minimize,
ButtonState.Normal);
// Draw a rectangle around the entire Form
g.DrawRectangle(Pens.Black, ptOffset.X,
ptOffset.Y - rTitleBar.Height, _frm.Width,
_frm.Height + rTitleBar.Height);
}
The bulk of the code in this method prints the parts of the Form itself -- the title bar, the Form's window, and the title bar buttons (maximize, minimize, and close). An important point is that the parts are drawn in a certain order. The main form is the bottom most control and should be drawn first. All controls contained in the Form are drawn next using the private void DrawControl(Control ctl, Point ptOffset, Graphics g)
{
// Cycle through each control on the form and paint
// it on the graphics object
foreach (Control c in ctl.Controls)
{
// Skip invisible controls
if (!c.Visible)
continue;
// Calculate the location offset for the control - this offset is
// relative to the original offset passed in
Point p = new Point(c.Left, c.Top);
p.Offset(ptOffset.X, ptOffset.Y);
// Draw the control
if (c is GroupBox)
DrawGroupBox((GroupBox)c, p, g);
else if (c is Button)
DrawButton((Button)c, p, g);
else if (c is TextBox)
DrawTextBox((TextBox)c, p, g);
else if (c is CheckBox)
DrawCheckBox((CheckBox)c, p, g);
else if (c is Label)
DrawLabel((Label)c, p, g);
else if (c is ComboBox)
DrawComboBox((ComboBox)c, p, g);
else if (c is RadioButton)
DrawRadioButton((RadioButton)c, p, g);
else
return;
// Draw the controls within this control
DrawControl(c, p, g);
}
}
This method is the "meat & potatoes" of the project. It draws the contents of the specified Control on the specified Graphics object at the specified offset. This is where you'd add a couple lines of code to draw additional controls. The magic of this method lies in recursion. Note that it calls itself after the current control is drawn. This way, child controls are drawn on top. Let's take a peek at one of the methods used to draw a particular control. I'll pick the private void DrawRadioButton(RadioButton rdo, Point p, Graphics g)
{
// Setup the size of a RadioButton
Rectangle rRadioButton = new Rectangle(p.X, p.Y, 12, 12);
ControlPaint.DrawRadioButton(g, p.X,
p.Y + (rdo.Height / 2) - (rRadioButton.Height / 2),
rRadioButton.Width,
rRadioButton.Height,
(rdo.Checked ? ButtonState.Checked : ButtonState.Normal));
// RadioButton's text left justified & centered vertically
g.DrawString(rdo.Text,
rdo.Font,
new SolidBrush(rdo.ForeColor),
rRadioButton.Right + 1,
p.Y + (rdo.Height / 2) - (g.MeasureString(rdo.Text,
rdo.Font).Height / 2));
}
In this case, the Framework already provides a method to draw the Radio Button itself within the It's a bit of work to draw a Form in this way, but if you don't want to use unmanaged code it's the only solution I've found for the .NET environment. In the end, it's up to you how accurately you want your forms to print. SummaryI found it interesting that the framework provides a handful of methods to draw controls but leaves most of the work to the programmer. You must supply the size and location, and in some cases the state of the control. It doesn't simply allow you to pass the control itself. In retrospect, this was more than I had intended to tackle. History
| |||||||||||||||||||||