diff --git a/Stylet/BindableCollection.cs b/Stylet/BindableCollection.cs index 4ba09ea..daebbcf 100644 --- a/Stylet/BindableCollection.cs +++ b/Stylet/BindableCollection.cs @@ -29,11 +29,19 @@ namespace Stylet void RemoveRange(IEnumerable items); } + /// + /// Interface encapsulating IReadOnlyList and INotifyCollectionChanged + /// + /// + public interface IReadOnlyObservableCollection : IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChangedDispatcher + { + } + /// /// ObservableCollection subclass which supports AddRange and RemoveRange /// /// - public class BindableCollection : ObservableCollection, IObservableCollection + public class BindableCollection : ObservableCollection, IObservableCollection, IReadOnlyObservableCollection { private Action _propertyChangedDispatcher = Execute.DefaultPropertyChangedDispatcher; /// diff --git a/Stylet/BootstrapperBase.cs b/Stylet/BootstrapperBase.cs index 9dcd594..e8ad8fd 100644 --- a/Stylet/BootstrapperBase.cs +++ b/Stylet/BootstrapperBase.cs @@ -45,7 +45,7 @@ namespace Stylet protected virtual void Start() { // Use the current SynchronizationContext for the Execute helper - Execute.SynchronizationContext = SynchronizationContext.Current; + Execute.Dispatcher = new DispatcherWrapper(); // Add the current assembly to the assemblies list - this will be needed by the IViewManager // However it must be done *after* the SynchronizationContext has been set, or we'll try to raise a PropertyChanged notification and fail diff --git a/Stylet/Execute.cs b/Stylet/Execute.cs index 4a6f9a3..412a50b 100644 --- a/Stylet/Execute.cs +++ b/Stylet/Execute.cs @@ -7,15 +7,63 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Threading; namespace Stylet { + /// + /// Generalised dispatcher, which can post and end + /// + public interface IDispatcher + { + /// + /// Execute asynchronously + /// + void Post(Action action); + + /// + /// Execute synchronously + /// + void Send(Action action); + + /// + /// True if invocation isn't required + /// + bool IsCurrent { get; } + } + + internal class DispatcherWrapper : IDispatcher + { + private Dispatcher dispatcher; + + public DispatcherWrapper() + { + this.dispatcher = Dispatcher.CurrentDispatcher; + } + + + public void Post(Action action) + { + this.dispatcher.BeginInvoke(action); + } + + public void Send(Action action) + { + this.dispatcher.Invoke(action); + } + + public bool IsCurrent + { + get { return this.dispatcher.CheckAccess(); } + } + } + public static class Execute { /// - /// Should be set to the UI thread's SynchronizationContext. This is normally done by the Bootstrapper. + /// Should be set to the UI thread's Dispatcher. This is normally done by the Bootstrapper. /// - public static SynchronizationContext SynchronizationContext; + public static IDispatcher Dispatcher; /// /// FOR TESTING ONLY. Causes everything to execute synchronously @@ -29,9 +77,9 @@ namespace Stylet /// public static Action DefaultPropertyChangedDispatcher = Execute.BeginOnUIThreadOrSynchronous; - private static void EnsureSynchronizationContext() + private static void EnsureDispatcher() { - if (SynchronizationContext == null && !TestExecuteSynchronously) + if (Dispatcher == null && !TestExecuteSynchronously) throw new InvalidOperationException("Execute.SynchronizationContext must be set before this method can be called. This should normally have been done by the Bootstrapper"); } @@ -40,9 +88,9 @@ namespace Stylet /// public static void BeginOnUIThread(Action action) { - EnsureSynchronizationContext(); + EnsureDispatcher(); if (!TestExecuteSynchronously) - SynchronizationContext.Post(_ => action(), null); + Dispatcher.Post(action); else action(); } @@ -52,9 +100,9 @@ namespace Stylet /// public static void BeginOnUIThreadOrSynchronous(Action action) { - EnsureSynchronizationContext(); - if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously) - SynchronizationContext.Post(_ => action(), null); + EnsureDispatcher(); + if (!TestExecuteSynchronously && !Dispatcher.IsCurrent) + Dispatcher.Post(action); else action(); } @@ -64,11 +112,11 @@ namespace Stylet /// public static void OnUIThread(Action action) { - EnsureSynchronizationContext(); + EnsureDispatcher(); Exception exception = null; - if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously) + if (!TestExecuteSynchronously && !Dispatcher.IsCurrent) { - SynchronizationContext.Send(_ => + Dispatcher.Send(() => { try { @@ -78,7 +126,7 @@ namespace Stylet { exception = e; } - }, null); + }); if (exception != null) throw new System.Reflection.TargetInvocationException("An error occurred while dispatching a call to the UI Thread", exception); @@ -94,11 +142,11 @@ namespace Stylet /// public static Task OnUIThreadAsync(Action action) { - EnsureSynchronizationContext(); - if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously) + EnsureDispatcher(); + if (!TestExecuteSynchronously && !Dispatcher.IsCurrent) { var tcs = new TaskCompletionSource(); - SynchronizationContext.Post(_ => + Dispatcher.Post(() => { try { @@ -109,7 +157,7 @@ namespace Stylet { tcs.SetException(e); } - }, null); + }); return tcs.Task; } else diff --git a/StyletUnitTests/ExecuteTests.cs b/StyletUnitTests/ExecuteTests.cs index 87055da..010ab72 100644 --- a/StyletUnitTests/ExecuteTests.cs +++ b/StyletUnitTests/ExecuteTests.cs @@ -21,71 +21,71 @@ namespace StyletUnitTests } [Test] - public void OnUIThreadExecutesUsingSynchronizationContext() + public void OnUIThreadExecutesUsingDispatcher() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; - SendOrPostCallback passedAction = null; - sync.Setup(x => x.Send(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + Action passedAction = null; + sync.Setup(x => x.Send(It.IsAny())).Callback((Action a) => passedAction = a); bool actionCalled = false; Execute.OnUIThread(() => actionCalled = true); Assert.IsFalse(actionCalled); - passedAction(null); + passedAction(); Assert.IsTrue(actionCalled); } [Test] - public void BeginOnUIThreadExecutesUsingSynchronizationContext() + public void BeginOnUIThreadExecutesUsingDispatcher() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; - SendOrPostCallback passedAction = null; - sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); bool actionCalled = false; Execute.BeginOnUIThread(() => actionCalled = true); Assert.IsFalse(actionCalled); - passedAction(null); + passedAction(); Assert.IsTrue(actionCalled); } [Test] - public void BeginOnUIThreadOrSynchronousExecutesUsingSynchronizationContextIfNotCurrent() + public void BeginOnUIThreadOrSynchronousExecutesUsingDispatcherIfNotCurrent() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; - SendOrPostCallback passedAction = null; - sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); bool actionCalled = false; Execute.BeginOnUIThreadOrSynchronous(() => actionCalled = true); Assert.IsFalse(actionCalled); - passedAction(null); + passedAction(); Assert.IsTrue(actionCalled); } [Test] - public void OnUIThreadAsyncExecutesAsynchronouslyIfSynchronizationContextIsNotNull() + public void OnUIThreadAsyncExecutesAsynchronouslyIfDispatcherIsNotNull() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; - SendOrPostCallback passedAction = null; - sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); bool actionCalled = false; var task = Execute.OnUIThreadAsync(() => actionCalled = true); Assert.IsFalse(task.IsCompleted); Assert.IsFalse(actionCalled); - passedAction(null); + passedAction(); Assert.IsTrue(actionCalled); Assert.IsTrue(task.IsCompleted); } @@ -93,11 +93,11 @@ namespace StyletUnitTests [Test] public void OnUIThreadPropagatesException() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; var ex = new Exception("testy"); - sync.Setup(x => x.Send(It.IsAny(), null)).Callback((a, b) => a(b)); + sync.Setup(x => x.Send(It.IsAny())).Callback(a => a()); Exception caughtEx = null; try { Execute.OnUIThread(() => { throw ex; }); } @@ -110,45 +110,45 @@ namespace StyletUnitTests [Test] public void OnUIThreadAsyncPropagatesException() { - var sync = new Mock(); - Execute.SynchronizationContext = sync.Object; + var sync = new Mock(); + Execute.Dispatcher = sync.Object; - SendOrPostCallback passedAction = null; - sync.Setup(x => x.Post(It.IsAny(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a); + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); var ex = new Exception("test"); var task = Execute.OnUIThreadAsync(() => { throw ex; }); - passedAction(null); + passedAction(); Assert.IsTrue(task.IsFaulted); Assert.AreEqual(ex, task.Exception.InnerExceptions[0]); } [Test] - public void ThrowsIfBeginOnUIThreadCalledWithNoSynchronizationContext() + public void ThrowsIfBeginOnUIThreadCalledWithNoDispatcher() { - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; Assert.Throws(() => Execute.BeginOnUIThread(() => { })); } [Test] - public void ThrowsIfBeginOnUIThreadOrSynchronousCalledWithNoSynchronizationContext() + public void ThrowsIfBeginOnUIThreadOrSynchronousCalledWithNoDispatcher() { - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; Assert.Throws(() => Execute.BeginOnUIThreadOrSynchronous(() => { })); } [Test] - public void ThrowsIfOnUIThreadCalledWithNoSynchronizationContext() + public void ThrowsIfOnUIThreadCalledWithNoDispatcher() { - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; Assert.Throws(() => Execute.OnUIThread(() => { })); } [Test] - public void ThrowsIfOnUIThreadAsyncCalledWithNoSynchronizationContext() + public void ThrowsIfOnUIThreadAsyncCalledWithNoDispatcher() { - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; Assert.Throws(() => Execute.OnUIThreadAsync(() => { })); } @@ -157,7 +157,7 @@ namespace StyletUnitTests { Execute.TestExecuteSynchronously = true; - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; bool called = false; Execute.BeginOnUIThread(() => called = true); Assert.True(called); @@ -168,7 +168,7 @@ namespace StyletUnitTests { Execute.TestExecuteSynchronously = true; - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; bool called = false; Execute.OnUIThread(() => called = true); Assert.True(called); @@ -179,7 +179,7 @@ namespace StyletUnitTests { Execute.TestExecuteSynchronously = true; - Execute.SynchronizationContext = null; + Execute.Dispatcher = null; bool called = false; Execute.OnUIThreadAsync(() => called = true); Assert.True(called);