Added experimental option to sync events to UI thread.

This commit is contained in:
soukoku
2014-04-16 20:39:30 -04:00
parent b7993de123
commit a91d5aa4f2
6 changed files with 162 additions and 132 deletions

View File

@@ -15,7 +15,6 @@ namespace NTwain
/// </summary> /// </summary>
class MessageLoop class MessageLoop
{ {
// mostly wraps around a dispatcher?
static MessageLoop _instance = new MessageLoop(); static MessageLoop _instance = new MessageLoop();
public static MessageLoop Instance { get { return _instance; } } public static MessageLoop Instance { get { return _instance; } }
@@ -24,6 +23,7 @@ namespace NTwain
HwndSource _dummyWindow; HwndSource _dummyWindow;
private MessageLoop() { } private MessageLoop() { }
public void EnsureStarted() public void EnsureStarted()
{ {
if (!_started) if (!_started)
@@ -42,8 +42,9 @@ namespace NTwain
// CS_NOCLOSE, WS_DISABLED, and WS_EX_NOACTIVATE // CS_NOCLOSE, WS_DISABLED, and WS_EX_NOACTIVATE
_dummyWindow = new HwndSource(0x0200, 0x8000000, 0x8000000, 0, 0, "NTWAIN_LOOPER", IntPtr.Zero); _dummyWindow = new HwndSource(0x0200, 0x8000000, 0x8000000, 0, 0, "NTWAIN_LOOPER", IntPtr.Zero);
} }
hack.Set(); hack.Set();
Dispatcher.Run(); Dispatcher.Run();
_started = false;
})); }));
loopThread.IsBackground = true; loopThread.IsBackground = true;
loopThread.SetApartmentState(ApartmentState.STA); loopThread.SetApartmentState(ApartmentState.STA);

View File

@@ -14,6 +14,6 @@ namespace NTwain
// keep this same in majors releases // keep this same in majors releases
public const string Release = "0.11.0.0"; public const string Release = "0.11.0.0";
// change this for each nuget release // change this for each nuget release
public const string Build = "0.11.1"; public const string Build = "0.11.2";
} }
} }

View File

