This is a program I recently wrote to print product signs for my bakery The Enchanted Oven. You can modify the code to use your product names and prices.
In the program’s main form (upper left in the picture), select the items for which you want to generate product signs. Next pick a divider type: Grid (shown in the picture), Spaced Grid (a grid with a blank margin between cells), or Cut Marks (little plus signs at the grid cell corners).
Set the minimum and maximum X and Y coordinates that you want the signs to cover on the printed page in inches. Note that the program prints in landscape orientation. You can modify the code to print in portrait orientation if you like. These values indicate the left and right page margins, but they also let you position the text in the product signs vertically. To make a good sign, they should not be centered vertically.
After you’ve made your selections, click the Print button to display the Print Preview Dialog (lower right in the picture above). Use the dialog to view the printout’s pages. If you like the result, click the Print button in the dialog’s upper left corner to send the printout to the printer.
After the printout is finished, I cut the three columns apart. I then fold the strips along the horizontal lines and tape the result closed to form a triangular prism that I can use for product signs as in the picture on the right.
The program demonstrates some useful techniques including:
The following sections describe those techniques and explain other interesting pieces of code.
This example uses a list of Product objects to store the product data. The following code shows the Product class.
This class basically just stores a product’s name and price. It has one initializing constructor to make creating objects easier.
The class also overrides its ToString method to display the product’s name followed by its price in parentheses. More on that in a bit.
The following code shows how the program initializes its product data.
The code starts by defining a list of Product objects. The form’s Load event handler adds objects to the list.
The program has a couple of categories that share the same prices, so the code make arrays holding their names and then loops through the arrays to generate the corresponding Product objects
This code finishes by setting the clbProducts CheckedListBox control’s DataSource property equal to the list. List boxes and combo boxes use the ToString methods of the objects that they contain to determine what to display. The Product class overrides its ToString method to display the product’s name and price, so that’s what the clbProducts control displays.
At design time I set the checked list box’s CheckOnClick property to true, so if you click a row the control checks its box. (If you don’t set that property to true, then you need to select a row and then click it again to check its box.) That lets to check and uncheck list items manually. The All and None buttons let you check or uncheck all of the items at once. The following code shows how those buttons work.
The buttons’ event handlers simply call the following CheckUncheckAll method.
This method loops through all of the items in the list and calls the control’s SetItemChecked for each to check or uncheck them. (You might think the control would provide a method to check or uncheck all of the items, but no. You could turn it into an extension method, or maybe two.)
When you check or uncheck an item, the following code enables the Print button if at least one item is checked.
This is another place where you might think the control could be improved. Instead of providing an ItemChecked event to tell you when an item has been checked or unchecked, the control only provides the ItemCheck event that tells you what is going to happen. You need to figure out what the consequences of that will be.
The code shown above checks the number of items currently checked. If only one item is checked and the clicked item is about to be unchecked, then there will soon be no checked items. In that case the code disabled the Print button.
If there will soon be at least one checked item, the code enables the Print button.
When you click Print, the following code executes.
This code sets the ppdSigns PrintPreviewDialog component’s Document property equal to the pdonSigns PrintDocument object. The dialog uses the document to generate the printout and then displays it to the user.
The code sets the dialog’s size to make it larger than normal and then displays it.
When the dialog needs to generate a printout, it raises the print document’s events to make it produce the printout. The first event uses by this example is BeginPrint. The following code shows the event handler.
The program uses the variable NextProductNum to keep track of the next product that it should print. You could set this value to 0 in the Print button’s event handler, but the dialog may actually need to generate the printout several times. It creates the printout when it first appears so it can show it to you. If you click the dialog’s Print button in its upper left corner, the dialog uses the PrintDocument events again to regenerate the printout to send it to the printer. If you click the button repeatedly, the dialog generates the printout again and again.
If you reset NextProductNum the the program’s btnPrint_Click event handler, then it is only reset once and not each time the print dialog needs to generate the printout.
The BeginPrint event handler solves this problem. It first parses the information that you entered on the program’s main form. Notice that the code multiplies the minimum and maximum X and Y coordinates by 100. The printer measures in hundredths of inches, so that converts the inches that you enter into the printer’s units.
Next the code sets the printout’s page margins (again in hundredths of inches) and makes the page print in landscape mode. (I actually should make the right margin slightly wider because my printer doesn’t print all the way to half an inch from the edge of the paper so the grid’s right edges are cropped off.) It finishes by resetting NextProductNum to 0 so the printout starts with the first product.
When it needs to generate a printed page, the print dialog raises the print document’s PrintPage event handler. This is where the drawing occurs. The following code shows the program’s PrintPage event handler.
This code calls the DrawPage method, passing it the Graphics object in which to draw, to do all of the most interesting work. It then sets e.HasMorePages to tell the print dialog whether there are more product signs to print.
The following section describes the code that actually draws the product signs on the printout.
The following DrawPage method draws a single page of product signs on the printout.
This method first calls the DrawProduct method shown shortly to draw three product signs. It passes that method the Graphics object on which to draw and the column number where it should draw each sign.
The method then draws an appropriate grid. That code isn’t short, but it is straightforward so I won’t describe it.
The following code shows the DrawProduct method that draws a single product sign.
This is the method that draws the product signs. If first checks NextProductNum to see if we have finished printing all of the signs. If we have run out of product signs, the method just returns. The DrawPage method still draws the grid around the sign’s position, so the page may contain blank product signs. We draw on those with a marker if we need extra signs in the bakery.
The code then gets the next Product object from the Products list and it starts drawing the appropriate sign.
The code follows roughly the same approach to draw the product name, product price, and a divider both right-side up and upside down. Each of those pieces uses the same approach. It calculates the rectangle that should contain the piece, shrinks the rectangle slightly if it will contain text (so the text doesn’t get too close to the grid’s edges), and then calls either DrawText or DrawDivider to draw the piece.
After it finishes drawing all of the sign’s pieces, the method increments NextProductNum so it draw the next product sign the next time it is called.
The following code shows the DrawText method.
The DrawText method draws text inside a rectangle with a given angle of rotation. For this example, the angle is either 0 (right-side up) or 180 (upside down).
The method first creates a StringFormat object and prepares it to draw the text centered in the rectangle.
The code then saves the Graphics object’s state so it can restore it later and resets the object’s transformations to remove any previous ones.
Next, the code creates a rectangle the same size as the target rectangle but centered at the origin. It then applies a rotation to the Graphics object so the text is drawn right-side up or upside down as desired. It then appends a translation transformation to move the result to the original rectangle where the text should appear. Now if the program draws text inside the rectangle at the origin, the text will be rotated and then translated into the desired position.
Before it draws the text, however, the code makes one adjustment. Some of the product signs have long names so their text doesn’t fit within the target rectangle. To fix that, the method makes variable font_size loop from the desired font size down to 6, decreasing by 0.5 each time through the loop. Inside the loop, the code creates a font with the current size and uses the Graphics object’s MeasureString method to see how large the text would be in the test font.
If the text will fit within the target rectangle, the program draws the text and breaks out of the loop. If the text doesn’t fit, the loop continues to try the next smaller font size.
The method finishes by restoring the saved graphics state.
The following DrawDivider method draws a divider much as the DrawText method draws text.
Like the DrawText method, DrawDivider first saves the Graphics object’s state and resets its transformations.
The method then resizes the divider so it is as large as possible but still fits within the target rectangle. To do that, it compares the target rectangle’s aspect ratio (width-to-height ratio) of that of the divider image. (If you look back at the DrawProduct method, you’ll see that the image is stored in the project’s property Properties.Resources.divider.)
If the rectangle is too short and wide, the code makes it narrower so it has its original height but has a width that gives it the correct aspect ratio. Similarly, if the rectangle is too tall and thin, the code makes it shorter so it has the right aspect ratio.
Next the method makes a rectangle of the correct dimensions centered at the origin. It adds transformations to the Graphics object to rotate the divider and translate it so it is placed at the center of the original destination rectangle. Finally, it draws the divider image and restores the Graphics object’s saved state.
This example prints product signs and that may be useful to you. More importantly, it demonstrates some useful printing techniques including:
You may even be able to use the DrawText and DrawDivider methods with minimal modification.
Download the example to see additional details.