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

View File

@ -14,6 +14,6 @@ namespace NTwain
// keep this same in majors releases
public const string Release = "0.11.0.0";
// 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
@ -88,7 +98,7 @@ namespace NTwain
if (notifyChange)
{
RaisePropertyChanged("State");
OnStateChanged();
SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
}
}
@ -101,7 +111,7 @@ namespace NTwain
{
SourceId = sourceId;
RaisePropertyChanged("SourceId");
OnSourceChanged();
SafeAsyncSyncableRaiseOnEvent(OnSourceChanged, SourceChanged);
}
#endregion
@ -132,7 +142,7 @@ namespace NTwain
{
_state = value;
RaisePropertyChanged("State");
OnStateChanged();
SafeAsyncSyncableRaiseOnEvent(OnStateChanged, StateChanged);
}
}
}
@ -195,8 +205,27 @@ namespace NTwain
/// <param name="propertyName">Name of the property.</param>
protected void RaisePropertyChanged(string propertyName)
{
var hand = PropertyChanged;
if (hand != null) { hand(this, new PropertyChangedEventArgs(propertyName)); }
if (SynchronizationContext == null)
{
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
@ -389,7 +418,7 @@ namespace NTwain
{
_callbackObj = null;
}
OnSourceDisabled();
SafeAsyncSyncableRaiseOnEvent(OnSourceDisabled, SourceDisabled);
}
});
return rc;
@ -483,123 +512,108 @@ namespace NTwain
public event EventHandler<TransferErrorEventArgs> TransferError;
/// <summary>
/// Called when <see cref="State"/> changed
/// and raises the <see cref="StateChanged" /> event.
/// Raises event and if applicable marshal it synchronously to the <see cref="SynchronizationContext" /> thread.
/// </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;
if (hand != null)
var syncer = SynchronizationContext;
if (syncer == null)
{
try
{
hand(this, EventArgs.Empty);
onEventFunc(e);
if (handler != null) { handler(this, e); }
}
catch { }
}
else
{
syncer.Send(o =>
{
try
{
onEventFunc(e);
if (handler != null) { handler(this, e); }
}
catch { }
}, null);
}
}
/// <summary>
/// Called when <see cref="SourceId"/> changed
/// and raises the <see cref="SourceChanged" /> event.
/// Raises event and if applicable marshal it asynchronously to the <see cref="SynchronizationContext"/> thread.
/// </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;
if (hand != null)
var syncer = SynchronizationContext;
if (syncer == null)
{
try
{
hand(this, EventArgs.Empty);
onEventFunc();
if (handler != null) { handler(this, EventArgs.Empty); }
}
catch { }
}
else
{
syncer.Post(o =>
{
try
{
onEventFunc();
if (handler != null) { handler(this, EventArgs.Empty); }
}
catch { }
}, null);
}
}
/// <summary>
/// Called when source has been disabled (back to state 4)
/// and raises the <see cref="SourceDisabled" /> event.
/// Called when <see cref="State"/> changed.
/// </summary>
protected virtual void OnSourceDisabled()
{
var hand = SourceDisabled;
if (hand != null)
{
try
{
hand(this, EventArgs.Empty);
}
catch { }
}
}
protected virtual void OnStateChanged() { }
/// <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>
/// <param name="e">The <see cref="DeviceEventArgs"/> instance containing the event data.</param>
protected virtual void OnDeviceEvent(DeviceEventArgs e)
{
var hand = DeviceEvent;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
protected virtual void OnDeviceEvent(DeviceEventArgs e) { }
/// <summary>
/// Raises the <see cref="E:TransferReady" /> event.
/// Called when a data transfer is ready.
/// </summary>
/// <param name="e">The <see cref="TransferReadyEventArgs"/> instance containing the event data.</param>
protected virtual void OnTransferReady(TransferReadyEventArgs e)
{
var hand = TransferReady;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
protected virtual void OnTransferReady(TransferReadyEventArgs e) { }
/// <summary>
/// Raises the <see cref="E:DataTransferred" /> event.
/// Called when data has been transferred.
/// </summary>
/// <param name="e">The <see cref="DataTransferredEventArgs"/> instance containing the event data.</param>
protected virtual void OnDataTransferred(DataTransferredEventArgs e)
{
var hand = DataTransferred;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
protected virtual void OnDataTransferred(DataTransferredEventArgs e) { }
/// <summary>
/// Raises the <see cref="E:TransferError" /> event.
/// Called when an error has been encountered during transfer.
/// </summary>
/// <param name="e">The <see cref="TransferErrorEventArgs"/> instance containing the event data.</param>
protected virtual void OnTransferError(TransferErrorEventArgs e)
{
var hand = TransferError;
if (hand != null)
{
try
{
hand(this, e);
}
catch { }
}
}
protected virtual void OnTransferError(TransferErrorEventArgs e) { }
#endregion
#region TWAIN logic during xfer work
@ -671,7 +685,7 @@ namespace NTwain
var rc = DGControl.DeviceEvent.Get(out de);
if (rc == ReturnCode.Success)
{
OnDeviceEvent(new DeviceEventArgs(de));
SafeSyncableRaiseOnEvent(OnDeviceEvent, DeviceEvent, new DeviceEventArgs(de));
}
break;
case Message.CloseDSReq:
@ -726,7 +740,7 @@ namespace NTwain
EndOfJob = pending.EndOfJob == 0
};
OnTransferReady(preXferArgs);
SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, preXferArgs);
#endregion
@ -805,16 +819,17 @@ namespace NTwain
{
lockedPtr = MemoryManager.Instance.Lock(dataPtr);
}
OnDataTransferred(new DataTransferredEventArgs { NativeData = lockedPtr });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { NativeData = lockedPtr });
}
else
{
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
}
}
catch (Exception ex)
{
OnTransferError(new TransferErrorEventArgs { Exception = ex });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
}
finally
{
@ -845,11 +860,11 @@ namespace NTwain
var xrc = DGAudio.AudioFileXfer.Get();
if (xrc == ReturnCode.XferDone)
{
OnDataTransferred(new DataTransferredEventArgs { FileDataPath = filePath });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { FileDataPath = filePath });
}
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);
}
OnDataTransferred(new DataTransferredEventArgs { NativeData = lockedPtr, ImageInfo = imgInfo });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { NativeData = lockedPtr, ImageInfo = imgInfo });
}
else
{
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
}
}
catch (Exception ex)
{
OnTransferError(new TransferErrorEventArgs { Exception = ex });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
}
finally
{
@ -921,11 +936,11 @@ namespace NTwain
{
imgInfo = null;
}
OnDataTransferred(new DataTransferredEventArgs { FileDataPath = filePath, ImageInfo = imgInfo });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { FileDataPath = filePath, ImageInfo = imgInfo });
}
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)
{
OnDataTransferred(new DataTransferredEventArgs { MemData = xferredData.ToArray(), ImageInfo = imgInfo });
SafeSyncableRaiseOnEvent(OnDataTransferred, DataTransferred, new DataTransferredEventArgs { MemData = xferredData.ToArray(), ImageInfo = imgInfo });
}
else
{
@ -1004,13 +1019,13 @@ namespace NTwain
}
else
{
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
}
}
}
catch (Exception ex)
{
OnTransferError(new TransferErrorEventArgs { Exception = ex });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
}
finally
{
@ -1130,12 +1145,12 @@ namespace NTwain
}
else
{
OnTransferError(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = this.GetSourceStatus() });
}
}
catch (Exception ex)
{
OnTransferError(new TransferErrorEventArgs { Exception = ex });
SafeSyncableRaiseOnEvent(OnTransferError, TransferError, new TransferErrorEventArgs { Exception = ex });
}
finally
{
@ -1157,7 +1172,7 @@ namespace NTwain
{
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
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
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 System.Threading;
using GalaSoft.MvvmLight.Messaging;
using System.Diagnostics;
namespace Tester.WPF
{
@ -22,7 +23,7 @@ namespace Tester.WPF
public TwainVM()
: base(TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Audio, Assembly.GetEntryAssembly()))
{
this.SynchronizationContext = SynchronizationContext.Current;
}
private ImageSource _image;
@ -63,7 +64,6 @@ namespace Tester.WPF
Button = System.Windows.MessageBoxButton.OK
});
}
base.OnTransferError(e);
}
protected override void OnTransferReady(TransferReadyEventArgs e)
@ -82,24 +82,22 @@ namespace Tester.WPF
};
var rc = this.DGControl.SetupFileXfer.Set(fileSetup);
}
base.OnTransferReady(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();
}
else if (!string.IsNullOrEmpty(e.FileDataPath))
{
var img = new BitmapImage(new Uri(e.FileDataPath));
Image = img;
}
}));
base.OnDataTransferred(e);
Image = e.NativeData.GetWPFBitmap();
}
else if (!string.IsNullOrEmpty(e.FileDataPath))
{
var img = new BitmapImage(new Uri(e.FileDataPath));
Image = img;
}
//}));
}
public void TestCapture(IntPtr hwnd)

View File

@ -61,10 +61,21 @@ namespace Tester.Winform
{
var appId = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetEntryAssembly());
_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) =>
{
this.Invoke(new Action(() =>
{
//this.Invoke(new Action(() =>
//{
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
@ -72,7 +83,6 @@ namespace Tester.Winform
}
if (e.NativeData != IntPtr.Zero)
{
//_ptrTest = e.Data;
var img = e.NativeData.GetDrawingBitmap();
if (img != null)
pictureBox1.Image = img;
@ -82,17 +92,17 @@ namespace Tester.Winform
var img = new Bitmap(e.FileDataPath);
pictureBox1.Image = img;
}
}));
//}));
};
_twain.SourceDisabled += (s, e) =>
{
this.Invoke(new Action(() =>
{
//this.Invoke(new Action(() =>
//{
btnStopScan.Enabled = false;
btnStartCapture.Enabled = true;
panelOptions.Enabled = true;
LoadSourceCaps();
}));
//}));
};
_twain.TransferReady += (s, e) =>
{