@@ -65,6 +65,16 @@ namespace NTwain
} }
} }
/// <summary>
/// EXPERIMENTAL. Gets or sets the optional synchronization context.
/// This allows events to be raised on the thread
/// associated with the context.
/// </summary>
/// <value>
/// The synchronization context.
/// </value>
public SynchronizationContext SynchronizationContext { get; set; }
#region ITwainStateInternal Members #region ITwainStateInternal Members
@@ -88,7 +98,7 @@ namespace NTwain
if (notifyChange) if (notifyChange)
{ {
RaisePropertyChanged("State"); RaisePropertyChanged("State");
OnStateChanged(); SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
} }
} }
@@ -101,7 +111,7 @@ namespace NTwain
{ {
SourceId = sourceId; SourceId = sourceId;
RaisePropertyChanged("SourceId"); RaisePropertyChanged("SourceId");
OnSourceChanged(); SafeAsyncSyncableRaiseOnEvent(OnSourceChanged, SourceChanged);
} }
#endregion #endregion
@@ -132,7 +142,7 @@ namespace NTwain
{ {
_state = value; _state = value;
RaisePropertyChanged("State"); RaisePropertyChanged("State");
OnStateChanged(); SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
} }
} }
} }
@@ -195,8 +205,27 @@ namespace NTwain
/// <param name="propertyName">Name of the property.</param> /// <param name="propertyName">Name of the property.</param>
protected void RaisePropertyChanged(string propertyName) protected void RaisePropertyChanged(string propertyName)
{ {
var hand = PropertyChanged; if (SynchronizationContext == null)
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); } {
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}
else
{
SynchronizationContext.Post(o =>
{
try
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
}
catch { }
}, null);
}
} }
#endregion #endregion
@@ -389,7 +418,7 @@ namespace NTwain
{ {
_callbackObj = null; _callbackObj = null;
} }
OnSourceDisabled(); SafeAsyncSyncableRaiseOnEvent(OnSourceDisabled, SourceDisabled);
} }
}); });
return rc; return rc;
@@ -483,123 +512,108 @@ namespace NTwain
public event EventHandler<TransferErrorEventArgs> TransferError; public event EventHandler<TransferErrorEventArgs> TransferError;
/// <summary> /// <summary>
/// Called when <see cref="State"/> changed /// Raises event and if applicable marshal it synchronously to the <see cref="SynchronizationContext" /> thread.
/// and raises the <see cref="StateChanged" /> event.
/// </summary> /// </summary>
protected virtual void OnStateChanged() /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <param name="onEventFunc">The on event function.</param>
/// <param name="handler">The handler.</param>
/// <param name="e">The <see cref="TEventArgs"/> instance containing the event data.</param>
void SafeSyncableRaiseOnEvent<TEventArgs>(Action<TEventArgs> onEventFunc, EventHandler<TEventArgs> handler, TEventArgs e) where TEventArgs : EventArgs
{ {
var hand = StateChanged; var syncer = SynchronizationContext;
if (hand != null) if (syncer == null)
{ {
try try
{ {
hand(this, EventArgs.Empty); onEventFunc(e);
if (handler != null) { handler(this, e); }
} }
catch { } catch { }
} }
else
{
syncer.Send(o =>
{
try
{
onEventFunc(e);
if (handler != null) { handler(this, e); }
}
catch { }
}, null);
}
} }
/// <summary> /// <summary>
/// Called when <see cref="SourceId"/> changed /// Raises event and if applicable marshal it asynchronously to the <see cref="SynchronizationContext"/> thread.
/// and raises the <see cref="SourceChanged" /> event.
/// </summary> /// </summary>
protected virtual void OnSourceChanged() /// <param name="onEventFunc">The on event function.</param>
/// <param name="handler">The handler.</param>
void SafeAsyncSyncableRaiseOnEvent(Action onEventFunc, EventHandler handler)
{ {
var hand = SourceChanged; var syncer = SynchronizationContext;
if (hand != null) if (syncer == null)
{ {
try try
{ {
hand(this, EventArgs.Empty); onEventFunc();
if (handler != null) { handler(this, EventArgs.Empty); }
} }
catch { } catch { }
} }
else
{
syncer.Post(o =>
{
try
{
onEventFunc();
if (handler != null) { handler(this, EventArgs.Empty); }
}
catch { }
}, null);
}
} }
/// <summary> /// <summary>
/// Called when source has been disabled (back to state 4) /// Called when <see cref="State"/> changed.
/// and raises the <see cref="SourceDisabled" /> event.
/// </summary> /// </summary>
protected virtual void OnSourceDisabled() protected virtual void OnStateChanged() { }
{
var hand = SourceDisabled;
if (hand != null)
{
try
{
hand(this, EventArgs.Empty);
}
catch { }
}
}
/// <summary> /// <summary>
/// Raises the <see cref="E:DeviceEvent" /> event. /// Called when <see cref="SourceId"/> changed.
/// </summary>
protected virtual void OnSourceChanged() { }
/// <summary>
/// Called when source has been disabled (back to state 4).
/// </summary>
protected virtual void OnSourceDisabled() { }
/// <summary>
/// Called when the source has generated an event.
/// </summary> /// </summary>
/// <param name="e">The <see cref="DeviceEventArgs"/> instance containing the event data.</param> /// <param name="e">The <see cref="DeviceEventArgs"/> instance containing the event data.</param>
protected virtual void OnDeviceEvent(DeviceEventArgs e) protected virtual void OnDeviceEvent(DeviceEventArgs e) { }
{
var hand = DeviceEvent;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
/// <summary> /// <summary>
/// Raises the <see cref="E:TransferReady" /> event. /// Called when a data transfer is ready.
/// </summary> /// </summary>
/// <param name="e">The <see cref="TransferReadyEventArgs"/> instance containing the event data.</param> /// <param name="e">The <see cref="TransferReadyEventArgs"/> instance containing the event data.</param>
protected virtual void OnTransferReady(TransferReadyEventArgs e) protected virtual void OnTransferReady(TransferReadyEventArgs e) { }
{
var hand = TransferReady;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
/// <summary> /// <summary>
/// Raises the <see cref="E:DataTransferred" /> event. /// Called when data has been transferred.
/// </summary> /// </summary>
/// <param name="e">The <see cref="DataTransferredEventArgs"/> instance containing the event data.</param> /// <param name="e">The <see cref="DataTransferredEventArgs"/> instance containing the event data.</param>
protected virtual void OnDataTransferred(DataTransferredEventArgs e) protected virtual void OnDataTransferred(DataTransferredEventArgs e) { }
{
var hand = DataTransferred;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
/// <summary> /// <summary>
/// Raises the <see cref="E:TransferError" /> event. /// Called when an error has been encountered during transfer.
/// </summary> /// </summary>
/// <param name="e">The <see cref="TransferErrorEventArgs"/> instance containing the event data.</param> /// <param name="e">The <see cref="TransferErrorEventArgs"/> instance containing the event data.</param>
protected virtual void OnTransferError(TransferErrorEventArgs e) protected virtual void OnTransferError(TransferErrorEventArgs e) { }
{
var hand = TransferError;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
#endregion #endregion
#region TWAIN logic during xfer work #region TWAIN logic during xfer work
@@ -671,7 +685,7 @@ namespace NTwain
var rc = DGControl.DeviceEvent.Get(out de); var rc = DGControl.DeviceEvent.Get(out de);
if (rc == ReturnCode.Success) if (rc == ReturnCode.Success)
{ {
OnDeviceEvent(new DeviceEventArgs(de)); SafeSyncableRaiseOnEvent(OnDeviceEvent, DeviceEvent, new DeviceEventArgs(de));
} }
break; break;
case Message.CloseDSReq: case Message.CloseDSReq:
@@ -726,7 +740,7 @@ namespace NTwain
EndOfJob = pending.EndOfJob == 0 EndOfJob = pending.EndOfJob == 0
}; };
OnTransferReady(preXferArgs); SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, preXferArgs);
#endregion #endregion
@@ -805,16 +819,17 @@ namespace NTwain
{ {
lockedPtr = MemoryManager.Instance.Lock(dataPtr); lockedPtr = MemoryManager.Instance.Lock(dataPtr);
} }
OnDataTransferred(new DataTransferredEventArgs { NativeData = lockedPtr });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { NativeData = lockedPtr });
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
OnTransferError(new TransferErrorEventArgs { Exception = ex }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
} }
finally finally
{ {
@@ -845,11 +860,11 @@ namespace NTwain
var xrc = DGAudio.AudioFileXfer.Get(); var xrc = DGAudio.AudioFileXfer.Get();
if (xrc == ReturnCode.XferDone) if (xrc == ReturnCode.XferDone)
{ {
OnDataTransferred(new DataTransferredEventArgs { FileDataPath = filePath }); SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { FileDataPath = filePath });
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
@@ -876,16 +891,16 @@ namespace NTwain
{ {
lockedPtr = MemoryManager.Instance.Lock(dataPtr); lockedPtr = MemoryManager.Instance.Lock(dataPtr);
} }
OnDataTransferred(new DataTransferredEventArgs { NativeData = lockedPtr, ImageInfo = imgInfo }); SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { NativeData = lockedPtr, ImageInfo = imgInfo });
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
OnTransferError(new TransferErrorEventArgs { Exception = ex }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
} }
finally finally
{ {
@@ -921,11 +936,11 @@ namespace NTwain
{ {
imgInfo = null; imgInfo = null;
} }
OnDataTransferred(new DataTransferredEventArgs { FileDataPath = filePath, ImageInfo = imgInfo }); SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { FileDataPath = filePath, ImageInfo = imgInfo });
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
@@ -995,7 +1010,7 @@ namespace NTwain
//} //}
if (DGImage.ImageInfo.Get(out imgInfo) == ReturnCode.Success) if (DGImage.ImageInfo.Get(out imgInfo) == ReturnCode.Success)
{ {
OnDataTransferred(new DataTransferredEventArgs { MemData = xferredData.ToArray(), ImageInfo = imgInfo }); SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { MemData = xferredData.ToArray(), ImageInfo = imgInfo });
} }
else else
{ {
@@ -1004,13 +1019,13 @@ namespace NTwain
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
OnTransferError(new TransferErrorEventArgs { Exception = ex }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
} }
finally finally
{ {
@@ -1130,12 +1145,12 @@ namespace NTwain
} }
else else
{ {
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
OnTransferError(new TransferErrorEventArgs { Exception = ex }); SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
} }
finally finally
{ {
@@ -1157,7 +1172,7 @@ namespace NTwain
{ {
imgInfo = null; imgInfo = null;
} }
OnDataTransferred(new DataTransferredEventArgs { FileDataPath = finalFile, ImageInfo = imgInfo }); SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { FileDataPath = finalFile, ImageInfo = imgInfo });
} }
} }
} }

