1
0
mirror of https://github.com/soukoku/ntwain.git synced 2025-04-05 20:59:23 +08:00

Expanded ITwainSession interface for more useful stuff.

This commit is contained in:
soukoku 2014-09-11 22:24:27 -04:00
parent 9415d5d43d
commit 81710b7e99
7 changed files with 363 additions and 249 deletions

View File

@ -279,6 +279,9 @@
<Compile Include="..\NTwain\TwainSession.cs">
<Link>TwainSession.cs</Link>
</Compile>
<Compile Include="..\NTwain\TwainSessionInternal.cs">
<Link>TwainSessionInternal.cs</Link>
</Compile>
<Compile Include="..\NTwain\TwainSource.Caps.cs">
<Link>TwainSource.Caps.cs</Link>
</Compile>

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
namespace NTwain
{
@ -13,6 +14,25 @@ namespace NTwain
/// </summary>
public interface ITwainSession : INotifyPropertyChanged
{
/// <summary>
/// [Experimental] Gets or sets the optional synchronization context when not specifying a <see cref="MessageLoopHook"/> on <see cref="Open"/>.
/// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use.
/// </summary>
/// <value>
/// The synchronization context.
/// </value>
SynchronizationContext SynchronizationContext { get; set; }
/// <summary>
/// Gets or sets a value indicating whether calls to triplets will verify the current twain session state.
/// </summary>
/// <value>
/// <c>true</c> if state value is enforced; otherwise, <c>false</c>.
/// </value>
bool EnforceState { get; set; }
/// <summary>
/// Gets the currently open source.
/// </summary>
@ -37,6 +57,27 @@ namespace NTwain
/// <value>The state.</value>
int State { get; }
/// <summary>
/// Quick flag to check if the DSM has been opened.
/// </summary>
bool IsDsmOpen { get; }
/// <summary>
/// Quick flag to check if a source has been opened.
/// </summary>
bool IsSourceOpen { get; }
/// <summary>
/// Quick flag to check if a source has been enabled.
/// </summary>
bool IsSourceEnabled { get; }
/// <summary>
/// Quick flag to check if a source is in the transferring state.
/// </summary>
bool IsTransferring { get; }
/// <summary>
/// Try to show the built-in source selector dialog and return the selected source.
/// This is not recommended and is only included for completeness.
@ -99,5 +140,36 @@ namespace NTwain
/// </summary>
/// <returns></returns>
TWStatusUtf8 GetStatusUtf8();
/// <summary>
/// Occurs when <see cref="State"/> has changed.
/// </summary>
event EventHandler StateChanged;
/// <summary>
/// Occurs when <see cref="CurrentSource"/> has changed.
/// </summary>
event EventHandler SourceChanged;
/// <summary>
/// Occurs when source has been disabled (back to state 4).
/// </summary>
event EventHandler SourceDisabled;
/// <summary>
/// Occurs when the source has generated an event.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceEvent;
/// <summary>
/// Occurs when a data transfer is ready.
/// </summary>
event EventHandler<TransferReadyEventArgs> TransferReady;
/// <summary>
/// Occurs when data has been transferred.
/// </summary>
event EventHandler<DataTransferredEventArgs> DataTransferred;
/// <summary>
/// Occurs when an error has been encountered during transfer.
/// </summary>
event EventHandler<TransferErrorEventArgs> TransferError;
}
}

View File

@ -16,14 +16,6 @@ namespace NTwain.Internals
MessageLoopHook MessageLoopHook { get; }
/// <summary>
/// Gets or sets a value indicating whether calls to triplets will verify the current twain session state.
/// </summary>
/// <value>
/// <c>true</c> if state value is enforced; otherwise, <c>false</c>.
/// </value>
bool EnforceState { get; set; }
/// <summary>
/// Changes the state right away.
/// </summary>

View File

@ -92,6 +92,7 @@
<Compile Include="Triplets\Dsm.Linux.cs" />
<Compile Include="Triplets\Dsm.WinOld.cs" />
<Compile Include="Triplets\Dsm.WinNew.cs" />
<Compile Include="TwainSessionInternal.cs" />
<Compile Include="TwainSource.Caps.cs" />
<Compile Include="TwainSession.cs" />
<Compile Include="TwainSource.cs" />

View File

