none
Showing printer properties page in VSTO and altering properties RRS feed

  • Question

  • Hi Everyone,
    Sorry in advance for the rather complex nature of this post...   I was wondering if anyone would be able to help here as I am really struggling to get this working now and I cant believe it is not possible to do....   
    What I am aiming for is the following functionality from within a VSTO add-in for Word 2010/2013.   This solution is built on the .NET 4.0 platform at the moment and ideally the answer will work on x86/x64 as well as local and network print queues. 
    • Ability to launch printer properties pages for any installed printer on a machine regardless of operating system version (x86/x64) or printer driver and save the settings on the print driver for when the document is printed (using the ActiveDocument.PrintOut function) 
    • Ability to set the duplex setting (if available) to either Horizontal, Vertical or Simplex and save the setting on the print driver for when the document is printed (using the ActiveDocument.PrintOut function)
    • Ability to switch the Colour setting (if available) to Colour or Greyscale and save the setting on the print driver for when the document is printed (using the ActiveDocument.PrintOut function)
     Here are the problems I face...
    1. We have a dropdown listing the installed printers on a machine, the user can select one of these and then next to the dropdown is a button to launch the print drivers properties page (as if doing it through the Word/File/Print/Printer Properties link button in Word 2010).   This button currently invokes the "OpenPrinterPropertiesDialog(IntPtr windowHandler)" on the PrintHelper106DotNet class (source code listed below).   The issue here is that sometimes it works fine but most of the time I get the "sizeNeeded" returned from the DocumentProperties call (on the NativePrintMethods106 class - source below) returned as -1 with a windows error of 1008 which is apparently "Error 1008 - "An attempt was made to reference a token that does not exist"...   Cany anyone see what I have done wrong or suggest ways that I might make this code more stable?
    2. The PrintSettings Class in .NET appears to be totally ignored by ActiveDocument.PrintOut when I set the IsDuplex properties so I figure changing the print driver selected might work..    There are other calls that can be made to the spool.drv where settings on teh devicemode (DEVMODE) structure can be changed such as "dmDuplex" and "dmColor".   I have experiemented with several approaches to try and set this silently and save the settings but with no luck.    We have also tried the ReachFramework PrintTicket apprach too and this didnt seem to work either..   Can anyone offer any assistance with these two methods?   Something that will be stable across different print drivers and OS platforms.     I realise that SOME printerdrivers will ignore the settings in the standard DEVMODE structure however I assume there is little that can be done about that.    
    Any help would be greatly appriciated..   Code is listed below for the two print helper classes..  
    
    class NativePrintMethods106
    
        {
    
             #region Printing Extern Methods and Consts
    
    
            public const int DM_UPDATE = 1;
    
            public const int DM_COPY = 2;
    
            public const int DM_PROMPT = 4;
    
            public const int DM_MODIFY = 8;
    
    
            public const int DM_IN_BUFFER = DM_MODIFY;
    
            public const int DM_IN_PROMPT = DM_PROMPT;
    
            public const int DM_OUT_BUFFER = DM_COPY;
    
            public const int DM_OUT_DEFAULT = DM_UPDATE;
    
    
            [DllImport("winspool.Drv", EntryPoint = "DocumentPropertiesA", CharSet =CharSet.Auto,  SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    
            public static extern int DocumentProperties
    
                (IntPtr hwnd, IntPtr hPrinter,
    
                [MarshalAs(UnmanagedType.LPWStr)] 
    
                string pDeviceName,
    
                IntPtr pDevModeOutput,
    
                IntPtr pDevModeInput,
    
                int fMode);
    
    
            [DllImport("kernel32.dll", ExactSpelling = true)]
    
            public static extern IntPtr GlobalFree(IntPtr handle);
    
    
            [DllImport("kernel32.dll", ExactSpelling = true)]
    
            public static extern IntPtr GlobalLock(IntPtr handle);
    
    
            [DllImport("kernel32.dll", ExactSpelling = true)]
    
            public static extern IntPtr GlobalUnlock(IntPtr handle);
    
    
    
            /// <summary>
    
            ///  FormatMessage flags
    
            /// </summary>
    
            const int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
    
            const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
    
            const int FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
    
            const int FORMAT_MESSAGE_FROM_STRINGFORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000400;
    
            const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
    
            const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
    
    
            /// <summary>
    
            /// Formats a Win32 error message string
    
            /// </summary>
    
            /// <param name="dwFlags"></param>
    
            /// <param name="lpSource"></param>
    
            /// <param name="dwMessageId"></param>
    
            /// <param name="dwLanguageId"></param>
    
            /// <param name="lpBuffer"></param>
    
            /// <param name="nSize"></param>
    
            /// <param name="va_list_arguments"></param>
    
            /// <returns></returns>
    
            [DllImport("KERNEL32", CharSet = CharSet.Unicode, BestFitMapping = true)]
    
            [ResourceExposure(ResourceScope.None)]
    
            static extern int FormatMessage(int dwFlags, IntPtr lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr va_list_arguments);
    
    
            #endregion
    
    
            /// <summary>
    
            /// Lookup Win32 error code from number
    
            /// </summary>
    
            /// <param name="errorCode"></param>
    
            /// <returns></returns>
    
            public static String GetMessage(int errorCode)
    
            {
    
                StringBuilder sb = new StringBuilder(512);
    
                int result = FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, IntPtr.Zero, errorCode, 0, sb, sb.Capacity, IntPtr.Zero);
    
                if (result != 0)
    
                {
    
                    // result is the # of characters copied to the StringBuilder.
    
                    String s = sb.ToString();
    
                    return String.Format("{0} : {1}", errorCode.ToString(), s).Replace('\n', ' ').Replace('\r', ' ');
    
                }
    
                else
    
                {
    
                    return String.Format("{0} : unknown Win32 error code ", errorCode).Replace('\n', ' ').Replace('\r', ' ');
    
                }
    
            }
    
    
        }
    
    
        #region Printing Structs
    
    
        [Flags()]
    
        enum DM : int
    
        {
    
            Orientation = 0x1,
    
            PaperSize = 0x2,
    
            PaperLength = 0x4,
    
            PaperWidth = 0x8,
    
            Scale = 0x10,
    
            Position = 0x20,
    
            NUP = 0x40,
    
            DisplayOrientation = 0x80,
    
            Copies = 0x100,
    
            DefaultSource = 0x200,
    
            PrintQuality = 0x400,
    
            Color = 0x800,
    
            Duplex = 0x1000,
    
            YResolution = 0x2000,
    
            TTOption = 0x4000,
    
            Collate = 0x8000,
    
            FormName = 0x10000,
    
            LogPixels = 0x20000,
    
            BitsPerPixel = 0x40000,
    
            PelsWidth = 0x80000,
    
            PelsHeight = 0x100000,
    
            DisplayFlags = 0x200000,
    
            DisplayFrequency = 0x400000,
    
            ICMMethod = 0x800000,
    
            ICMIntent = 0x1000000,
    
            MediaType = 0x2000000,
    
            DitherType = 0x4000000,
    
            PanningWidth = 0x8000000,
    
            PanningHeight = 0x10000000,
    
            DisplayFixedOutput = 0x20000000
    
        }
    
    
        struct POINTL
    
        {
    
            public Int32 x;
    
            public Int32 y;
    
        }
    
    
        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
    
        struct DEVMODE
    
        {
    
            public const int CCHDEVICENAME = 32;
    
            public const int CCHFORMNAME = 32;
    
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
    
            [System.Runtime.InteropServices.FieldOffset(0)]
    
            public string dmDeviceName;
    
            [System.Runtime.InteropServices.FieldOffset(32)]
    
            public Int16 dmSpecVersion;
    
            [System.Runtime.InteropServices.FieldOffset(34)]
    
            public Int16 dmDriverVersion;
    
            [System.Runtime.InteropServices.FieldOffset(36)]
    
            public Int16 dmSize;
    
            [System.Runtime.InteropServices.FieldOffset(38)]
    
            public Int16 dmDriverExtra;
    
            [System.Runtime.InteropServices.FieldOffset(40)]
    
            public DM dmFields;
    
    
            [System.Runtime.InteropServices.FieldOffset(44)]
    
            Int16 dmOrientation;
    
            [System.Runtime.InteropServices.FieldOffset(46)]
    
            Int16 dmPaperSize;
    
            [System.Runtime.InteropServices.FieldOffset(48)]
    
            Int16 dmPaperLength;
    
            [System.Runtime.InteropServices.FieldOffset(50)]
    
            Int16 dmPaperWidth;
    
            [System.Runtime.InteropServices.FieldOffset(52)]
    
            Int16 dmScale;
    
            [System.Runtime.InteropServices.FieldOffset(54)]
    
            Int16 dmCopies;
    
            [System.Runtime.InteropServices.FieldOffset(56)]
    
            Int16 dmDefaultSource;
    
            [System.Runtime.InteropServices.FieldOffset(58)]
    
            Int16 dmPrintQuality;
    
    
            [System.Runtime.InteropServices.FieldOffset(44)]
    
            public POINTL dmPosition;
    
            [System.Runtime.InteropServices.FieldOffset(52)]
    
            public Int32 dmDisplayOrientation;
    
            [System.Runtime.InteropServices.FieldOffset(56)]
    
            public Int32 dmDisplayFixedOutput;
    
    
            [System.Runtime.InteropServices.FieldOffset(60)]
    
            public short dmColor;
    
            [System.Runtime.InteropServices.FieldOffset(62)]
    
            public short dmDuplex;
    
            [System.Runtime.InteropServices.FieldOffset(64)]
    
            public short dmYResolution;
    
            [System.Runtime.InteropServices.FieldOffset(66)]
    
            public short dmTTOption;
    
            [System.Runtime.InteropServices.FieldOffset(68)]
    
            public short dmCollate;
    
            [System.Runtime.InteropServices.FieldOffset(72)]
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
    
            public string dmFormName;
    
            [System.Runtime.InteropServices.FieldOffset(102)]
    
            public Int16 dmLogPixels;
    
            [System.Runtime.InteropServices.FieldOffset(104)]
    
            public Int32 dmBitsPerPel;
    
            [System.Runtime.InteropServices.FieldOffset(108)]
    
            public Int32 dmPelsWidth;
    
            [System.Runtime.InteropServices.FieldOffset(112)]
    
            public Int32 dmPelsHeight;
    
            [System.Runtime.InteropServices.FieldOffset(116)]
    
            public Int32 dmDisplayFlags;
    
            [System.Runtime.InteropServices.FieldOffset(116)]
    
            public Int32 dmNup;
    
            [System.Runtime.InteropServices.FieldOffset(120)]
    
            public Int32 dmDisplayFrequency;
    
        }
    
    
    
    
        #endregion
    
    
    
    
    
        /// <summary>
    
        /// .NET Printer helper class
    
        /// </summary>
    
        public class PrinterHelper106DotNet : IPrinterHelper 
    
        {        
    
            private PrinterSettings _printerSettings;
    
    
            public PrinterSettings LoadSettings(string printerName)
    
            {
    
                var pd = new PrintDocument { PrinterSettings = { PrinterName = printerName } };
    
                _printerSettings = pd.PrinterSettings;
    
                return _printerSettings;
    
            }        
    
    
    
            public PrintDuplexSetting GetDuplex()
    
            {
    
                switch (_printerSettings.Duplex)
    
                {
    
                    case Duplex.Simplex:
    
                        return PrintDuplexSetting.Simplex;
    
                    case Duplex.Horizontal:
    
                        return PrintDuplexSetting.Horizontal;
    
                    case Duplex.Vertical:
    
                        return PrintDuplexSetting.Vertical;
    
                    default:
    
                        return PrintDuplexSetting.Default;
    
                }            
    
            }
    
    
            public void SetDuplex(PrintDuplexSetting duplex)
    
            {
    
                switch (duplex)
    
                {
    
                    case PrintDuplexSetting.Horizontal:
    
                        _printerSettings.Duplex = Duplex.Horizontal;
    
                        break;
    
                    case PrintDuplexSetting.Vertical:
    
                        _printerSettings.Duplex = Duplex.Vertical;
    
                        break;
    
                    case PrintDuplexSetting.Simplex:
    
                        _printerSettings.Duplex = Duplex.Simplex;
    
                        break;
    
                    case PrintDuplexSetting.Default:
    
                    default:
    
                        break;
    
                }           
    
            }
    
    
            public PrintColourSetting GetColour()
    
            {
    
                return _printerSettings.DefaultPageSettings.Color == true ? PrintColourSetting.Colour : PrintColourSetting.Greyscale;
    
            }
    
    
            public void SetColour(PrintColourSetting colourPrinting)
    
            {
    
                _printerSettings.DefaultPageSettings.Color = colourPrinting == PrintColourSetting.Colour;
    
            }
    
         
    
    
    
            /// <summary>
    
            /// Uses DEVMODE to display the printer properties dialog of the print driver
    
            /// </summary>
    
            /// <param name="windowHandler"></param>
    
            /// <returns></returns>
    
            public PrinterSettings OpenPrinterPropertiesDialog(IntPtr windowHandler)
    
            {           
    
                LoggingHelper.Info("Starting...");
    
                var printerSettings = _printerSettings;
    
    
                LoggingHelper.Info("Getting page settings to HDevMode object...");
    
                IntPtr ipDevMode = _printerSettings.GetHdevmode();
    
                _printerSettings.DefaultPageSettings.CopyToHdevmode(ipDevMode);
    
    
    
                IntPtr hDevMode = IntPtr.Zero;
    
                IntPtr devModeData = IntPtr.Zero;
    
                String pName = printerSettings.PrinterName;
    
                try
    
                {
    
                    LoggingHelper.Info("Attempt get of HDevMode ...");
    
                    hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
    
                    LoggingHelper.Info("Locking HDevMode object...");
    
                    IntPtr pDevMode = NativePrintMethods106.GlobalLock(hDevMode);
    
    
                    DEVMODE devMode = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE));
    
    
                    LoggingHelper.Info("Obtaining Memory chunk required...");
    
                    int sizeNeeded = NativePrintMethods106.DocumentProperties(windowHandler, IntPtr.Zero, pName, pDevMode, pDevMode, 0); //get needed size and allocate memory 
    
                    if (sizeNeeded < 0)
    
                    {
    
                        LoggingHelper.Info(String.Format("Memory Required [{0}]...", sizeNeeded.ToString()));
    
                        int error = Marshal.GetLastWin32Error();
    
    
                        LoggingHelper.Info("Freeing memory for devModeData and hDevMode object...");
    
                        Marshal.FreeHGlobal(devModeData);
    
                        Marshal.FreeHGlobal(hDevMode);
    
                        devModeData = IntPtr.Zero;
    
                        hDevMode = IntPtr.Zero;
    
    
                        throw new InvalidOperationException("Can't get size of devmode structure - " + NativePrintMethods106.GetMessage(error));
    
                    }
    
                    LoggingHelper.Info(String.Format("Allocating Memory Required for devModeData [{0}]...", sizeNeeded.ToString()));
    
                    devModeData = Marshal.AllocHGlobal(sizeNeeded);
    
                    //show the native dialog 
    
                    LoggingHelper.Info("Attempt to show native print driver dialog windows...");
    
                    int returncode = NativePrintMethods106.DocumentProperties(windowHandler, IntPtr.Zero, pName, devModeData, pDevMode, 14);
    
                    LoggingHelper.Info(String.Format("Return Code from native dialog [{0}]", returncode));
    
                    if (returncode < 0) //Failure to display native dialogue
    
                    {
    
                        int error = Marshal.GetLastWin32Error();
    
    
                        Marshal.FreeHGlobal(devModeData);
    
                        Marshal.FreeHGlobal(hDevMode);
    
                        devModeData = IntPtr.Zero;
    
                        hDevMode = IntPtr.Zero;
    
    
                        throw new InvalidOperationException("Error showing the properties dialog - " + NativePrintMethods106.GetMessage(error));
    
                    }
    
    
                    if (returncode == 2) //User clicked "Cancel"
    
                    {
    
                        LoggingHelper.Info("User clicked cancel - free memory...");
    
                        NativePrintMethods106.GlobalUnlock(hDevMode); //unlocks the memory
    
                        if (hDevMode != IntPtr.Zero)
    
                        {
    
                            LoggingHelper.Info("Free hDevMode memory...");
    
                            Marshal.FreeHGlobal(hDevMode); //Frees the memory
    
                            hDevMode = IntPtr.Zero;
    
                        }
    
                        if (devModeData != IntPtr.Zero)
    
                        {
    
                            LoggingHelper.Info("Free devModeData memory...");
    
                            NativePrintMethods106.GlobalFree(devModeData);
    
                            devModeData = IntPtr.Zero;
    
                        }
    
                    }
    
    
                    LoggingHelper.Info("Unlock Memory");
    
                    NativePrintMethods106.GlobalUnlock(hDevMode); //unlocks the memory
    
                    if (hDevMode != IntPtr.Zero)
    
                    {
    
                        LoggingHelper.Info("Unlocking hDevMode memory...");
    
                        Marshal.FreeHGlobal(hDevMode); //Frees the memory
    
                        hDevMode = IntPtr.Zero;
    
                    }
    
    
                    if (devModeData != IntPtr.Zero)
    
                    {
    
                        LoggingHelper.Info("Pushing new properties to PrinterSettings object...");
    
                        printerSettings.SetHdevmode(devModeData);
    
                        printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
    
                        LoggingHelper.Info("Free DevMode Data memory...");
    
                        NativePrintMethods106.GlobalFree(devModeData);
    
                        devModeData = IntPtr.Zero;
    
                    }
    
                }
    
                catch (Exception ex)
    
                {
    
                    LoggingHelper.HandleException(ex);
    
                }
    
                finally
    
                {
    
                    if (hDevMode != IntPtr.Zero)
    
                    {
    
                        LoggingHelper.Info("Final - Unlocking hDevMode memory...");
    
                        Marshal.FreeHGlobal(hDevMode);
    
                    }
    
                    if (devModeData != IntPtr.Zero)
    
                    {
    
                        LoggingHelper.Info("Final - Unlocking devMode memory...");
    
                        Marshal.FreeHGlobal(devModeData);
    
                    }
    
                }
    
                return printerSettings;
    
            }
    
    
        }
    
    

    Kind regards
    Andy

    Saturday, February 9, 2013 11:31 AM

