Work towards conductors

This commit is contained in:
Antony Male 2014-02-05 15:22:37 +00:00
parent 23a361df6e
commit 11a58f3af2
9 changed files with 393 additions and 19 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
@ -20,6 +21,7 @@ namespace Stylet
public void Start() public void Start()
{ {
this.Application = Application.Current; this.Application = Application.Current;
Execute.SynchronizationContext = SynchronizationContext.Current;
this.Application.Startup += OnStartup; this.Application.Startup += OnStartup;
this.Application.Exit += OnExit; this.Application.Exit += OnExit;

110
Stylet/Conductor.cs Normal file
View File

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Stylet
{
public abstract class ConductorWithActiveItem<T> : Screen, IConductor<T>, IParent<T> where T : class
{
protected bool CloseConductedItemsWhenConductorCannotClose = false;
private T _activeItem;
public T ActiveItem
{
get { return this._activeItem; }
set { this.ActivateItem(value); }
}
public abstract void ActivateItem(T item);
public abstract void DeactivateItem(T item, bool close);
public IEnumerable<T> GetChildren()
{
return new[] { ActiveItem };
}
protected virtual void ChangeActiveItem(T newItem, bool closePrevious)
{
var activeItem = this.ActiveItem as IDeactivate;
if (activeItem != null)
activeItem.Deactivate(closePrevious);
var newItemAsChild = newItem as IChild;
if (newItemAsChild != null && newItemAsChild.Parent != this)
newItemAsChild.Parent = this;
if (this.IsActive)
{
this.ActivateItemIfActive(newItem);
this._activeItem = newItem;
this.NotifyOfPropertyChange(() => this.ActiveItem);
}
}
protected void ActivateItemIfActive(T item)
{
var itemAsIActivate = item as IActivate;
if (this.IsActive && itemAsIActivate != null)
itemAsIActivate.Activate();
}
protected virtual async Task<IEnumerable<T>> ItemsThatCanCloseAsync(IEnumerable<T> toClose)
{
var results = await Task.WhenAll(toClose.Select(x => this.CanCloseItem(x).ContinueWith(t => new { Item = x, Result = t.Result })));
if (this.CloseConductedItemsWhenConductorCannotClose)
return results.Where(x => x.Result).Select(x => x.Item);
else
return results.All(x => x.Result) ? results.Select(x => x.Item) : Enumerable.Empty<T>();
}
protected virtual Task<bool> CanCloseItem(T item)
{
var itemAsGuardClose = item as IGuardClose;
if (itemAsGuardClose != null)
return itemAsGuardClose.CanCloseAsync();
else
return Task.FromResult(true);
}
}
public class Conductor<T> : ConductorWithActiveItem<T> where T : class
{
public override async void ActivateItem(T item)
{
if (item != null && item.Equals(this.ActiveItem))
this.ActivateItemIfActive(item);
else if (await this.CanCloseItem(this.ActiveItem))
this.ChangeActiveItem(item, true);
}
public override async void DeactivateItem(T item, bool close)
{
if (item == null || !item.Equals(this.ActiveItem))
return;
if (await this.CanCloseItem(item))
this.ChangeActiveItem(default(T), close);
}
public Task<bool> CanClose()
{
return this.CanCloseItem(this.ActiveItem);
}
protected override void OnActivate()
{
this.ActivateItemIfActive(this.ActiveItem);
}
protected override void OnDeactivate(bool close)
{
var activeItemAsDeactivate = this.ActiveItem as IDeactivate;
if (activeItemAsDeactivate != null)
activeItemAsDeactivate.Deactivate(close);
}
}
}

37
Stylet/Execute.cs Normal file
View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Stylet
{
public static class Execute
{
public static SynchronizationContext SynchronizationContext;
public static void OnUIThread(Action action)
{
SynchronizationContext.Post(_ => action(), null);
}
public static Task OnUIThreadAsync(Action action)
{
var tcs = new TaskCompletionSource<object>();
SynchronizationContext.Post(_ =>
{
try
{
action();
tcs.SetResult(null);
}
catch (Exception e)
{
tcs.SetException(e);
}
}, null);
return tcs.Task;
}
}
}

26
Stylet/IConductor.cs Normal file
View File

@ -0,0 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Stylet
{
public interface IParent<out T>
{
IEnumerable<T> GetChildren();
}
public interface IHaveActiveItem<T>
{
T ActiveItem { get; set; }
}
public interface IConductor<T> : IParent<T>
{
void ActivateItem(T item);
void DeactivateItem(T item, bool close);
}
}

57
Stylet/IScreen.cs Normal file
View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Stylet
{
public interface IViewAware
{
void AttachView(object view);
}
public interface IActivate
{
bool IsActive { get; }
void Activate();
event EventHandler<EventArgs> Activated;
}
public interface IDeactivate
{
void Deactivate(bool close);
event EventHandler<DeactivationEventArgs> Deactivated;
}
public interface IHaveDisplayName
{
string DisplayName { get; set; }
}
public interface IChild
{
object Parent { get; set; }
}
public interface IClose
{
void TryClose();
}
public interface IGuardClose : IClose
{
Task<bool> CanCloseAsync();
}
public interface IScreen : IViewAware, IHaveDisplayName, IActivate, IDeactivate, IChild
{
}
public class DeactivationEventArgs : EventArgs
{
public bool WasClosed;
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Stylet
{
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void Refresh()
{
this.NotifyOfPropertyChange(String.Empty);
}
protected void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
{
string propertyName;
if (property.Body is UnaryExpression)
propertyName = ((MemberExpression)((UnaryExpression)property.Body).Operand).Member.Name;
else
propertyName = ((MemberExpression)property.Body).Member.Name;
this.NotifyOfPropertyChange(propertyName);
}
protected virtual void NotifyOfPropertyChange([CallerMemberName] string propertyName = "")
{
Execute.OnUIThread(() =>
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
});
}
}
}

View File

@ -4,25 +4,16 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls.Primitives;
namespace Stylet namespace Stylet
{ {
public interface IViewAware public class Screen : PropertyChangedBase, IScreen
{
object View { get; }
void AttachView(object view);
}
public interface IScreen : IViewAware
{
}
public class Screen : IScreen
{ {
public virtual void TryClose(bool? dialogResult = null) public virtual void TryClose(bool? dialogResult = null)
{ {
// TODO: Check for parent conductor // TODO: Check for parent conductor
var viewWindow = this.View as Window; var viewWindow = this.view as Window;
if (viewWindow != null) if (viewWindow != null)
{ {
if (dialogResult != null) if (dialogResult != null)
@ -31,19 +22,99 @@ namespace Stylet
return; return;
} }
var viewPopover = this.view as Popup;
if (viewPopover != null)
{
viewPopover.IsOpen = false;
return;
}
throw new InvalidOperationException(String.Format("Unable to close ViewModel {0} as it must have a parent, or its view must be a Window", this.GetType().Name)); throw new InvalidOperationException(String.Format("Unable to close ViewModel {0} as it must have a parent, or its view must be a Window", this.GetType().Name));
} }
#region IHaveDisplayName
private string _displayName;
public string DisplayName
{
get { return this._displayName; }
set
{
this._displayName = value;
this.NotifyOfPropertyChange();
}
}
#endregion
#region IActivate
public event EventHandler<EventArgs> Activated;
private bool _isActive;
public bool IsActive
{
get { return this._isActive; }
set
{
this._isActive = value;
this.NotifyOfPropertyChange();
}
}
void IActivate.Activate()
{
if (this.IsActive)
return;
this.IsActive = true;
this.OnActivate();
var handler = this.Activated;
if (handler != null)
handler(this, EventArgs.Empty);
}
protected virtual void OnActivate() { }
#endregion
#region IDeactivate
public event EventHandler<DeactivationEventArgs> Deactivated;
void IDeactivate.Deactivate(bool close)
{
if (!this.IsActive)
return;
this.IsActive = false;
this.OnDeactivate(close);
var handler = this.Deactivated;
if (handler != null)
handler(this, new DeactivationEventArgs() { WasClosed = close });
if (close)
this.view = null;
}
protected virtual void OnDeactivate(bool close) { }
#endregion
#region IViewAware #region IViewAware
public object View { get; private set; } private object view;
void IViewAware.AttachView(object view) void IViewAware.AttachView(object view)
{ {
if (this.View != null) if (this.view != null)
throw new Exception(String.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, this.GetType().Name)); throw new Exception(String.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, this.GetType().Name));
this.View = view; this.view = view;
var viewAsFrameworkElement = view as FrameworkElement; var viewAsFrameworkElement = view as FrameworkElement;
if (viewAsFrameworkElement != null) if (viewAsFrameworkElement != null)
@ -58,5 +129,20 @@ namespace Stylet
protected virtual void OnViewLoaded() { } protected virtual void OnViewLoaded() { }
#endregion #endregion
#region IChild
private object _parent;
public object Parent
{
get { return this._parent; }
set
{
this._parent = value;
this.NotifyOfPropertyChange();
}
}
#endregion
} }
} }

View File

@ -54,7 +54,12 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</ApplicationDefinition> </ApplicationDefinition>
<Compile Include="Bootstrapper.cs" /> <Compile Include="Bootstrapper.cs" />
<Compile Include="Conductor.cs" />
<Compile Include="Execute.cs" />
<Compile Include="IConductor.cs" />
<Compile Include="IoC.cs" /> <Compile Include="IoC.cs" />
<Compile Include="IScreen.cs" />
<Compile Include="PropertyChangedBase.cs" />
<Compile Include="Screen.cs" /> <Compile Include="Screen.cs" />
<Compile Include="SubView.xaml.cs"> <Compile Include="SubView.xaml.cs">
<DependentUpon>SubView.xaml</DependentUpon> <DependentUpon>SubView.xaml</DependentUpon>

View File

@ -4,23 +4,24 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Data;
using System.Windows.Navigation; using System.Windows.Navigation;
namespace Stylet namespace Stylet
{ {
public interface IWindowManager public interface IWindowManager
{ {
void ShowWindow(object viewModel, IDictionary<string, object> settings = null); void ShowWindow(object viewModel);
} }
public class WindowManager : IWindowManager public class WindowManager : IWindowManager
{ {
public void ShowWindow(object viewModel, IDictionary<string, object> settings = null) public void ShowWindow(object viewModel)
{ {
this.CreateWindow(viewModel, false, settings).Show(); this.CreateWindow(viewModel, false).Show();
} }
private Window CreateWindow(object viewModel, bool isDialog, IDictionary<string, object> settings) private Window CreateWindow(object viewModel, bool isDialog)
{ {
var view = ViewLocator.LocateForModel(viewModel) as Window; var view = ViewLocator.LocateForModel(viewModel) as Window;
if (view == null) if (view == null)
@ -28,6 +29,13 @@ namespace Stylet
ViewModelBinder.Bind(view, viewModel); ViewModelBinder.Bind(view, viewModel);
var haveDisplayName = viewModel as IHaveDisplayName;
if (haveDisplayName != null)
{
var binding = new Binding("DisplayName") { Mode = BindingMode.TwoWay };
view.SetBinding(Window.TitleProperty, binding);
}
if (isDialog) if (isDialog)
{ {
var owner = this.InferOwnerOf(view); var owner = this.InferOwnerOf(view);