Merge branch 'release/0.9.5'

This commit is contained in:
Antony Male 2014-04-24 13:00:38 +01:00
commit f32e83cbb5
20 changed files with 245 additions and 121 deletions

View File

@ -1,6 +1,13 @@
Stylet Changelog
================
v0.9.5
------
- Add StyletConductorItemsControl style, for using an ItemsControl with a Conductor<T>.Collection.AllActive
- Make Execute.OnUIThread synchronous, and add BeginOnUIThread (asynchronous)
- Rename conductors. It's now Conductor<T>.Collection.xxx not Conductor<T>.Collections.xxx
v0.9.4
------

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
<metadata>
<id>Stylet</id>
<version>0.9.4</version>
<version>0.9.5</version>
<title>Stylet</title>
<authors>Antony Male</authors>
<owners>Antony Male</owners>

View File

@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Stylet.Samples.RedditBrowser.Pages
{
public class ShellViewModel : Conductor<IScreen>.Collections.OneActive, IHandle<OpenSubredditEvent>
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<OpenSubredditEvent>
{
private ISubredditViewModelFactory subredditViewModelFactory;

View File

@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Stylet.Samples.RedditBrowser.Pages
{
public class SubredditViewModel : Conductor<IScreen>.Collections.Navigation
public class SubredditViewModel : Conductor<IScreen>.StackNavigation
{
private IPostCommentsViewModelFactory postCommentsViewModelFactory;

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Stylet.Samples.TabNavigation
{
class ShellViewModel : Conductor<IScreen>.Collections.OneActive
class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
public ShellViewModel(Page1ViewModel page1, Page2ViewModel page2)
{

View File

@ -10,7 +10,7 @@ namespace Stylet
{
public partial class Conductor<T>
{
public partial class Collections
public partial class Collection
{
/// <summary>
/// Conductor which has many items, all of which active at the same time

View File

@ -8,106 +8,103 @@ namespace Stylet
{
public partial class Conductor<T>
{
public partial class Collections
{
/// <summary>
/// Stack-based navigation. A Conductor which has one active item, and a stack of previous items
/// </summary>
public class Navigation : ConductorBaseWithActiveItem<T>
public class StackNavigation : ConductorBaseWithActiveItem<T>
{
// We need to remove arbitrary items, so no Stack<T> here!
private List<T> history = new List<T>();
/// <summary>
/// Activate the given item. This deactivates the previous item, and pushes it onto the history stack
/// </summary>
/// <param name="item">Item to activate</param>
public override void ActivateItem(T item)
{
// We need to remove arbitrary items, so no Stack<T> here!
private List<T> history = new List<T>();
/// <summary>
/// Activate the given item. This deactivates the previous item, and pushes it onto the history stack
/// </summary>
/// <param name="item">Item to activate</param>
public override void ActivateItem(T item)
if (item != null && item.Equals(this.ActiveItem))
{
if (item != null && item.Equals(this.ActiveItem))
if (this.IsActive)
ScreenExtensions.TryActivate(this.ActiveItem);
}
else
{
if (this.ActiveItem != null)
this.history.Add(this.ActiveItem);
this.ChangeActiveItem(item, false);
}
}
/// <summary>
/// Deactivate the given item
/// </summary>
/// <param name="item">Item to deactivate</param>
public override void DeactivateItem(T item)
{
ScreenExtensions.TryDeactivate(item);
}
/// <summary>
/// Close the active item, and re-activate the top item in the history stack
/// </summary>
public void GoBack()
{
this.CloseItem(this.ActiveItem);
}
/// <summary>
/// Close and remove all items in the history stack, leaving the ActiveItem
/// </summary>
public void Clear()
{
foreach (var item in this.history)
this.CloseAndCleanUp(item);
this.history.Clear();
}
/// <summary>
/// Close the given item. If it was the ActiveItem, activate the top item in the history stack
/// </summary>
/// <param name="item"></param>
public override async void CloseItem(T item)
{
if (item == null || !await this.CanCloseItem(item))
return;
if (item.Equals(this.ActiveItem))
{
var newItem = default(T);
if (this.history.Count > 0)
{
if (this.IsActive)
ScreenExtensions.TryActivate(this.ActiveItem);
}
else
{
if (this.ActiveItem != null)
this.history.Add(this.ActiveItem);
this.ChangeActiveItem(item, false);
newItem = this.history.Last();
this.history.RemoveAt(this.history.Count - 1);
}
this.ChangeActiveItem(newItem, true);
}
/// <summary>
/// Deactivate the given item
/// </summary>
/// <param name="item">Item to deactivate</param>
public override void DeactivateItem(T item)
else if (this.history.Contains(item))
{
ScreenExtensions.TryDeactivate(item);
this.CloseAndCleanUp(item);
this.history.Remove(item);
}
}
/// <summary>
/// Close the active item, and re-activate the top item in the history stack
/// </summary>
public void GoBack()
{
this.CloseItem(this.ActiveItem);
}
/// <summary>
/// Returns true if and when all items (ActiveItem + everything in the history stack) can close
/// </summary>
/// <returns></returns>
public override Task<bool> CanCloseAsync()
{
return this.CanAllItemsCloseAsync(this.history.Concat(new[] { this.ActiveItem }));
}
/// <summary>
/// Close and remove all items in the history stack, leaving the ActiveItem
/// </summary>
public void Clear()
{
foreach (var item in this.history)
this.CloseAndCleanUp(item);
this.history.Clear();
}
protected override void OnClose()
{
// We've already been deactivated by this point
foreach (var item in this.history)
this.CloseAndCleanUp(item);
this.history.Clear();
/// <summary>
/// Close the given item. If it was the ActiveItem, activate the top item in the history stack
/// </summary>
/// <param name="item"></param>
public override async void CloseItem(T item)
{
if (item == null || !await this.CanCloseItem(item))
return;
if (item.Equals(this.ActiveItem))
{
var newItem = default(T);
if (this.history.Count > 0)
{
newItem = this.history.Last();
this.history.RemoveAt(this.history.Count-1);
}
this.ChangeActiveItem(newItem, true);
}
else if (this.history.Contains(item))
{
this.CloseAndCleanUp(item);
this.history.Remove(item);
}
}
/// <summary>
/// Returns true if and when all items (ActiveItem + everything in the history stack) can close
/// </summary>
/// <returns></returns>
public override Task<bool> CanCloseAsync()
{
return this.CanAllItemsCloseAsync(this.history.Concat(new[] { this.ActiveItem }));
}
protected override void OnClose()
{
// We've already been deactivated by this point
foreach (var item in this.history)
this.CloseAndCleanUp(item);
this.history.Clear();
this.CloseAndCleanUp(this.ActiveItem);
}
this.CloseAndCleanUp(this.ActiveItem);
}
}
}

View File

@ -10,7 +10,7 @@ namespace Stylet
{
public partial class Conductor<T>
{
public partial class Collections
public partial class Collection
{
/// <summary>
/// Conductor with many items, only one of which is active

View File

@ -12,24 +12,90 @@ namespace Stylet
{
public static class Execute
{
/// <summary>
/// Should be set to the UI thread's SynchronizationContext. This is normally done by the Bootstrapper.
/// </summary>
public static SynchronizationContext SynchronizationContext;
/// <summary>
/// FOR TESTING ONLY. Causes everything to execute synchronously
/// </summary>
public static bool TestExecuteSynchronously = false;
private static bool? inDesignMode;
public static Action<Action> DefaultPropertyChangedDispatcher = Execute.OnUIThread;
/// <summary>
/// Default dispatcher used by PropertyChangedBase instances. Defaults to BeginOnUIThreadOrSynchronous
/// </summary>
public static Action<Action> DefaultPropertyChangedDispatcher = Execute.BeginOnUIThreadOrSynchronous;
public static void OnUIThread(Action action)
private static void EnsureSynchronizationContext()
{
// If we're already on the given SynchronizationContext, or it hasn't been set, run synchronously
if (SynchronizationContext != null && SynchronizationContext != SynchronizationContext.Current)
if (SynchronizationContext == null && !TestExecuteSynchronously)
throw new Exception("Execute.SynchronizationContext must be set before this method can be called. This should normally have been done by the Bootstrapper");
}
/// <summary>
/// Dispatches the given action to be run on the UI thread, even if the current thread is the UI thread
/// </summary>
public static void BeginOnUIThread(Action action)
{
EnsureSynchronizationContext();
if (!TestExecuteSynchronously)
SynchronizationContext.Post(_ => action(), null);
else
action();
}
/// <summary>
/// Dispatches the given action to be run on the UI thread, or runs it synchronously if the current thread is the UI thread
/// </summary>
public static void BeginOnUIThreadOrSynchronous(Action action)
{
EnsureSynchronizationContext();
if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously)
SynchronizationContext.Post(_ => action(), null);
else
action();
}
/// <summary>
/// Dispatches the given action to be run on the UI thread and blocks until it completes, or runs it synchronously if the current thread is the UI thread
/// </summary>
public static void OnUIThread(Action action)
{
EnsureSynchronizationContext();
Exception exception = null;
if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously)
{
SynchronizationContext.Send(_ =>
{
try
{
action();
}
catch (Exception e)
{
exception = e;
}
}, null);
if (exception != null)
throw new System.Reflection.TargetInvocationException("An error occurred while dispatching a call to the UI Thread", exception);
}
else
{
action();
}
}
/// <summary>
/// Dispatches the given action to be run on the UI thread and returns a task that completes when the action completes, or runs it synchronously if the current thread is the UI thread
/// </summary>
public static Task OnUIThreadAsync(Action action)
{
// If we're already on the given SynchronizationContext, or it hasn't been set, run synchronously
if (SynchronizationContext != null && SynchronizationContext != SynchronizationContext.Current)
EnsureSynchronizationContext();
if (SynchronizationContext != SynchronizationContext.Current && !TestExecuteSynchronously)
{
var tcs = new TaskCompletionSource<object>();
SynchronizationContext.Post(_ =>

View File

@ -35,5 +35,5 @@ using System.Windows.Markup;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.9.4.0")]
[assembly: AssemblyFileVersion("0.9.4.0")]
[assembly: AssemblyVersion("0.9.5.0")]
[assembly: AssemblyFileVersion("0.9.5.0")]

View File

@ -64,7 +64,7 @@ namespace Stylet
var propertyAsEventInfo = valueService.TargetProperty as EventInfo;
if (propertyAsEventInfo != null)
{
var ec = new EventAction((DependencyObject)valueService.TargetObject, propertyAsEventInfo, this.Method, this.NullTarget.GetValueOrDefault(ActionUnavailableBehaviour.Throw), this.ActionNotFound.GetValueOrDefault(ActionUnavailableBehaviour.Throw));
var ec = new EventAction((DependencyObject)valueService.TargetObject, propertyAsEventInfo, this.Method, this.NullTarget.GetValueOrDefault(ActionUnavailableBehaviour.Enable), this.ActionNotFound.GetValueOrDefault(ActionUnavailableBehaviour.Throw));
return ec.GetDelegate();
}

View File

@ -13,4 +13,15 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="StyletConductorItemsControl" TargetType="ItemsControl">
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@ -16,6 +16,12 @@ namespace StyletUnitTests
{
private class Element { }
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[Test]
public void AddRangeAddsElements()
{

View File

@ -12,12 +12,18 @@ namespace StyletUnitTests
[TestFixture]
public class ConductorAllActiveTests
{
private Conductor<IScreen>.Collections.AllActive conductor;
private Conductor<IScreen>.Collection.AllActive conductor;
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[SetUp]
public void SetUp()
{
this.conductor = new Conductor<IScreen>.Collections.AllActive();
this.conductor = new Conductor<IScreen>.Collection.AllActive();
}
[Test]

View File

@ -12,12 +12,12 @@ namespace StyletUnitTests
[TestFixture]
public class ConductorNavigatingTests
{
private Conductor<IScreen>.Collections.Navigation conductor;
private Conductor<IScreen>.StackNavigation conductor;
[SetUp]
public void SetUp()
{
this.conductor = new Conductor<IScreen>.Collections.Navigation();
this.conductor = new Conductor<IScreen>.StackNavigation();
}
[Test]

View File

@ -12,12 +12,18 @@ namespace StyletUnitTests
[TestFixture]
public class ConductorOneActiveTests
{
private Conductor<IScreen>.Collections.OneActive conductor;
private Conductor<IScreen>.Collection.OneActive conductor;
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[SetUp]
public void SetUp()
{
this.conductor = new Conductor<IScreen>.Collections.OneActive();
this.conductor = new Conductor<IScreen>.Collection.OneActive();
}
[Test]

View File

@ -13,23 +13,21 @@ namespace StyletUnitTests
[TestFixture]
public class ExecuteTests
{
[Test]
public void OnUIThreadExecutesSynchronouslyIfNoSynchronizationContext()
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.SynchronizationContext = null;
bool blockWasRun = false;
Execute.OnUIThread(() => blockWasRun = true);
Assert.IsTrue(blockWasRun);
// Dont want this being previously set by anything and messing us around
Execute.TestExecuteSynchronously = false;
}
[Test]
public void OnUIThreadExecutesAsynchronouslyIfSynchronizationContextIsNotNull()
public void OnUIThreadExecutesUsingSynchronizationContext()
{
var sync = new Mock<SynchronizationContext>();
Execute.SynchronizationContext = sync.Object;
SendOrPostCallback passedAction = null;
sync.Setup(x => x.Post(It.IsAny<SendOrPostCallback>(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a);
sync.Setup(x => x.Send(It.IsAny<SendOrPostCallback>(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a);
bool actionCalled = false;
Execute.OnUIThread(() => actionCalled = true);
@ -40,12 +38,20 @@ namespace StyletUnitTests
}
[Test]
public async Task OnUIThreadAsyncExecutesSynchronouslyIfNoSynchronizationContext()
public void BeginOnUIThreadExecutesUsingSynchronizationContext()
{
Execute.SynchronizationContext = null;
bool blockWasRun = false;
await Execute.OnUIThreadAsync(() => blockWasRun = true);
Assert.IsTrue(blockWasRun);
var sync = new Mock<SynchronizationContext>();
Execute.SynchronizationContext = sync.Object;
SendOrPostCallback passedAction = null;
sync.Setup(x => x.Post(It.IsAny<SendOrPostCallback>(), null)).Callback((SendOrPostCallback a, object o) => passedAction = a);
bool actionCalled = false;
Execute.BeginOnUIThread(() => actionCalled = true);
Assert.IsFalse(actionCalled);
passedAction(null);
Assert.IsTrue(actionCalled);
}
[Test]

View File

@ -34,6 +34,12 @@ namespace StyletUnitTests
}
}
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[Test]
public void RefreshRaisesPropertyChangedWithEmptyString()
{

View File

@ -50,6 +50,12 @@ namespace StyletUnitTests
}
}
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[Test]
public void StrongBindingBinds()
{

View File

@ -48,6 +48,13 @@ namespace StyletUnitTests
private MyScreen screen;
[TestFixtureSetUp]
public void SetUpFixture()
{
Execute.TestExecuteSynchronously = true;
}
[SetUp]
public void SetUp()
{