All replies

  • Hi Iphelion,

    Thanks for posting in the MSDN Forum.

    I will involve some experts into this issue to see whether they can help you out. There might be some time delay, appreciate for your patience.

    Have a good day,

    Tom


    Tom Xu [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, February 11, 2013 1:57 AM
    Moderator
  • Hi Tom,

    Really appreciate your help.   I have posted this on the partner development forum as well but VSTO is not covered by that team.   I am also investigating if we can use some of our partner advisory hours on the issue as well if it comes to it.

    If there is any more info I can provide, please do not hesitate to ask.

    Kind regards

    Andy

    Monday, February 11, 2013 7:46 AM
  • Hello Andy,

    This seems to be too complex to troubleshoot via forum. Because of its complexity your question falls into the paid support category which requires a more in-depth level of support. If the support engineer determines that the issue is the result of a bug the service request will be a no-charge case and you won't be charged. Please visit the below link to see the various paid support options that are available to better meet your needs. http://support.microsoft.com/default.aspx?id=fh;en-us;offerprophone

    Thanks,

    Sreerenj G Nair

    Monday, February 11, 2013 11:30 PM
  • Hi Sreerenj,

    Thank you for your comment.   While I am happy to contact Microsoft and use some partner advisory hours on this I would say don't worry about debugging the code I have provided here, it was more of a reference of the method I was trying.   If you think about what the functionality needs to be, it is not that outrageous.   The way I tried to do it doesn't work, but that does not mean there is not another/better way..  So if you look at it from a problem solving point of view..

    The development requirement is ... "From within a VSTO addin for Office 2010/2013 have a set of buttons and/or dropdowns on the ribbon to perform the following user tasks..

    • Launch printer properties pages for any installed printer on a machine regardless of operating system version (x86/x64) or printer driver and save the settings on the print driver for when the document is printed (using the ActiveDocument.PrintOut function) 
    • Print the current document to a printer using the PrintOut function and forcing the printer to duplex if it has the capability (without the user having to do any manual steps). 
    • Print the current document to a printer using the PrintOut function and forcing the printer to simplex (without the user having to do any manual steps)

    I am open to people totally replacing the code I provided to achieve this functionality if there is a better way to do it than we have tried here.

    Kind regards,

    A

    Tuesday, February 12, 2013 9:53 AM
  • Hello Andy,

    One thing you can try is to record a macro while manually printing the document and check if the code generated addresses your need. If it is not, it cannot be achieved using Word OM. However, you can use .NET Framework printer class to force the printer settings before printing the document.

    As I mentioned earlier, to troubleshoot this further, you need to open a paid support incident.

    Thanks,

    Sreerenj G Nair

    Tuesday, February 26, 2013 2:55 AM
  • Hi Sreerenj,

    Unfortunatley recording a macro did not throw up any code (and thus no clues) so I think the Word OM is out of the question.

    Our technical director is now looking into the issue so for now this one is on hold as an unanswered query.  If we make any progress I will report back.

    Regards

    Andy

    Thursday, February 28, 2013 12:02 PM