Solved

300 Points.  Dll to change default printer paper size

Posted on 2001-06-14
12
463 Views
Last Modified: 2013-11-20
I've come to the end of my rope.  I am needing a dll that changes the default printer paper size to legal. and the returns to the previous state.I think it needs to be a dll so that it can be used in Access 97.  Access doesn't allow any changing of printer sizes programmically unless you leave the program in an MBD state.   Up on entering Access MDE it would store the present papersize and change it to legal. After exiting Access it would then restore the previous papersize.

Any Help, Any Place,
Any Time Please.....

Charlie3
0
Comment
Question by:chasferr
  • 5
  • 3
  • 2
  • +2
12 Comments
 
LVL 32

Accepted Solution

by:
jhance earned 100 total points
ID: 6193201
1) Use GetDefaultPrinter() to get the name of the default printer.

2) Use OpenPrinter() to open the printer and get a handle to it.

3) Use DocumentProperties() to get the current page size and to set the desired one.

4) Use ClosePrinter() to close the handle to the printer.

From ACCESS you need a COM object not a standard DLL.  A COM in-process server is implemented as a DLL but it must be a COM DLL, not a "standard" DLL.

There are hooks in ACCESS for running VBA code when entering the application and when leaving so this is possible.  I'm just not sure how to do it since I'm not much of a VBA or ACCESS guru...
0
 
LVL 2

Assisted Solution

by:Chandra V
Chandra V earned 100 total points
ID: 6194628
Add a Function to your App Class like this

CMyApp::SetPaperLegal()
{
     PRINTDLG pd;
     pd.lStructSize=(DWORD)sizeof(PRINTDLG);
     BOOL bRet=GetPrinterDeviceDefaults(&pd);
     if(bRet)
     {
      // protect memory handle with ::GlobalLock and ::GlobalUnlock
      DEVMODE FAR *pDevMode=(DEVMODE FAR *)::GlobalLock(m_hDevMode);
      // set orientation to landscape
  pDevMode->dmPaperSize = DMPAPER_LEGAL;
      ::GlobalUnlock(m_hDevMode);
     }
}

For Additional Settings seee 'CWinApp::GetPrinterDeviceDefaults' Documentation
0
 

Author Comment

by:chasferr
ID: 6194681
jhance,
  Thank you for the comment However, where do I find the documentation on
1) Use GetDefaultPrinter() to get the name of the default printer.

2) Use OpenPrinter() to open the printer and get a handle to it.

3) Use DocumentProperties() to get the current page size and to set the desired one.

4) Use ClosePrinter() to close the handle to the printer.
Thanks
0
 

Author Comment

by:chasferr
ID: 6194695
To cvallabhaneni
  Thank you for your comment However...

How do I get to this from access. Any ideas.

Add a Function to your App Class like this

CMyApp::SetPaperLegal()
{
    PRINTDLG pd;
    pd.lStructSize=(DWORD)sizeof(PRINTDLG);
    BOOL bRet=GetPrinterDeviceDefaults(&pd);
    if(bRet)
    {
     // protect memory handle with ::GlobalLock and ::GlobalUnlock
     DEVMODE FAR *pDevMode=(DEVMODE FAR *)::GlobalLock(m_hDevMode);
     // set orientation to landscape
 pDevMode->dmPaperSize = DMPAPER_LEGAL;
     ::GlobalUnlock(m_hDevMode);
    }
}

For Additional Settings seee 'CWinApp::GetPrinterDeviceDefaults' Documentation

Charlie
0
 
LVL 2

Expert Comment

by:Chandra V
ID: 6194744
Hai chasferr,

The following article helps in your case

http://www.microsoft.com/AccessDev/Articles/GetzCh10.HTM
0
 
LVL 32

Expert Comment

by:jhance
ID: 6194750
Documentation for all Windows API functions is in the MSDN library.  If you have a Windows development tool it is surely included.  If not, you can download the Windows SDK from Microsoft free.  You can also access the MSDN library online free of charge at http://msdn.microsoft.com.
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 2

