We have taken a look at printing in WPF twice before here at SOTC -
first with a simple tutorial on just getting somethingprinted, and then a more complex one on
pagination. Today we are
not going to focus much on the printing side of things, but more on
the printer side. For example, how do you get a list of the printers
available on the system? Or their capabilities? If you need the answers
to those questions, then this is the tutorial for you.
Today, we will be creating a little sample application that finds all
the printers on your system (both local and network printers) and lists
them out. When you pick a particular printer, you will get a list of the
supported page sizes for that printer. Once you pick a page size, you
can then print a test page to the chosen printer at the chosen page size
(and you can even pick landscape or portrait). Oh, and did I mention
that we never have to show the standard print dialog for any of this?
Ok, to start off we first have to get a hold of the list of printers
attached to the system - well, actually, the list of print queues
attached to the system. These are not necessarily physical printers (for
example, if you have a PDF Printer installed on your system), but they
do represent something you can print to. Doing this is actually pretty
easy - you just have to know where to go. First, we need to add the
System.Printing
dll as a reference to our Visual Studio project, since
most of what we need resides in that dll. Once we have that, we want to
get a hold of the local printer server - which really couldn't be any
easier:var server = new PrintServer();
By default, when you create a new
PrintServer
instance, it connects to the local print server. There are other
constructors on
PrintServer
that take things like a path to a
different machine (in case you wanted the print server, for, say, some
central network system), but for today, all we care about is the local
server.
Now that we have the the print server, to get all the available print
queues, we need to call the method
GetPrintQueues
. This method has a
number of different signatures to make it easy for you to get the queues
that you want. The no argument version of the function will generally do
what you need - it will return all the queues that are attached directly
to that print server (in this case, any printer attached to your
computer).
For our sample application, we want to go a little bit beyond that - we
want to grab any network printers as well. To do this we need to use the
GetPrintQueues
call that takes an array of
EnumeratedPrintQueueTypes:var server = new PrintServer();
var queues = server.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections});
The no argument version of
GetPrintQueues
is equivalent to calling
this with just EnumeratedPrintQueueTypes.Local
, and by adding
EnumeratedPrintQueueTypes.Connections
, we get network printers.
Now that we have a collection of
PrintQueues
, let's take a look at
what we can do with one. There is a whole bunch of stuff available off
of
PrintQueue
- you can look at printer status, what jobs are currently queued, and
all sorts of other things. At the moment, though, we are interesting in
printer capabilities. To get the capabilities, you call the method
GetPrintCapabilities
, which returns a
PrintCapabilities.
The capabilities class covers everything from paper size, to duplexing,
even down to if the printer supports automatic stapling. Oh, and an
important thing to note - the PrintCapabilities
class is in the
ReachFramework dll, so to do anything with capabilities, you have to add
that dll to your Visual Studio project references.var server = new PrintServer();
var queues = server.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections});
foreach (var queue in queues)
{
Console.WriteLine(queue.Name);
var capabilities = queue.GetPrintCapabilities();
foreach (PageMediaSize size in capabilities.PageMediaSizeCapability)
{ Console.WriteLine(size.ToString()); }
Console.WriteLine();
}
The code above will print out, for each printer, every paper size that
that printer is capable of handling. For example, on my system:
Send To OneNote 2007
NorthAmericaLetter (816 x 1056)
NorthAmericaTabloid (1056 x 1632)
NorthAmericaLegal (816 x 1344)
ISOA3 (1122.51968503937 x 1587.40157480315)
ISOA4 (793.700787401575 x 1122.51968503937)
ISOA5 (559.370078740158 x 793.700787401575)
JISB4 (971.338582677165 x 1375.74803149606)
JISB5 (687.874015748032 x 971.338582677165)
JapanHagakiPostcard (377.952755905512 x 559.370078740158)
HP Color LaserJet CP2020 Series PCL 6
NorthAmericaLetter (816 x 1056)
NorthAmericaLegal (816 x 1344)
NorthAmericaExecutive (695.811023622047 x 1008)
ISOA3 (1122.51968503937 x 1587.40157480315)
ISOA4 (793.700787401575 x 1122.51968503937)
ISOA5 (559.370078740158 x 793.700787401575)
JISB4 (971.338582677165 x 1375.74803149606)
JISB5 (687.874015748032 x 971.338582677165)
NorthAmerica11x17 (1056 x 1632)
NorthAmericaNumber10Envelope (395.716535433071 x 912)
ISODLEnvelope (415.748031496063 x 831.496062992126)
ISOC5Envelope (612.283464566929 x 865.511811023622)
ISOB5Envelope (665.196850393701 x 944.88188976378)
NorthAmericaMonarchEnvelope (371.905511811024 x 720)
ISOA6 (396.850393700787 x 559.370078740158)
So that covers enough of the basics that we can start putting together
the example application. First, we have most of the code behind:
using System;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace PrintQueuesExample
{
public partial class Window1 : Window
{
PrintQueueCollection _Printers;
public Window1()
{
_Printers = new PrintServer().GetPrintQueues(new[] {
EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections});
InitializeComponent();
}
public PrintQueueCollection Printers
{ get { return _Printers; } }
private void PrintTestPageClick(object sender, RoutedEventArgs e)
{
//TODO: Print Test Page
}
}
public class PrintQueueToPageSizesConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? null :
((PrintQueue)value).GetPrintCapabilities().PageMediaSizeCapability;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ throw new NotImplementedException(); }
}
}
Nothing new here - this is just a reorganization of the code that we
have already covered, getting it into a form that can be easily used by
WPF controls (a public collection of the print queues, a converter to
get from a print queue to a collection of
PageMediaSize
s). Now for
some XAML:<Window x:Class="PrintQueuesExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PrintQueuesExample" x:Name="This"
Title="Print Queue Example" Height="300" Width="300">
<Window.Resources>
<local:PrintQueueToPageSizesConverter x:Key="printQueueToPageSizesConverter" />
<Canvas Margin="10" x:Key="MyPrintingExample">
<TextBlock Canvas.Left="30" Canvas.Top="70">
A bunch of text.
</TextBlock>
<Ellipse Width="30" Height="50" Canvas.Left="200"
Canvas.Top="10" Fill="Blue" />
<Rectangle Width="100" Height="10" Canvas.Left="175"
Canvas.Top="150" Fill="Red" />
<TextBlock FontSize="100" Foreground="Green"
FontWeight="Bold" Canvas.Top="150"
Canvas.Left="20">
{ }
</TextBlock>
</Canvas>
</Window.Resources>
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Available Printers" FontSize="14" />
<ListBox x:Name="_PrinterList" DisplayMemberPath="Name"
x:FieldModifier="private" Grid.Row="1"
ItemsSource="{Binding ElementName=This, Path=Printers}" />
<TextBlock Text="Available Page Sizes for Selected Printer" FontSize="14"
Grid.Row="2" Margin="0 5 0 0"/>
<ListBox Grid.Row="3" x:Name="_SizeList" x:FieldModifier="private"
ItemsSource="{Binding ElementName=_PrinterList, Path=SelectedItem,
Converter={StaticResource printQueueToPageSizesConverter}}" />
<StackPanel Orientation="Horizontal" Margin="0 5 0 0" Grid.Row="4">
<RadioButton Content="Portrait" x:Name="_PortraitRadio" Margin="0 0 10 0"
x:FieldModifier="private" IsChecked="True" />
<RadioButton Content="Landscape" />
</StackPanel>
<Button Grid.Row="5" HorizontalAlignment="Right" Content="Print Test Page"
Click="PrintTestPageClick" />
</Grid>
</Window>
Walking through this XAML, at the top we have some resources - notably
the
PrintQueueToPageSizesConverter
and a Canvas
. It might seem a
little odd to have a Canvas
in resources, but here it is the contents
of our test page (the page that will get printed when a user clicks on
the "Print Test Page" button). By putting it in the resources, we get
the benefit of defining it in XAML, without the downside of it actually
being in the visual tree of the Window
.
Past that, we get to the meat. We have a
ListView
bound to the
collection of print queues (Printers
) that we created in the C# code.
We then have a second ListView
that is bound to the selected item in
the first list view, using the PrintQueueToPageSizesConverter
. This
way, we get the collection of available PageMediaSize
s for the
selected print queue.
Then we have two radio buttons for portrait and landscape - these aren't
attached to anything, we will just be querying their values when the
user clicks "Print Test Page". And finally, we have the "Print Test
Page" button, which is hooked to the method
PrintTestPageClick
.
Now the only thing we haven't covered so far is how to take a print
queue and some selected configuration information, and actually print
something. For that, we have the contents of the
PrintTestPageClick
method:private void PrintTestPageClick(object sender, RoutedEventArgs e)
{
var queue = _PrinterList.SelectedItem as PrintQueue;
if (queue == null)
{
MessageBox.Show("Please select a printer.");
return;
}
var size = _SizeList.SelectedItem as PageMediaSize;
if (size == null)
{
MessageBox.Show("Please select a page size.");
return;
}
queue.UserPrintTicket.PageMediaSize = size;
queue.UserPrintTicket.PageOrientation = _PortraitRadio.IsChecked == true ?
PageOrientation.Portrait : PageOrientation.Landscape;
var canvas = (Canvas)Resources["MyPrintingExample"];
canvas.Measure(new Size(size.Width.Value, size.Height.Value));
canvas.Arrange(new Rect(0, 0, canvas.DesiredSize.Width,
canvas.DesiredSize.Height));
var writer = PrintQueue.CreateXpsDocumentWriter(queue);
writer.Write(canvas);
}
To set up custom settings for a print job, you want to modify the
UserPrintTicker
on the print queue you want to print to. The
UserPrintTicket
is what will be looked at when the time comes to
print, and is in fact what the standard print dialog modifies as the
user changes setting in the dialog. So here, we want to set the
PageMediaSize
property to the selected size, and the PageOrientation
property to the selected orientation.
One important thing to note - just because a printer is capable of a
particular paper size does not mean that it currently has a tray filled
with that type of paper. Choosing a paper size that a printer supports
but does not have any of is valid, and the end result varies depending
on the printer. Some printers will print on the nearest possible size or
their default size, others will wait until the user puts in the correct
size paper. Unfortunately, there isn't a way (that I know of) to query
what types of paper a printer has at this very moment - all the print
capability stuff is about what a printer can potentially do.
Ok, back to printing out the test page. We grab the canvas out from the
resource dictionary, and measure and arrange it according to the chosen
paper size. Then we use a static method on
PrintQueue
called
CreateXpsDocumentWriter
to create an
XpsDocumentWriter
for the print queue we want to print on. It is this XpsDocumentWriter
that we can hand our canvas to to print - and lo and behold, printed
output:
Well, that is it for this quick introduction to
PrintServers
,
PrintQueues
, and PrintCapabilities
. You can grab the Visual Studio
project for the example application below if you would like to poke at
the printers on your own computer.
Source Files:
Comments
Post a Comment