|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if !DONOTREFPRINTINGASMMETA
using System;
using System.Printing.Interop;
using System.Printing;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Controls;
namespace MS.Internal.Printing
{
internal partial class Win32PrintDialog
{
/// <summary>
/// This class abstracts the Marshaling of the PrintDlgEx structure to/from
/// Native memory for displaying the Win32 print dialog via the PrintDlgEx
/// API. The reason for this abstraction is to handle a known issue with
/// this API. The 32-bit and 64-bit versions of this method have different
/// packing schemes for the input structure that need to be handled separately
/// to function correctly depending on the CPU archetecture.
/// </summary>
private sealed class PrintDlgExMarshaler : IDisposable
{
#region Constructor
/// <summary>
/// Construct and initialize a PrintDlgExMarshaler instance.
/// </summary>
/// <param name="owner">
/// A Win32 window handle to use as the parent of this dialog.
/// </param>
/// <param name="dialog">
/// A reference to the Win32PrintDialog that contains the necessary
/// data to display a Win32 Print Dialog via the PrintDlgEx call.
/// </param>
internal
PrintDlgExMarshaler(
IntPtr owner,
Win32PrintDialog dialog
)
{
_ownerHandle = owner;
_dialog = dialog;
_unmanagedPrintDlgEx = IntPtr.Zero;
}
#endregion Constructor
#region Finalizer
~PrintDlgExMarshaler()
{
this.Dispose(true);
}
#endregion Finalizer
#region Internal properties
/// <summary>
/// Gets an IntPtr that points to unmanaged memory that represents
/// a PRINTDLGEX structure for calling into PrintDlgEx Win32 API.
/// </summary>
internal IntPtr UnmanagedPrintDlgEx
{
get
{
return _unmanagedPrintDlgEx;
}
}
#endregion Internal properties
#region Internal methods
/// <summary>
/// This method synchronizes the internal PRINTDLGEX unmanaged data
/// structure with the internal Win32 print dialog configuration
/// parameters. You call this prior to a call to PrintDlgEx Win32
/// API to configure the unmanaged memory.
/// </summary>
internal
UInt32
SyncFromStruct()
{
if (_unmanagedPrintDlgEx == IntPtr.Zero)
{
return NativeMethods.PD_RESULT_CANCEL;
}
UInt32 dialogResult = AcquireResultFromPrintDlgExStruct(_unmanagedPrintDlgEx);
if ((dialogResult == NativeMethods.PD_RESULT_PRINT) ||
(dialogResult == NativeMethods.PD_RESULT_APPLY))
{
IntPtr devModeHandle;
string printerName;
UInt32 flags;
PageRange pageRange;
ExtractPrintDataAndDevMode(
_unmanagedPrintDlgEx,
out printerName,
out flags,
out pageRange,
out devModeHandle);
_dialog.PrintQueue = AcquirePrintQueue(printerName);
_dialog.PrintTicket = AcquirePrintTicket(devModeHandle, printerName);
if ((flags & NativeMethods.PD_PAGENUMS) == NativeMethods.PD_PAGENUMS)
{
if (pageRange.PageFrom > pageRange.PageTo)
{
int temp = pageRange.PageTo;
pageRange.PageTo = pageRange.PageFrom;
pageRange.PageFrom = temp;
}
_dialog.PageRangeSelection = PageRangeSelection.UserPages;
_dialog.PageRange = pageRange;
}
else if ((flags & NativeMethods.PD_SELECTION) == NativeMethods.PD_SELECTION)
{
_dialog.PageRangeSelection = PageRangeSelection.SelectedPages;
}
else if ((flags & NativeMethods.PD_CURRENTPAGE) == NativeMethods.PD_CURRENTPAGE)
{
_dialog.PageRangeSelection = PageRangeSelection.CurrentPage;
}
else
{
_dialog.PageRangeSelection = PageRangeSelection.AllPages;
}
}
return dialogResult;
}
/// <summary>
/// This method synchronizes the managed Win32 data with the PRINTDLGEX
/// unmanaged data structure. It is used after a successful call to the
/// PrintDlgEx Win32 API.
/// </summary>
internal
void
SyncToStruct()
{
if (_unmanagedPrintDlgEx != IntPtr.Zero)
{
FreeUnmanagedPrintDlgExStruct(_unmanagedPrintDlgEx);
}
//
// If parent is not valid then get the desktop window handle.
//
if (_ownerHandle == IntPtr.Zero)
{
_ownerHandle = MS.Win32.UnsafeNativeMethods.GetDesktopWindow();
}
//
// Allocate an unmanaged PRINTDLGEX structure with our current internal settings.
//
_unmanagedPrintDlgEx = AllocateUnmanagedPrintDlgExStruct();
}
#endregion Internal methods
#region Private helper methods
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">
/// true if managed resources should be disposed; otherwise, false.
/// </param>
private
void
Dispose(
bool disposing
)
{
if (disposing)
{
if (_unmanagedPrintDlgEx != IntPtr.Zero)
{
FreeUnmanagedPrintDlgExStruct(_unmanagedPrintDlgEx);
_unmanagedPrintDlgEx = IntPtr.Zero;
}
}
}
/// <summary>
/// Extracts the printer name, flags, pageRange, and devmode handle from the
/// given PRINTDLGEX structure.
/// </summary>
/// <param name="unmanagedBuffer">
/// An unmanaged buffer representing the PRINTDLGEX structure. This structure
/// is passed around as an unmanaged buffer since the 32-bit and 64-bit versions
/// of this buffer are not the same and need to be handled uniquely.
/// </param>
/// <param name="printerName">
/// An out parameter to store store the printer name.
/// </param>
/// <param name="flags">
/// The set of flags that are inside the PRINTDIALOGEX structure.
/// </param>
/// <param name="pageRange">
/// The user specified page range that is in PRINTDLGEX structure.
/// This is only useful if the PD_PAGENUMS bit is set in the flags.
/// </param>
/// <param name="devModeHandle">
/// An out parameter to store the devmode handle.
/// </param>
private
void
ExtractPrintDataAndDevMode(
IntPtr unmanagedBuffer,
out string printerName,
out UInt32 flags,
out PageRange pageRange,
out IntPtr devModeHandle
)
{
IntPtr devNamesHandle = IntPtr.Zero;
IntPtr pageRangePtr = IntPtr.Zero;
//
// Extract the devmode and devnames handles from the appropriate PRINTDLGEX structure
//
if (!Is64Bit())
{
NativeMethods.PRINTDLGEX32 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX32>(unmanagedBuffer);
devModeHandle = pdex.hDevMode;
devNamesHandle = pdex.hDevNames;
flags = pdex.Flags;
pageRangePtr = pdex.lpPageRanges;
}
else
{
NativeMethods.PRINTDLGEX64 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX64>(unmanagedBuffer);
devModeHandle = pdex.hDevMode;
devNamesHandle = pdex.hDevNames;
flags = pdex.Flags;
pageRangePtr = pdex.lpPageRanges;
}
//
// Get a managed copy of the page ranges. This only matters if the PD_PAGENUMS bit is
// set in the flags.
//
if (((flags & NativeMethods.PD_PAGENUMS) == NativeMethods.PD_PAGENUMS) &&
(pageRangePtr != IntPtr.Zero))
{
NativeMethods.PRINTPAGERANGE pageRangeStruct = Marshal.PtrToStructure<NativeMethods.PRINTPAGERANGE>(pageRangePtr);
pageRange = new PageRange((int)pageRangeStruct.nFromPage, (int)pageRangeStruct.nToPage);
}
else
{
pageRange = new PageRange(1);
}
//
// Get a managed copy of the device name
//
if (devNamesHandle != IntPtr.Zero)
{
IntPtr pDevNames = IntPtr.Zero;
try
{
pDevNames = UnsafeNativeMethods.GlobalLock(devNamesHandle);
NativeMethods.DEVNAMES devNames = Marshal.PtrToStructure<NativeMethods.DEVNAMES>(pDevNames);
int devNamesOffset = checked(devNames.wDeviceOffset * Marshal.SystemDefaultCharSize);
printerName = Marshal.PtrToStringAuto(pDevNames + devNamesOffset);
}
finally
{
if (pDevNames != IntPtr.Zero)
{
UnsafeNativeMethods.GlobalUnlock(devNamesHandle);
}
}
}
else
{
printerName = string.Empty;
}
}
/// <summary>
/// Acquires an instance of the PrintQueue that cooresponds to the given printer name.
/// </summary>
/// <param name="printerName">
/// The printer name to search for.
/// </param>
private
PrintQueue
AcquirePrintQueue(
string printerName
)
{
PrintQueue printQueue = null;
EnumeratedPrintQueueTypes[] types = new EnumeratedPrintQueueTypes[] {
EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections
};
//
// This forces us to acquire the cached version of the print queues.
// This theoretically should prevent crashing in the printing system
// since all it is doing is reading the registry.
//
PrintQueueIndexedProperty[] props = new PrintQueueIndexedProperty[] {
PrintQueueIndexedProperty.Name,
PrintQueueIndexedProperty.QueueAttributes
};
//
// Get the PrintQueue instance for the printer
//
using (LocalPrintServer server = new LocalPrintServer())
{
foreach (PrintQueue queue in server.GetPrintQueues(props, types))
{
if (printerName.Equals(queue.FullName, StringComparison.OrdinalIgnoreCase))
{
printQueue = queue;
break;
}
}
}
if (printQueue != null)
{
printQueue.InPartialTrust = true;
}
return printQueue;
}
/// <summary>
/// Acquires an instance of a PrintTicket given a handle to a DEVMODE and a printer name.
/// </summary>
/// <param name="devModeHandle">
/// The DEVMODE handle to use for the PrintTicket.
/// </param>
/// <param name="printQueueName">
/// The printer name for the PrintTicket converter.
/// </param>
private
PrintTicket
AcquirePrintTicket(
IntPtr devModeHandle,
string printQueueName
)
{
PrintTicket printTicket = null;
byte[] devModeData = null;
//
// Copy the devmode into a byte array
//
IntPtr pDevMode = IntPtr.Zero;
try
{
pDevMode = UnsafeNativeMethods.GlobalLock(devModeHandle);
NativeMethods.DEVMODE devMode = Marshal.PtrToStructure<NativeMethods.DEVMODE>(pDevMode);
devModeData = new byte[devMode.dmSize + devMode.dmDriverExtra];
Marshal.Copy(pDevMode, devModeData, 0, devModeData.Length);
}
finally
{
if (pDevMode != IntPtr.Zero)
{
UnsafeNativeMethods.GlobalUnlock(devModeHandle);
}
}
//
// Convert the devmode data to a PrintTicket object
//
using (PrintTicketConverter ptConverter = new PrintTicketConverter(
printQueueName,
PrintTicketConverter.MaxPrintSchemaVersion))
{
printTicket = ptConverter.ConvertDevModeToPrintTicket(devModeData);
}
return printTicket;
}
/// <summary>
/// Extracts the result value from a given PRINTDLGEX structure.
/// </summary>
/// <param name="unmanagedBuffer">
/// An unmanaged buffer representing the PRINTDLGEX structure. This structure
/// is passed around as an unmanaged buffer since the 32-bit and 64-bit versions
/// of this buffer are not the same and need to be handled uniquely.
/// </param>
private
UInt32
AcquireResultFromPrintDlgExStruct(
IntPtr unmanagedBuffer
)
{
UInt32 result = 0;
//
// Extract the devmode and devnames handles from the appropriate PRINTDLGEX structure
//
if (!Is64Bit())
{
NativeMethods.PRINTDLGEX32 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX32>(unmanagedBuffer);
result = pdex.dwResultAction;
}
else
{
NativeMethods.PRINTDLGEX64 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX64>(unmanagedBuffer);
result = pdex.dwResultAction;
}
return result;
}
/// <summary>
/// Allocates an unmanaged buffer for the PRINTDLGEX structure and
/// sets the initial values on it.
///
/// NOTE: The reason for returning an unmanaged pointer to a buffer
/// and not just a structure that can be passed to the unmanaged APIs
/// is because this class is providing an abstraction to a problem that
/// exists in the unmanaged print world where the 32-bit and 64-bit
/// structure packing is inconsistent.
/// </summary>
private
IntPtr
AllocateUnmanagedPrintDlgExStruct()
{
IntPtr unmanagedBuffer = IntPtr.Zero;
NativeMethods.PRINTPAGERANGE range;
range.nToPage = (uint)_dialog.PageRange.PageTo;
range.nFromPage = (uint)_dialog.PageRange.PageFrom;
uint defaultFlags =
NativeMethods.PD_ALLPAGES |
NativeMethods.PD_USEDEVMODECOPIESANDCOLLATE |
NativeMethods.PD_DISABLEPRINTTOFILE |
NativeMethods.PD_HIDEPRINTTOFILE;
try
{
if (!Is64Bit())
{
NativeMethods.PRINTDLGEX32 pdex = new NativeMethods.PRINTDLGEX32();
pdex.hwndOwner = _ownerHandle;
pdex.nMinPage = _dialog.MinPage;
pdex.nMaxPage = _dialog.MaxPage;
pdex.Flags = defaultFlags;
if (_dialog.SelectedPagesEnabled)
{
if (_dialog.PageRangeSelection == PageRangeSelection.SelectedPages)
{
pdex.Flags |= NativeMethods.PD_SELECTION;
}
}
else
{
pdex.Flags |= NativeMethods.PD_NOSELECTION;
}
if (_dialog.CurrentPageEnabled)
{
if (_dialog.PageRangeSelection == PageRangeSelection.CurrentPage)
{
pdex.Flags |= NativeMethods.PD_CURRENTPAGE;
}
}
else
{
pdex.Flags |= NativeMethods.PD_NOCURRENTPAGE;
}
if (_dialog.PageRangeEnabled)
{
pdex.lpPageRanges = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.PRINTPAGERANGE)));
pdex.nMaxPageRanges = 1;
if (_dialog.PageRangeSelection == PageRangeSelection.UserPages)
{
pdex.nPageRanges = 1;
Marshal.StructureToPtr(range, pdex.lpPageRanges, false);
pdex.Flags |= NativeMethods.PD_PAGENUMS;
}
else
{
pdex.nPageRanges = 0;
}
}
else
{
pdex.lpPageRanges = IntPtr.Zero;
pdex.nMaxPageRanges = 0;
pdex.Flags |= NativeMethods.PD_NOPAGENUMS;
}
//
// If we know a print queue and print ticket, then we need to try to select
// the printer in the dialog and use the print ticket. We allocate and setup
// a DEVNAMES structure for this as well as convert the PrintTicket to a
// DEVMODE.
//
if (_dialog.PrintQueue != null)
{
pdex.hDevNames = AllocateAndInitializeDevNames(_dialog.PrintQueue.FullName);
if (_dialog.PrintTicket != null)
{
pdex.hDevMode = AllocateAndInitializeDevMode(
_dialog.PrintQueue.FullName,
_dialog.PrintTicket);
}
}
int cbBufferSize = Marshal.SizeOf(typeof(NativeMethods.PRINTDLGEX32));
unmanagedBuffer = Marshal.AllocHGlobal(cbBufferSize);
Marshal.StructureToPtr(pdex, unmanagedBuffer, false);
}
else
{
NativeMethods.PRINTDLGEX64 pdex = new NativeMethods.PRINTDLGEX64();
pdex.hwndOwner = _ownerHandle;
pdex.nMinPage = _dialog.MinPage;
pdex.nMaxPage = _dialog.MaxPage;
pdex.Flags = defaultFlags;
if (_dialog.SelectedPagesEnabled)
{
if (_dialog.PageRangeSelection == PageRangeSelection.SelectedPages)
{
pdex.Flags |= NativeMethods.PD_SELECTION;
}
}
else
{
pdex.Flags |= NativeMethods.PD_NOSELECTION;
}
if (_dialog.CurrentPageEnabled)
{
if (_dialog.PageRangeSelection == PageRangeSelection.CurrentPage)
{
pdex.Flags |= NativeMethods.PD_CURRENTPAGE;
}
}
else
{
pdex.Flags |= NativeMethods.PD_NOCURRENTPAGE;
}
if (_dialog.PageRangeEnabled)
{
pdex.lpPageRanges = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.PRINTPAGERANGE)));
pdex.nMaxPageRanges = 1;
if (_dialog.PageRangeSelection == PageRangeSelection.UserPages)
{
pdex.nPageRanges = 1;
Marshal.StructureToPtr(range, pdex.lpPageRanges, false);
pdex.Flags |= NativeMethods.PD_PAGENUMS;
}
else
{
pdex.nPageRanges = 0;
}
}
else
{
pdex.lpPageRanges = IntPtr.Zero;
pdex.nMaxPageRanges = 0;
pdex.Flags |= NativeMethods.PD_NOPAGENUMS;
}
//
// If we know a print queue and print ticket, then we need to try to select
// the printer in the dialog and use the print ticket. We allocate and setup
// a DEVNAMES structure for this as well as convert the PrintTicket to a
// DEVMODE.
//
if (_dialog.PrintQueue != null)
{
pdex.hDevNames = AllocateAndInitializeDevNames(_dialog.PrintQueue.FullName);
if (_dialog.PrintTicket != null)
{
pdex.hDevMode = AllocateAndInitializeDevMode(
_dialog.PrintQueue.FullName,
_dialog.PrintTicket);
}
}
int cbBufferSize = Marshal.SizeOf(typeof(NativeMethods.PRINTDLGEX64));
unmanagedBuffer = Marshal.AllocHGlobal(cbBufferSize);
Marshal.StructureToPtr(pdex, unmanagedBuffer, false);
}
}
catch (Exception)
{
if (unmanagedBuffer != IntPtr.Zero)
{
FreeUnmanagedPrintDlgExStruct(unmanagedBuffer);
unmanagedBuffer = IntPtr.Zero;
}
throw;
}
return unmanagedBuffer;
}
/// <summary>
/// Frees an unmanaged buffer.
/// </summary>
/// <param name="unmanagedBuffer">
/// An unmanaged buffer representing the PRINTDLGEX structure. This structure
/// is passed around as an unmanaged buffer since the 32-bit and 64-bit versions
/// of this buffer are not the same and need to be handled uniquely.
/// </param>
private
void
FreeUnmanagedPrintDlgExStruct(
IntPtr unmanagedBuffer
)
{
if (unmanagedBuffer == IntPtr.Zero)
{
return;
}
IntPtr devModeHandle = IntPtr.Zero;
IntPtr devNamesHandle = IntPtr.Zero;
IntPtr pageRangePtr = IntPtr.Zero;
//
// Extract the devmode and devnames handles from the appropriate PRINTDLGEX structure
//
if (!Is64Bit())
{
NativeMethods.PRINTDLGEX32 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX32>(unmanagedBuffer);
devModeHandle = pdex.hDevMode;
devNamesHandle = pdex.hDevNames;
pageRangePtr = pdex.lpPageRanges;
}
else
{
NativeMethods.PRINTDLGEX64 pdex = Marshal.PtrToStructure<NativeMethods.PRINTDLGEX64>(unmanagedBuffer);
devModeHandle = pdex.hDevMode;
devNamesHandle = pdex.hDevNames;
pageRangePtr = pdex.lpPageRanges;
}
if (devModeHandle != IntPtr.Zero)
{
UnsafeNativeMethods.GlobalFree(devModeHandle);
}
if (devNamesHandle != IntPtr.Zero)
{
UnsafeNativeMethods.GlobalFree(devNamesHandle);
}
if (pageRangePtr != IntPtr.Zero)
{
UnsafeNativeMethods.GlobalFree(pageRangePtr);
}
Marshal.FreeHGlobal(unmanagedBuffer);
}
/// <summary>
/// Returns a boolean value representing whether the current runtime is
/// 32-bit or 64-bit.
/// </summary>
private
bool
Is64Bit()
{
return IntPtr.Size == sizeof(long);
}
/// <summary>
/// This method allocates a DEVNAMES structure in unmanaged code and configures
/// it to point to the specified printer.
/// </summary>
/// <param name="printerName">
/// The printer name to use for the DEVNAMES structure.
/// </param>
/// <returns>
/// Returns an IntPtr pointing to a memory address in unmanaged code where
/// the structure has been initialized.
/// </returns>
private
IntPtr
AllocateAndInitializeDevNames(
string printerName
)
{
IntPtr hDevNames = IntPtr.Zero;
char[] printer = printerName.ToCharArray();
//
// Enough memory for the printer name character array + 3 null characters
// + the DEVNAMES structure. The 3 null characters are for the end of the
// printer name string, and 2 distinct empty strings.
//
int cbDevNames = checked(((printer.Length + 3) * Marshal.SystemDefaultCharSize) +
Marshal.SizeOf(typeof(NativeMethods.DEVNAMES)));
hDevNames = Marshal.AllocHGlobal(cbDevNames);
ushort baseOffset = (ushort)Marshal.SizeOf(typeof(NativeMethods.DEVNAMES));
//
// The wDeviceOffset contains an offset in characters to the printer name
// within the DEVNAMES structure. The wDriverOffset and wOutputOffset
// are offsets to two distinct empty strings that just happen to follow
// the printer name string. For more information on how this structure is
// layed out, please review the MSDN documentation for DEVNAMES structure.
//
NativeMethods.DEVNAMES devNames;
devNames.wDeviceOffset = (ushort)(baseOffset / Marshal.SystemDefaultCharSize);
devNames.wDriverOffset = checked((ushort)(devNames.wDeviceOffset + printer.Length + 1));
devNames.wOutputOffset = checked((ushort)(devNames.wDriverOffset + 1));
devNames.wDefault = 0;
Marshal.StructureToPtr(devNames, hDevNames, false);
//
// Calculate the position of the string containing the printer name
// - Printer name character array immediately follows the DEVNAMES structure
// - followed by 3 NULL characters
//
IntPtr offsetName =
checked((IntPtr)((long)hDevNames + (long)baseOffset));
IntPtr offsetNull =
checked((IntPtr)((long)offsetName + (printer.Length * Marshal.SystemDefaultCharSize)));
//
// Write the printer name and 3 NULL characters. The first NULL character
// is to null terminate our printer name string. The second and third null
// characters are to create to distinct empty strings within the buffer.
//
// NOTE: The byte array contains the NULL characters in a form that we are
// able to write them out. There are 3 * char size bytes total.
//
byte[] nulls = new byte[3 * Marshal.SystemDefaultCharSize];
Array.Clear(nulls, 0, nulls.Length);
Marshal.Copy(
printer,
0,
offsetName,
printer.Length);
Marshal.Copy(
nulls,
0,
offsetNull,
nulls.Length);
return hDevNames;
}
/// <summary>
/// This method allocates and initializes an unmanaged DEVMODE structure
/// for use to by the PrintDlgEx method based on the specified printer
/// and PrintTicket object.
/// </summary>
/// <param name="printerName">
/// The printer that is being targetted by the PrintTicket.
/// </param>
/// <param name="printTicket">
/// The PrintTicket that will be converted to a DEVMODE.
/// </param>
/// <returns>
/// An unmanaged pointer to an unmanaged DEVMODE structure that can be
/// used in the PRINTDLGEX structure for a call to PrintDlgEx.
/// </returns>
private
IntPtr
AllocateAndInitializeDevMode(
string printerName,
PrintTicket printTicket
)
{
byte[] devModeData = null;
//
// Convert the PrintTicket object to a DEVMODE
//
using (PrintTicketConverter ptConverter = new PrintTicketConverter(
printerName,
PrintTicketConverter.MaxPrintSchemaVersion))
{
devModeData = ptConverter.ConvertPrintTicketToDevMode(
printTicket,
BaseDevModeType.UserDefault);
}
//
// Make the dev mode data a DEVMODE structure in global memory
//
IntPtr hDevMode = Marshal.AllocHGlobal(devModeData.Length);
Marshal.Copy(devModeData, 0, hDevMode, devModeData.Length);
return hDevMode;
}
#endregion Private helper methods
#region Private data
private
Win32PrintDialog _dialog;
private
IntPtr _unmanagedPrintDlgEx;
private
IntPtr _ownerHandle;
#endregion Private data
#region IDisposable implementation
/// <summary>
/// Implements the Dispose method for IDisposable. This ensures that
/// any unmanaged PRINTDLGEX structure that was allocated by the class
/// will be property freed when this class goes away.
/// </summary>
public
void
Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion IDisposable implementation
}
}
}
#endif
|