@ -18,7 +18,7 @@ namespace NTwain
/// <summary>
/// Basic class for interfacing with TWAIN. You should only have one of this per application process.
/// </summary>
public class TwainSession : ITwainSessionInternal, IWinMessageFilter
public partial class TwainSession : ITwainSessionInternal, IWinMessageFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="TwainSession"/> class.
@ -40,6 +40,7 @@ namespace NTwain
if (appId == null) { throw new ArgumentNullException("appId"); }
_appId = appId;
_ownedSources = new Dictionary<string, TwainSource>();
((ITwainSessionInternal)this).ChangeState(1, false);
#if DEBUG
// defaults to false on release since it's only useful during dev
@ -52,13 +53,14 @@ namespace NTwain
TWIdentity _appId;
TWUserInterface _twui;
readonly Dictionary<string, TwainSource> _ownedSources = new Dictionary<string, TwainSource>();
// cache generated twain sources so if you get same source from one session it'll return the same object
readonly Dictionary<string, TwainSource> _ownedSources;
TwainSource GetSourceInstance(ITwainSessionInternal session, TWIdentity sourceId)
{
TwainSource source = null;
var key = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2}", sourceId.Manufacturer, sourceId.ProductFamily, sourceId.ProductName);
Debug.WriteLine("Source id = " + sourceId.Id);
var key = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2}|{3}", sourceId.Id, sourceId.Manufacturer, sourceId.ProductFamily, sourceId.ProductName);
if (_ownedSources.ContainsKey(key))
{
source = _ownedSources[key];
@ -69,27 +71,9 @@ namespace NTwain
}
return source;
}
#region ITwainSession Members
/// <summary>
/// Gets or sets the optional synchronization context when not specifying a <see cref="MessageLoopHook"/> on <see cref="Open"/>.
/// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use.
/// </summary>
/// <value>
/// The synchronization context.
/// </value>
public SynchronizationContext SynchronizationContext { get; set; }
#region ITwainSessionInternal Members
MessageLoopHook _msgLoopHook;
MessageLoopHook ITwainSessionInternal.MessageLoopHook { get { return _msgLoopHook; } }
/// <summary>
/// Gets the app id used for the session.
/// </summary>
/// <value>The app id.</value>
TWIdentity ITwainSessionInternal.AppId { get { return _appId; } }
/// <summary>
/// Gets or sets a value indicating whether calls to triplets will verify the current twain session state.
@ -99,84 +83,15 @@ namespace NTwain
/// </value>
public bool EnforceState { get; set; }
void ITwainSessionInternal.ChangeState(int newState, bool notifyChange)
{
_state = newState;
if (notifyChange)
{
OnPropertyChanged("State");
SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
}
}
/// <summary>
/// [Experimental] Gets or sets the optional synchronization context when not specifying a <see cref="MessageLoopHook"/> on <see cref="Open"/>.
/// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use.
/// </summary>
/// <value>
/// The synchronization context.
/// </value>
public SynchronizationContext SynchronizationContext { get; set; }
ICommittable ITwainSessionInternal.GetPendingStateChanger(int newState)
{
return new TentativeStateCommitable(this, newState);
}
void ITwainSessionInternal.ChangeCurrentSource(TwainSource source)
{
CurrentSource = source;
OnPropertyChanged("CurrentSource");
SafeAsyncSyncableRaiseOnEvent(OnSourceChanged, SourceChanged);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(DataTransferredEventArgs e)
{
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, e);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(TransferErrorEventArgs e)
{
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, e);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(TransferReadyEventArgs e)
{
SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, e);
}
DGAudio _dgAudio;
DGAudio ITwainSessionInternal.DGAudio
{
get
{
if (_dgAudio == null) { _dgAudio = new DGAudio(this); }
return _dgAudio;
}
}
DGControl _dgControl;
DGControl ITwainSessionInternal.DGControl
{
get
{
if (_dgControl == null) { _dgControl = new DGControl(this); }
return _dgControl;
}
}
DGImage _dgImage;
DGImage ITwainSessionInternal.DGImage
{
get
{
if (_dgImage == null) { _dgImage = new DGImage(this); }
return _dgImage;
}
}
DGCustom _dgCustom;
DGCustom ITwainSessionInternal.DGCustom
{
get
{
if (_dgCustom == null) { _dgCustom = new DGCustom(this); }
return _dgCustom;
}
}
#endregion
#region ITwainSession Members
/// <summary>
/// Gets the currently open source.
@ -250,6 +165,26 @@ namespace NTwain
}
}
/// <summary>
/// Quick flag to check if the DSM has been opened.
/// </summary>
public bool IsDsmOpen { get { return State > 2; } }
/// <summary>
/// Quick flag to check if a source has been opened.
/// </summary>
public bool IsSourceOpen { get { return State > 3; } }
/// <summary>
/// Quick flag to check if a source has been enabled.
/// </summary>
public bool IsSourceEnabled { get { return State > 4; } }
/// <summary>
/// Quick flag to check if a source is in the transferring state.
/// </summary>
public bool IsTransferring { get { return State > 5; } }
/// <summary>
/// Opens the data source manager. This must be the first method used
/// before using other TWAIN functions. Calls to this must be followed by
@ -348,6 +283,13 @@ namespace NTwain
/// <returns></returns>
public ReturnCode OpenSource(string sourceName)
{
var curSrc = CurrentSource;
if (curSrc != null)
{
// TODO: close any open sources first
}
var hit = GetSources().Where(s => string.Equals(s.Name, sourceName)).FirstOrDefault();
if (hit != null)
{
@ -378,142 +320,6 @@ namespace NTwain
return stat;
}
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void OnPropertyChanged(string propertyName)
{
var syncer = SynchronizationContext;
if (syncer == null)
{
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}
else
{
syncer.Post(o =>
{
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}, null);
}
}
#endregion
#region privileged calls that causes state change in TWAIN
/// <summary>
/// Enables the source to start transferring.
/// </summary>
/// <param name="mode">The mode.</param>
/// <param name="modal">if set to <c>true</c> any driver UI will display as modal.</param>
/// <param name="windowHandle">The window handle if modal.</param>
/// <returns></returns>
ReturnCode ITwainSessionInternal.EnableSource(SourceEnableMode mode, bool modal, IntPtr windowHandle)
{
var rc = ReturnCode.Failure;
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource with {1}.", Thread.CurrentThread.ManagedThreadId, mode));
// app v2.2 or higher uses callback2
if (_appId.ProtocolMajor >= 2 && _appId.ProtocolMinor >= 2)
{
var cb = new TWCallback2(HandleCallback);
var rc2 = ((ITwainSessionInternal)this).DGControl.Callback2.RegisterCallback(cb);
if (rc2 == ReturnCode.Success)
{
Debug.WriteLine("Registered callback2 OK.");
_callbackObj = cb;
}
}
else
{
var cb = new TWCallback(HandleCallback);
var rc2 = ((ITwainSessionInternal)this).DGControl.Callback.RegisterCallback(cb);
if (rc2 == ReturnCode.Success)
{
Debug.WriteLine("Registered callback OK.");
_callbackObj = cb;
}
}
_twui = new TWUserInterface();
_twui.ShowUI = mode == SourceEnableMode.ShowUI;
_twui.ModalUI = modal;
_twui.hParent = windowHandle;
if (mode == SourceEnableMode.ShowUIOnly)
{
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.EnableDSUIOnly(_twui);
}
else
{
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.EnableDS(_twui);
}
if (rc != ReturnCode.Success)
{
_callbackObj = null;
}
});
return rc;
}
bool _disabling;
ReturnCode ITwainSessionInternal.DisableSource()
{
var rc = ReturnCode.Failure;
if (!_disabling) // temp hack as a workaround to this being called from multiple threads (xfer logic & closedsreq msg)
{
_disabling = true;
try
{
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId));
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.DisableDS(_twui);
if (rc == ReturnCode.Success)
{
_callbackObj = null;
SafeAsyncSyncableRaiseOnEvent(OnSourceDisabled, SourceDisabled);
}
});
}
finally
{
_disabling = false;
}
}
return rc;
}
/// <summary>
/// Forces the stepping down of an opened source when things gets out of control.
/// Used when session state and source state become out of sync.
@ -567,9 +373,6 @@ namespace NTwain
EnforceState = origFlag;
}
#endregion
#region custom events and overridables
/// <summary>
/// Occurs when <see cref="State"/> has changed.
@ -601,6 +404,49 @@ namespace NTwain
public event EventHandler<TransferErrorEventArgs> TransferError;
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void OnPropertyChanged(string propertyName)
{
var syncer = SynchronizationContext;
if (syncer == null)
{
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}
else
{
syncer.Post(o =>
{
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}, null);
}
}
#endregion
#region events overridables
/// <summary>
/// Raises event and if applicable marshal it asynchronously to the <see cref="SynchronizationContext"/> thread
/// without exceptions.