Expert Comment

by:Chandra V
ID: 6194765
A Sample Code for managing Access Printer Settings
ftp://ftp.winsite.com/pub/pc/win3/access/chgprn.zip
0
 

Author Comment

by:chasferr
ID: 6194797
To cvallabhaneni
THanks again, up early huh,
   I've used the routine, the problem is it doesn't work unless you keep your application as a mdb. which exposes your code.
So..
  I forgot that I decided that I can't do it within access. I just need to change the default printer paper size using an exe file before actually loading the access application.
Any ideas.

Chasferr
0
 

Author Comment

by:chasferr
ID: 6220298
cvallabhaneni,
 Can you put the whole piece together. It doesn't have to be a dll.  Just, a program the changes the default printer to legal. And a program to change the default printer to letter.  This can be run outside any app.  
Something to programmically take the place of manually going to settings, control panel, printers properties and doing it manually.  I feel like you are close  
0
 
LVL 2

Assisted Solution

by:christophm
christophm earned 100 total points
ID: 6501725
Hi chasferr,

I believe your needs are answered by cvallabhaneni's code and jhance's suggestion that you put the code into an Automation component.  Automation components are the easiest thing for the VB/VBA code to tet to.  Creating an automation component is not difficult, I have included - it follows this note - a COMPREHENSIVE explanation of how to do that.  If this satisfies your needs do not give me any points for this - the others have really answered your question.

christophm

This is a writeup I did a while ago on how to create an automation
component and then use the exposed methods and properties from VB
(or VBScript or VBA).  If you follow this step by step you will create
a working automation component.  At the end of this writeup I will take
you through using the automation component from VBScript - (it will work
the same from VB and VBA.)

By The Way - I learned this from the Microsoft Press Book, "Inside Visual
C++", by  David Kruglinski.

To create an automation component you start with the the simplest
application that the wizard will support.  The way to get a simple
application is to use the wizard to create a dialog application then
delete all the code and all the files that you can from that
application.  This first section explains how to do that.

Use the the wizard to create a dialog application. (I named it
'Christophm' and put it in the 'Christophm' subdirectory).

I deselected (clicked to turn the check mark off) all the options in
Step 2 of the app wizard.

I checked the "As a statically linked library" box in Step 3.  (I do
this because some of my stuff runs all over North America, and some in
Europe.  I statically link so I won't have as many problems with installs
on PCs that are far away).

I made no other changes in the app wizard options.

Do not run the application yet!  We won't run this until we are further
along.

Now you have to delete the files from the project and from the disk
that are not needed for an automation component.  The app is not going to
end up being a dialog application so you don't need either the
dialog.cpp or the dialog.h files.  Go to the "FileView" tab (in the
IDE) and delete  "CHRISTOPHMDlg.h"  from the 'Header Files' and
delete "CHRISTOPHMDlg.cpp" from the 'Source Files'.

You have removed the CHRISTOPHMDlg.h and CHRISTOPHMDlg.cpp files from
the project's list of files but the physical files are still in subdirectory.  
Go to Windows Explorer and delete those two files from the hard drive.

