Title:
Author:
Category:
Date:

TWAINSharp: .NET TWAIN Made Easy
Russell Klenk <russ [at] xsdev [dot] net>
C#, TWAIN, Interop
August, 25 2003

Introduction:


Source Code Downloads

TWAINSharp was developed to fulfill the needs of Virtual Charting, software which provides a wireless, and paperless practice management solution for medical offices. The software required a scanning component in order to provide maximum compatibility with the scanning hardware used by medical offices. While a WIA (Windows Image Acquisition) component was originally developed to meet this need, WIA hardware support was found to be seriously lacking. This left a TWAIN implementation as the only other viable solution.

This TWAIN component had to meet a few key requirements:
  •  It needed to be managed code, or at least be able to interact well with managed code.
  •  It needed to support multi-page image transfers.
  •  It needed to be able to make use of the .NET Image system.
  •  It needed to be low-cost.

There are a few .NET TWAIN components out there. First, there is LEAD TWAIN, developed by LEADTOOLS. The .NET components (including a TWAIN component) are included as part of their Raster Imaging Pro toolkit, which sports an impressive list of features. Unfortunately, Virtual Charting did not require the vast majority of the functionality implemented by the LEADTOOLS SDK’s (and many of the other products out there), and thus the price of these packages could not be justified. Virtual Charting decided to develop its own C# component. This article explains how to use TWAINSharp to acquire multiple images from a TWAIN-compliant data source.

Design:

The TWAIN API has its roots back in the days of 16-bit computing. Under Microsoft Windows, TWAIN uses the Windows Message Pump in order to communicate with the application. Because of this, the API is not particularly intuitive to modern-day developers. Searching for TWAIN examples on the Internet yields many results, primarily written in C/C++ or Delphi. TWAINSharp was developed in a style similar to that used in these examples, in order to assist the porting of some example code. At the same time, it presents an easy-to-use interface to the programmer.

TWAIN is a state-based API, and applications communicating with a TWAIN Data Source or the TWAIN Data Source Manager follow a fairly strict order of operations, detailed in Figure 1, below. Please refer to the current TWAIN specification (1.9 at the time of this writing) for more details on how TWAIN works.

In order to illustrate the steps required to initialze TWAIN for use, the sample application uses a system whereby buttons are enabled and disabled according to the current TWAIN state, as defined by the TWAIN State Diagram (see Figure 1, below).



Figure 1


Implementation Details:

First things first -- to add a TWAINSharp TWAINComponent to a form, you can simply drag and drop inside of the Windows Forms Designer from the Toolbox onto a form. There is one more step which needs to be performed before the TWAINComponent can be used inside the form. The TWAIN Data Source Manager uses a window handle to uniquely identify the application, so it is necessary to give the TWAINComponent object the window handle of the main form using the 'Handle' property of the form. The constructor implementation for the main application form is shown below:


    
public MainForm() {
      
// initialize our form components:
      
InitializeComponent();

      
// VERY IMPORTANT!! We MUST set the 'ContainerHandle' property
      // of the TWAIN component, or nothing will work:
      
this.twainComponent.ContainerHandle  = this.Handle;

      
// TODO: Add any other constructor code here.
    
}