View File

@@ -20,9 +20,15 @@ and how it works in general. Except for certain "important" calls that drive the
TWAIN state change, most triplet operations are only availble as-is so you will need to know TWAIN state change, most triplet operations are only availble as-is so you will need to know
when and how to use them. There are no high-level, single-line scan-a-page-for-me-now functions. when and how to use them. There are no high-level, single-line scan-a-page-for-me-now functions.
At the moment this lib does not provide ways to parse transferred image data and require
consumers to do the conversion. The winform project contains one such
example for handling DIB image in native transfer.
The main class to use is TwainSession. New it up, hook into the events, and start calling The main class to use is TwainSession. New it up, hook into the events, and start calling
all the TWAIN functions provided through it. all the TWAIN functions provided through it.
Caveats
--------------------------------------
At the moment this lib does not provide ways to parse transferred image data and require
consumers to do the conversion themselves. The winform project contains one such
example for handling DIB image in native transfer using the CommonWin32 lib.
Because it hosts its own message thread, the event callbacks will likely be from another thread.
If you would like things marshalled to a "UI" thread then set the SynchronizationContext property
to the one from the UI thread. This part is highly experimental.

View File

@@ -11,6 +11,7 @@ using System.Windows.Media.Imaging;
using CommonWin32; using CommonWin32;
using System.Threading; using System.Threading;
using GalaSoft.MvvmLight.Messaging; using GalaSoft.MvvmLight.Messaging;
using System.Diagnostics;
namespace Tester.WPF namespace Tester.WPF
{ {
@@ -22,7 +23,7 @@ namespace Tester.WPF
public TwainVM() public TwainVM()
: base(TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Audio, Assembly.GetEntryAssembly())) : base(TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Audio, Assembly.GetEntryAssembly()))
{ {
this.SynchronizationContext = SynchronizationContext.Current;
} }
private ImageSource _image; private ImageSource _image;
@@ -63,7 +64,6 @@ namespace Tester.WPF
Button = System.Windows.MessageBoxButton.OK Button = System.Windows.MessageBoxButton.OK
}); });
} }
base.OnTransferError(e);
} }
protected override void OnTransferReady(TransferReadyEventArgs e) protected override void OnTransferReady(TransferReadyEventArgs e)
@@ -82,24 +82,22 @@ namespace Tester.WPF
}; };
var rc = this.DGControl.SetupFileXfer.Set(fileSetup); var rc = this.DGControl.SetupFileXfer.Set(fileSetup);
} }
base.OnTransferReady(e);
} }
protected override void OnDataTransferred(DataTransferredEventArgs e) protected override void OnDataTransferred(DataTransferredEventArgs e)
{ {
App.Current.Dispatcher.Invoke(new Action(() => //App.Current.Dispatcher.Invoke(new Action(() =>
//{
if (e.NativeData != IntPtr.Zero)
{ {
if (e.NativeData != IntPtr.Zero) Image = e.NativeData.GetWPFBitmap();
{ }
Image = e.NativeData.GetWPFBitmap(); else if (!string.IsNullOrEmpty(e.FileDataPath))
} {
else if (!string.IsNullOrEmpty(e.FileDataPath)) var img = new BitmapImage(new Uri(e.FileDataPath));
{ Image = img;
var img = new BitmapImage(new Uri(e.FileDataPath)); }
Image = img; //}));
}
}));
base.OnDataTransferred(e);
} }
public void TestCapture(IntPtr hwnd) public void TestCapture(IntPtr hwnd)