There is still a reference (the  'include "CHRISTOPHMDlg.h"' at the top
of the "CHRISTOPHM.h" file.  Go to the "ClassView" tab, open the
"CHRISTOPHMApp.h" file and then delete the line near the top that says    
'include "CHRRISTOPHMDlg.h"

Now (using 'ClassView' again) open the function "InitInstance()".  You
see that "InitInstance()" has all the code to display the dialog
CHRISTOPHMDlg.  Delete everything in "InitInstance()" except these
three lines :

{
      return FALSE;
}

      Now "InitInstance() looks like this !

BOOL CJunkApp::InitInstance()
{
      return FALSE;
}


THIS FINISHES THE MAIN STEP ONE.  THESE STEPS REMOVED ALL
REFERENCES TO THE DIALOG CLASS FROM THE APPLICATION.


Next you have to enable your application for automation.  

Add the include "#include <afxdisp.h>" at the top after the other
includes that you find in stdafx.h in your project (you can use the
'FileView' tab to open stdafx.h and then type in that line.)

Now Change "InitInstance()" so it looks like this.

BOOL CJunkApp::InitInstance()
{
      AfxOleInit();
      
      if (RunEmbedded() || RunAutomated())
      {
            // component started by COM
            COleTemplateServer::RegisterAll();
            return TRUE;
      }
            
      // component is being run directly by the user
      COleObjectFactory::UpdateRegistryAll();
      ::AfxMessageBox("CHRISTOPHM server is registered");

      return FALSE;
}


This component is coded to register itself so that the registration
will not be a separate step.  (in an install procedure (install shield?)
this .EXE should be copied onto the target PC hard drive and also it
should be exectued to register the component on the target PC)

When it registers itself you will see the message from the
'AfxMessageBox()' in 'InitInstance()' above.  If you remove the
'AfxMessageBox()' the component will still register itself but won't
display anything.  


NOW AT THIS TIME YOU HAVE A WINDOWLESS (ALL DIALOG BOX STUFF WAS REMOVED)
APPLICATION THAT IS ENABLED TO BE AN AUTOMATION (COMPONENT) SERVER.  THIS
FINISHES MAIN STEP TWO.

Now you have to add in your own application's special code.

This code is what makes your automation component application do
something for you.  I am going to give an example of a 'method' and a
'Get/Set' property.  Everything your 'server' program exposes to the
'client' should be either as a 'method' or as a 'Get/Set' property.  

(For your 'set printer to legal' and 'set printer to 'standard' actions
I suggest you use a method).

Automation components are a special class of COM components, they are
designed to interface easily with VB/VBA/VBScript and 'methods' and
'Get/Set' properties are the technique that will be most familiar to the
VB users of your component. The client in this example will be a VBScript
but the code needed in VBA is identical.

From the Class Wizard click the 'Add Class' button on any tab of the Wizard
Dialog to add a new class.  Choose 'New Class' and add a class named  
'SAMPLE'.    Choose 'CCmdTarget' for the Base Class and in the
"Automation" area click the radio button "Creatable by Type ID".  
You will see that the wizard has filled in  "CHRISTOPHM.Sample"  as
the Type ID.  You can make this name anything you want, it goes in the
registry and it is what you will use in the VBScript code that uses
this component.

To access this component from VBScript (or VB/VBA) the code will look like :    
     CreateObject("CHRISTOPHM.Sample")      

There are complete VBScript code examples later on in this note.

Now Click "OK" in the "New Class" dialog box.
      
Now you have added a class   ("CHRISTOPHM.Sample") to the automation
component named "CHRISTOPHM".  We will go back to the wizard in a few
steps but close the wizard now and look at the ClassView tab.  You will
see the class "Sample" in the ClassView tree.
      
The next step is to add methods and properties to the component.  The
'properties' represent the state of the component - the properties often
(but not always!) have a one-to-one relationship with variables in the
component class.  The methods are ways to change the state(properties)
- the methods are functions in the component that change variables in the
component.  

Run the wizard again and select the 'Automation' tab.  The
project edit control has "CHRISTOPHM" and the class name edit control has
"Sample".  Click "Add Method" to add a method.
      
Add a method called "Hello".  Make both the 'External' and 'Internal'
name "Hello" - making them different would make it harder to keep up
with.  Make the "Return Type" void, and do not use any parameters.  
(I am making a simple example, for your needs you will be adding methods
'Set_Printer_to_Legal' and 'Set_Printer_Back' kinds of methods)
      
Click "OK", then close the wizard dialog.  In the "ClassView" tab you
will see the function "Hello" appear.  Double click "Hello" in
"ClassView" and put in a message box.
      
      void Sample::Hello()
      {
        ::AfxMessageBox "Hello Computer !"
      }
      
Go ahead and run the component.  You will know when it compiles, links
and runs successfully because you will see the message box from
'InitInstance()' displayed.

Now I am going to explain how to make a method do something
more than display a message box, and explain how to add a 'Get/Set'
property but first LET'S TEST THE COMPONENT.
      
Here is a test script file  "01.VBS".  Create this file somewhere
on your hard drive.  The 'associations' in Windows will associate the
.VBS extension with the scripting engine.  This means that you can
double click the 'KH01.VBS' file in Windows-Explorer and it will run.
      
      ' everything is a VARIANT in VBScript
      dim IMA_AutoComp
      
      ' create an instance of "CHRISTOPHM.Sample "
      Set IMA_AutoComp = CreateObject("CHRISTOPHM.Sample ")
      
      ' call/run the method 'Hello'
      IMA_AutoComp.Hello
      
      ' cleanup
      set AutoComp = Nothing
      
Now if your PC has the scripting engine (if you can run VBScript then
it does) then you will see the "Hello Computer !" message box.
If you don't then (1) you have not followed my example exactly - you
should delete everything in the CHRISTOPHM subdirectory and try
again, (2) your PC doesn't have the 'Scripting Engine' - I understand
that scripting comes as a part of Win2000, IE5, and that there is a
patch to add it to W95 PCs that have IE4.
      
BTW - Remember that when you have your application's component done
The component needs to be run once as an .EXE (double click it in Explorer
or run it from the IDE) to register it on your PC.
      
So far all the component is doing for you is displaying a message box.  
The next two things to explain are; 1-making a method do something,
and, 2-illustrating Get/Set properties.  

AT THIS POINT YOU HAVE A COMPONENT WITH A WORKING METHOD THAT YOU CAN
CALL FROM VBSCRIPT.  

NEXT I EXPLAIN HOW TO MAKE A METHOD THAT 'DOES SOMETHING'.

I will describe adding a method that will change the message (from VBScript)
that the 'Sample::Hello()' method displays.  You have already added the
'Hello()' method so I'm going to shorten my explanations some.

Add a variable to the class definition of the 'Sample' class.  (this is
in the 'Sample.h' file) this variable will hold a message that the 'Hello()'
method will cause to display from VBScript - and  - I will add a method that
changes the message.

     CString     wwMessage;

Add this line to the constructor (the function "Sample::Sample()") of the Sample
class.

     wwMessage = "Hello from Chris Miller";


Change the 'Hello()' function to this (now instead of displaying a
string literal it displays whatever is in 'wwMessage'

   void Sample::Hello()
   {
     ::AfxMessageBox(wwMessage);
   }

Run the class wizard and pick the "Automation" tab.  Click the
"Add Method" button.

Type    ChangeMessage    in the "External Name" and in the
"Internal Name"  (it will automatically fill in the "Internal Name"
for you).

Select   'void'    for the "Return Type".  Add one parameter, make
the name   "varNewMessage"    and make it a "Variant" .   You have to
click around some under both columns in the "Parameter List" box, it's a
little tricky to get the focus in the 'Type' column.

Click "OK" and you will go back to the Class Wizard.  You will see that
both "ChangeMessage" and "Hello" are in the 'External Names' list of the
'Automation' tab.

Select "ChangeMessage" and click the "Edit Code" button.  You will see
that the function (method to the VB people) appears in the 'ClassView'
and it belongs to the 'Sample' class.  The edit window positions you on
this function.

   void Sample::ChangeMessage(const VARIANT FAR& varNewMessage)
   {
         // TODO: Add your dispatch handler code here
   }

Change the function to --

   void Sample::ChangeMessage(const VARIANT FAR& varNewMessage)
   {
     wwMessage = (LPCWSTR)varNewMessage.bstrVal;
   }

A warning here !   This function assumes that a string value has been
assigned to the parameter by the VB client.  The variable corresponding
to varNewMessage on the VB client side is a VARIANT and the VB client
could have set it to anything - an integer?, a float?, a reference to
a Recordset?, ..    In a production app you should check the
type of the VARIANT.  Explaining variants would take a lot of writing
so I'm not going to do that here, if you are using VB (VBA or VBScript)
and plan to use components written in C/C++ then you are going to
have to learn a little about VARIANTs.  Basically you can pass many
different data types and there is an easy way to tell what data type the
VARIANT happens to contain at the moment.

Time to test again.  Compile/link/run the component, when it is
successful you will get the registration message.  Then run the
following script

   ' everything is a VARIANT in VBScript
   dim IMA_AutoComp

   ' create an instance of "CHRISTOPHM.Sample"
   Set IMA_AutoComp = CreateObject("CHRISTOPHM.Sample")

   ' call/run the method 'Hello' (the initial message displays)
   IMA_AutoComp.Hello

   ' change the message
   dim NewMessage
   NewMessage = "I like cookies !"
   IMA_AutoComp.ChangeMessage(NewMessage)

   ' call/run the method 'Hello' again (the new message displays)
   IMA_AutoComp.Hello

   ' cleanup
   set AutoComp = Nothing

At this point you have created a component, it self registers, it has
two methods (Hello and ChangeMessage) and you can set the message
string that 'Hello()' displays by using 'ChangeMessage()'.  There is
really only one trick left in my bag and that is the 'Get/Set'
property(ies).


CHASFERR - this is really as far as you need to go, everything you
wanted can be done with a couple of methods and using the code
samples from cvallabhaneni.  I leave the rest of this stuff in here
in case you want to learn more about components.


The following notes will detail creating a Get/Set property.

The procedures for creating 'Get/Set' properties are much like what
you did with the two methods so I'm going to abbreviate some.

Run the wizard, go to the Automation tab, click "Add Property"

In the "Implementation" box near the center of the "Add Property" dialog
there are three radio buttons.  "Member Variable" is selected, change it
to select "Get/Set Methods".

Type  "AThing"  in the "External Name" - you will see some of the other
edit controls auto fill.  For the "Type" pick VARIANT.  Notice the names
of the "Get" and "Set" functions.  Click "OK"

Now in the "External Names" list box on the "Automation" tab you will see
"AThing" in addition to "ChangeMessage" and "Hello"

Double click on "Athing" in the list box and you will be positioned here
in the code.

   VARIANT Sample::GetAThing()
   {
      VARIANT vaResult;
      VariantInit(&vaResult);
      // TODO: Add your property handler here

      return vaResult;
   }

   void Sample::SetAThing(const VARIANT FAR& newValue)
   {
     // TODO: Add your property handler here
   }
 
Now - (We are almost finished !)  It is difficult for some people (it
was for me!) to understand that what Get/Set methods really do is
invisible to the VB side.  The VB side (client) is going to run the
Get/Set methods and to the VB side it will appear that a single
variable is changed in the component, this may or may not be true.  
What the C++ component is doing is invisible to the VB client.  As an
example maybe, for some particular pair of Get/Set properties, the Set
property sets international time zone, and the Get property returns the
current time in that time zone.  You see in the time zone example the
Get/Set properties are not just a simple single variable in the C++ server.  
You can make the C++ server do whatever you want (or even do nothing!)
in either one, or both, of the Get/Set functions.  If you want a property
to be 'Get' only then just remove all the active code from the 'Set'
function and the VB client cannot change the property.

Though the Get/Set can be much more than just a way for the VB client to
change and get a single variable for this example I am going to map
the Get/Set property to just a single variable in the C++ code.

Add a variable to the 'Sample' class

   CString    csAProperty;

Initialize the variable in the Sample constructor

   csAProperty = "XXXXXXXXX";

Go back to the functions that are the Get/Set properties (use the Wizard
or use Class View to get there) and change them to the following.


   VARIANT Sample::GetAThing()
   {
     VARIANT vaResult;
     VariantInit(&vaResult);

     // TODO: Add your property handler here
     CString csWorkString;
     csWorkString = csAProperty;

     vaResult.vt = VT_BSTR;
     vaResult.bstrVal = csWorkString.AllocSysString();
      
     return vaResult;
}


   void Sample::SetAThing(const VARIANT FAR& newValue)
   {
     // you see here that the VB user doesn't see how
     // we do it but when he changes a property with
     // the 'Set' the variable mysteriously gets a "XX"
     // added in front and in back !

     // TODO: Add your property handler here
     CString     csJunk("XX");
     csAProperty = csJunk + (LPCWSTR)newValue.bstrVal + csJunk;
   }

You see that no matter how the client changes "AThing" with the Set
property the Get property returns it with an "XX" in front and in back.  
A Get/Set property can be associated with anything in the C++ code.  
You can remove all the code from either the Get or the Set property to
permit the client to only Set or only Get.

BTW - you also see some of the MFC/C++ supplied tools for converting
back and forth between a VARIANT containing a string (called a BSTR)
and a conventional CString.

Here is some VBScript that will use all the features in this example.

   ' everything is a VARIANT in VBScript
   dim IMA_AutoComp

   ' create an instance of "CHRISTOPHM.Sample"
   Set IMA_AutoComp = CreateObject("CHRISTOPHM.Sample")

   ' call/run the method 'Hello'
   IMA_AutoComp.Hello

   ' change the message
   dim NewMessage
   NewMessage = "Happy New Year !!"
   IMA_AutoComp.ChangeMessage(NewMessage)

   ' call/run the method 'Hello' again
   IMA_AutoComp.Hello

   ' example of the get/set
   dim AnotherVariable

   ' example of the GET function
   msgbox IMA_AutoComp.AThing

   ' example of the SET function
   IMA_AutoComp.AThing = "christophm"

   ' example of the GET function
   msgbox IMA_AutoComp.AThing

   ' cleanup
   set AutoComp = Nothing



And finally, ...

You need to be careful passing those VARIANTs back and forth - this
example program doesn't do any type checking to be sure that the VB client
isn't passing one variable type (maybe a float?) when the component
expects another type (a BSTR).  If you have those kinds of needs or
concerns you need to look up VARIANTs and how to type check them.

There is a (documented) protocol for passing arrays of VARIANTs, I
think a search for "SAFEARRAY" in MSDN will get you on the right track.

Components can be housed in DLLs - I prefer .EXEs because they are
self registering .  I recall that the .DLL will run in the same thread,
the .EXE will run in a separate thread but that is just my offhand
recollection - take it for what it's worth.  

Using automation as a server to VB works really well, if you want a
component to be a server to C++ it is suggested you use a COMponent and
the C-based VTABLE type of interface mechanism rather than the static
'IDispatch' based mechanism of Automation Components.  Automation components
can, however - and are - used from C++ clients.  All the MS-Office suite
products (Word, XL, PowerPoint, MS-Access) expose their methods and
properties through automation.  The 'file scripting object' is an
automation component, . . .

Good Luck with it !! - christophm
0
 
LVL 23

Expert Comment

by:Roshan Davis
ID: 9484699
No comment has been added lately, so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area that this question is:

Answered by : jhance, cvallabhaneni, christophm (points to be split)

Please leave any comments here within the next seven days.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

Roshan Davis
EE Cleanup Volunteer
0
 

Author Comment

by:chasferr
ID: 11977793
Okay, it's about time to close this out.  I appreciate the input however, none worked.  It so Happened that Access 2002 had the function built in.
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Introduction: Finishing the grid – keyboard support for arrow keys to manoeuvre, entering the numbers.  The PreTranslateMessage function is to be used to intercept and respond to keyboard events. Continuing from the fourth article about sudoku. …
Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now