Next, the form's message pump must be modified in order to communicate with TWAIN. This is done by implementing the IMessageFilter interface, and adding the IMessageFilter.PreFilterMessage method (illustrated below) to the form. This is also where the application will perform any data acquisition, close out the TWAIN Data Source and uninstall the application message filter. Note that the TWAINComponent object returns images as .NET Bitmap objects.


    
bool IMessageFilter.PreFilterMessage( ref Message msg )
    {
      
// pass this message off to the TWAIN Data Source (or Data Source Manager):
      
TWAIN.MSG  returnCode  = twainComponent.PassMessage( ref msg );

      
// If this is a normal Windows message, we pass it on
      // to the Windows default message handler:
      
if(TWAIN.MSG.YOUPROCESS == returnCode)
        
return false;

      
// This seems to be a message which TWAIN wants our application to process.
      // According to the TWAIN specification, there are only four messages that
      // the Application should care about with regards to Data Source events.
      // Refer to Chapter 3 (pgs. 28 and 29) of the TWAIN 1.9 specification for more details.
      
switch( returnCode ) {
      
case TWAIN.MSG.CLOSEDSREQ:
        {
          
// The user has clicked the "Close" button of the Acquisition
          // user interface, so TWAIN is asking us to close the currently
          // opened Data Source. We also uninstall our message filter here.
          
this.twainComponent.CloseDataSource();
          
// uninstall ourselves as a message filter:
          
this.RemoveMessageFilter();
          
// re-enable the "Close Data Source Manager" button:
          
this.closeDSMButton.Enabled     = true;
          
// disable the "Image Acquisition" button:
          
this.showAcquireButton.Enabled  = false;
          
// re-enable the main form:
          
this.Enabled = true;
          
this.Focus();
        }
        
break;
      
case TWAIN.MSG.CLOSEDSOK:
        {
          
// The user has clicked the "OK" button of the Acquisition user
          // interface, so TWAIN is asking us to close the currently
          // opened Data Source. We also uninstall our message filter here.
          // This is just a special case of TWAIN.MSG.CLOSEDSREQ.
          
this.twainComponent.CloseDataSource();
          
// TODO: Get custom data here.
          // uninstall ourselves as a message filter:
          
this.RemoveMessageFilter();
          
// re-enable the "Close Data Source Manager" button:
          
this.closeDSMButton.Enabled     = true;
          
// disable the "Image Acquisition" button:
          
this.showAcquireButton.Enabled  = false;
          
// re-enable the main form:
          
this.Enabled = true;
          
this.Focus();
        }
        
break;
      
case TWAIN.MSG.DEVICEEVENT:
        {
          
// This notification is sent to the Application by the Data Source
          // when a specific event has occurred, but only if the Application
          // gave the Data Source prior instructions to pass along such events.
          // We don't need to worry about this message right now.
        
}
        
break;
      
case TWAIN.MSG.XFERREADY:
        {
          
// This notification is sent when the Data Source has prepared data
          // it wishes to transfer to the Application. This is where we acquire
          // all of our images (there will be one or more) from the Data Source.
          
if( this.twainComponent != null ) {
            Bitmap  newImage    = 
null;
            
int     numPending  = 0;
            
int     imageIndex  = 1;

            
do {
              
// acquire a single image:
              
newImage = this.twainComponent.AcquireSingleImage( ref numPending, false );
              
// display it if acquisition was successful:
              
if( newImage != null ) {
                
// display it in a new PictureForm:
                
PictureForm picForm = new PictureForm();

                
if( picForm != null ) {
                  
// set the form text:
                  
picForm.Text     = "TWAINSharp Image " + imageIndex.ToString();
                  
// set the form image:
                  
picForm.Picture  = newImage;
                  picForm.Visible  = 
true;
                }

                
// increment the image index:
                
++imageIndex;
              }
            } 
while( numPending != );
          }
        }
        
break;
      }

      
return true;
    }



It is now time to start transitioning up in TWAIN state. Because TWAINSharp takes care of the transition through TWAIN states 1 and 2 automatically, the first step is to open the TWAIN Data Source Manager. This process is illustrated in the event handler 'openDSMButton_Click', below. When this event handler returns, the application is ready to execute the transition into TWAIN state 4, and the "Select Data Source" and "Open Data Source" buttons are enabled. Please note that selecting a data source is an optional step, because a TWAIN always maintains a "default" data source.


    private void openDSMButton_Click( object sender, System.EventArgs e ) {
      
// open the Data Source Manager, if possible:
      
if ( this.twainComponent != null && this.openDSMButton.Enabled ) {
        
// attempt to open the TWAIN Data Source Manager:
        
if ( this.twainComponent.OpenDataSourceManager() ) {
          
// disable the "Open Data Source Manager" button:
          
this.openDSMButton.Enabled     = false;
          
// enable the "Select Data Source" and "Open Data Source" buttons:
          
this.selectDSButton.Enabled    = true;
          
this.openDSButton.Enabled      = true;
          
// disable the "Show Image Acquisition Interface" button:
          
this.showAcquireButton.Enabled = false;
          
// enable the "Close Data Source Manager" button:
          
this.closeDSMButton.Enabled    = true;
        }
      }
    }



If there is more than one TWAIN Data Source installed on the system, the TWAIN Data Source Manager implements code which presents a dialog box to the end user that allows them to select the Data Source they wish to use. If the user selects a data source, it becomes the new "default" TWAIN data source. This step is optional, and does not cause a change in TWAIN state. In the sample application, this dialog is displayed if the user clicks the "Select Data Source" button, the event handler for which is shown below.


    private void selectDSButton_Click( object sender, System.EventArgs e ) {
      
// present the standard Data Source selection dialog box, if possible:
      
if(twainComponent != null && this.selectDSButton.Enabled) {
        
// display the "Select TWAIN Data Source" dialog box.
        // if the function returns 'true', the user selected a Data Source,
        // which becomes the new "default" Data Source. Otherwise, the
        // default Data Source is determined by the Data Source Manager.
        
this.twainComponent.SelectDataSource();
        
// because this doesn't cause the TWAIN Application state to
        // change, we don't enable/disable any of our buttons here.
      
}
    }