View File

@@ -61,10 +61,21 @@ namespace Tester.Winform
{ {
var appId = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetEntryAssembly()); var appId = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetEntryAssembly());
_twain = new TwainSession(appId); _twain = new TwainSession(appId);
// either set this and don't worry about threads during events,
// or don't and invoke during the events yourselv
_twain.SynchronizationContext = SynchronizationContext.Current;
_twain.StateChanged += (s, e) =>
{
Debug.WriteLine("State change on thread " + Thread.CurrentThread.ManagedThreadId);
//this.BeginInvoke(new Action(() =>
//{
// Debug.WriteLine("State change marshaled to thread " + Thread.CurrentThread.ManagedThreadId);
//}));
};
_twain.DataTransferred += (s, e) => _twain.DataTransferred += (s, e) =>
{ {
this.Invoke(new Action(() => //this.Invoke(new Action(() =>
{ //{
if (pictureBox1.Image != null) if (pictureBox1.Image != null)
{ {
pictureBox1.Image.Dispose(); pictureBox1.Image.Dispose();
@@ -72,7 +83,6 @@ namespace Tester.Winform
} }
if (e.NativeData != IntPtr.Zero) if (e.NativeData != IntPtr.Zero)
{ {
//_ptrTest = e.Data;
var img = e.NativeData.GetDrawingBitmap(); var img = e.NativeData.GetDrawingBitmap();
if (img != null) if (img != null)
pictureBox1.Image = img; pictureBox1.Image = img;
@@ -82,17 +92,17 @@ namespace Tester.Winform
var img = new Bitmap(e.FileDataPath); var img = new Bitmap(e.FileDataPath);
pictureBox1.Image = img; pictureBox1.Image = img;
} }
})); //}));
}; };
_twain.SourceDisabled += (s, e) => _twain.SourceDisabled += (s, e) =>
{ {
this.Invoke(new Action(() => //this.Invoke(new Action(() =>
{ //{
btnStopScan.Enabled = false; btnStopScan.Enabled = false;
btnStartCapture.Enabled = true; btnStartCapture.Enabled = true;
panelOptions.Enabled = true; panelOptions.Enabled = true;
LoadSourceCaps(); LoadSourceCaps();
})); //}));
}; };
_twain.TransferReady += (s, e) => _twain.TransferReady += (s, e) =>
{ {