View File

@ -0,0 +1,200 @@
using NTwain.Data;
using NTwain.Internals;
using NTwain.Triplets;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
namespace NTwain
{
// for internal pieces since the main twain session file is getting too long
partial class TwainSession
{
#region ITwainSessionInternal Members
MessageLoopHook _msgLoopHook;
MessageLoopHook ITwainSessionInternal.MessageLoopHook { get { return _msgLoopHook; } }
/// <summary>
/// Gets the app id used for the session.
/// </summary>
/// <value>The app id.</value>
TWIdentity ITwainSessionInternal.AppId { get { return _appId; } }
void ITwainSessionInternal.ChangeState(int newState, bool notifyChange)
{
_state = newState;
if (notifyChange)
{
OnPropertyChanged("State");
SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
}
}
ICommittable ITwainSessionInternal.GetPendingStateChanger(int newState)
{
return new TentativeStateCommitable(this, newState);
}
void ITwainSessionInternal.ChangeCurrentSource(TwainSource source)
{
CurrentSource = source;
OnPropertyChanged("CurrentSource");
SafeAsyncSyncableRaiseOnEvent(OnSourceChanged, SourceChanged);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(DataTransferredEventArgs e)
{
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, e);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(TransferErrorEventArgs e)
{
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, e);
}
void ITwainSessionInternal.SafeSyncableRaiseEvent(TransferReadyEventArgs e)
{
SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, e);
}
DGAudio _dgAudio;
DGAudio ITwainSessionInternal.DGAudio
{
get
{
if (_dgAudio == null) { _dgAudio = new DGAudio(this); }
return _dgAudio;
}
}
DGControl _dgControl;
DGControl ITwainSessionInternal.DGControl
{
get
{
if (_dgControl == null) { _dgControl = new DGControl(this); }
return _dgControl;
}
}
DGImage _dgImage;
DGImage ITwainSessionInternal.DGImage
{
get
{
if (_dgImage == null) { _dgImage = new DGImage(this); }
return _dgImage;
}
}
DGCustom _dgCustom;
DGCustom ITwainSessionInternal.DGCustom
{
get
{
if (_dgCustom == null) { _dgCustom = new DGCustom(this); }
return _dgCustom;
}
}
/// <summary>
/// Enables the source to start transferring.
/// </summary>
/// <param name="mode">The mode.</param>
/// <param name="modal">if set to <c>true</c> any driver UI will display as modal.</param>
/// <param name="windowHandle">The window handle if modal.</param>
/// <returns></returns>
ReturnCode ITwainSessionInternal.EnableSource(SourceEnableMode mode, bool modal, IntPtr windowHandle)
{
var rc = ReturnCode.Failure;
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource with {1}.", Thread.CurrentThread.ManagedThreadId, mode));
// app v2.2 or higher uses callback2
if (_appId.ProtocolMajor >= 2 && _appId.ProtocolMinor >= 2)
{
var cb = new TWCallback2(HandleCallback);
var rc2 = ((ITwainSessionInternal)this).DGControl.Callback2.RegisterCallback(cb);
if (rc2 == ReturnCode.Success)
{
Debug.WriteLine("Registered callback2 OK.");
_callbackObj = cb;
}
}
else
{
var cb = new TWCallback(HandleCallback);
var rc2 = ((ITwainSessionInternal)this).DGControl.Callback.RegisterCallback(cb);
if (rc2 == ReturnCode.Success)
{
Debug.WriteLine("Registered callback OK.");
_callbackObj = cb;
}
}
_twui = new TWUserInterface();
_twui.ShowUI = mode == SourceEnableMode.ShowUI;
_twui.ModalUI = modal;
_twui.hParent = windowHandle;
if (mode == SourceEnableMode.ShowUIOnly)
{
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.EnableDSUIOnly(_twui);
}
else
{
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.EnableDS(_twui);
}
if (rc != ReturnCode.Success)
{
_callbackObj = null;
}
});
return rc;
}
bool _disabling;
ReturnCode ITwainSessionInternal.DisableSource()
{
var rc = ReturnCode.Failure;
if (!_disabling) // temp hack as a workaround to this being called from multiple threads (xfer logic & closedsreq msg)
{
_disabling = true;
try
{
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId));
rc = ((ITwainSessionInternal)this).DGControl.UserInterface.DisableDS(_twui);
if (rc == ReturnCode.Success)
{
_callbackObj = null;
SafeAsyncSyncableRaiseOnEvent(OnSourceDisabled, SourceDisabled);
}
});
}
finally
{
_disabling = false;
}
}
return rc;
}
#endregion
}
}

View File

@ -75,7 +75,7 @@ When you're ready to get into transfer mode, just call Enable() on the source ob
```
#!c#
sources.Enable(...);
myDS.Enable(...);
```