It is now time to open a connection to the current data source. Before we do this, however, we can optionally perform capability negotiation with the Data Source. In the sample application, capability negotiation is used to enable the document feeder (if one is present) and enable the Data Source to automatically feed documents. As a final step before opening the device, the form must install its message filter. Once these steps have been performed, the application may open the TWAIN Data Source for image acquisition. In the sample application, all of these steps are performed in the event handler for the "Open Data Source" button, which is shown below.


    private void openDSButton_Click( object sender, System.EventArgs e ) {
      
// attempt to open the current "default" Data Source. there will
      // ALWAYS be a default Data Source, as long as the DSM was opened.
      
if( this.twainComponent != null && this.openDSButton.Enabled ) {
        
// attempt to open the Data Source:
        
if( this.twainComponent.OpenDataSource() ) {
          TWAIN.CAPABILITY capXFerCount   = 
null;
          TWAIN.CAPABILITY capFeederEnable= 
null;
          TWAIN.CAPABILITY capAutoFeed    = 
null;
          
ushort       xferCount      = ((ushort)TWAIN.CAP.XFERCOUNT);
          
ushort       feederEnable   = ((ushort)TWAIN.CAP.FEEDERENABLED);
          
ushort       autoFeedEnable = ((ushort)TWAIN.CAP.AUTOFEED);

          
// Install our message filter, in order to communicate with the Data Source:
          
this.InstallMessageFilter();

          
// Perform capability negotiation to enable the document feeder
          // and other such things necessary for multi-image acquisition:
          
capXFerCount    = new TWAIN.CAPABILITY();
          capFeederEnable = 
new TWAIN.CAPABILITY();
          capAutoFeed     = 
new TWAIN.CAPABILITY();

          
// we need to let the device know we can accept multiple images:
          
capXFerCount.FromONEVALUE( xferCount, TWAIN.TWTY.INT32, -);
          
// we need to enable the document feeder, if one is present:
          
capFeederEnable.FromONEVALUE( feederEnable, TWAIN.TWTY.BOOL, true );
          
// we need to enable the automatic feeding capability as well:
          
capAutoFeed.FromONEVALUE( autoFeedEnable, TWAIN.TWTY.BOOL, true );

          
// set the capabilities to the device. normally, you'd check for
          // success (as defined according to the TWAIN specification). we
          // won't do that here.
          
this.twainComponent.SetCapability( ref capXFerCount );
          
this.twainComponent.SetCapability( ref capFeederEnable );
          
this.twainComponent.SetCapability( ref capAutoFeed );

          
// TODO: Perform other capability negotiation here.

          // disable the "Select Data Source" and "Open Data Source" buttons:
          
this.openDSButton.Enabled      = false;
          
this.selectDSButton.Enabled    = false;
          
// enable the "Show Image Acquisition Interface" button:
          
this.showAcquireButton.Enabled = true;
          
// disable the "Close Data Source Manager" button:
          
this.closeDSMButton.Enabled    = false;
        }
      }



Now that the application has successfully opened the TWAIN Data Source, it is ready to acquire data. The device's TWAIN driver implements a dialog box which usually allows the user to specify things like acquisition rectangles, and image color depth. While this dialog is displayed, it is important to disable access to the parent form to prevent the user from prematurely closing the Data Source. When data is ready to be acquired from the Source, the application will receive a TWAIN.MSG.XFERREADY message. If the user chooses to cancel data acquisition, the application will receive a TWAIN.MSG.CLOSEDSREQ message. These messages are processed in the application's message filter. To display the data acquisition dialog box, call TWAINComponent.ShowAcquireUI(), as illustrated below in the following event handler:


    private void showAcquireButton_Click( object sender, System.EventArgs e ) {
      
// attempt to display the image acquisition interface:
      
if( this.twainComponent.ShowAcquireUI() ) {
        
// we need to disable access to the main form until
        // the image acquisition dialog box is dismissed:
        
this.Enabled = false;
      }
    }



Finally, it is time to close the Data Source Manager (the Data Source should have already been closed in the message filter). Because it is possible (through capability negotiation) to bypass the display of the data acquisition interface, the Data Source is closed and message filter uninstalled during this phase as well. Once the Data Source Manager has been closed, the process is ready to begin again.


    private void closeDSMButton_Click( object sender, System.EventArgs e ) {
      
// close the Data Source Manager, if possible:
      
if( this.twainComponent != null && this.closeDSMButton.Enabled ) {
        
// attempt to close the TWAIN Data Source:
        
if( this.twainComponent.CloseDataSource() ) {
          
// uninstall our message filter:
          
this.RemoveMessageFilter();
        }

        
// attempt to close the TWAIN Data Source Manager.
        
if( this.twainComponent.CloseDataSourceManager() ) {
          
// enable the "Open Data Source Manager" button:
          
this.openDSMButton.Enabled     = true;
          
// disable the rest of the buttons:
          
this.selectDSButton.Enabled    = false;
          
this.openDSButton.Enabled      = false;
          
this.showAcquireButton.Enabled = false;
          
this.closeDSMButton.Enabled    = false;
        }
      }
    }



Conclusion:

By now you should have a general idea of how TWAIN works, as well as how TWAINSharp can help you add TWAIN functionality to your applications quickly and easily. The example code for this article is available on the TWAINSharp website, in the "Download TWAINSharp" section. Buildable example code is included; however, it will not run due to licensing restrictions. If you would like to run the example application without a TWAIN capture source, the TWAIN Tookit is available from the main TWAIN web site. This includes a sample TWAIN Data Source driver. Some of the documentation was removed from the code samples posted with this article to shorten the length. The full documentation is available with the sample download.

Acknowledgements and References:
TWAIN
CSharpToHTML
TWAIN.net CodeProject Article
Virtual Charting
© 2002 - 2003 XS-Technology, LLC :: All rights reserved