From 11a58f3af266ded2cf01275208aa4e3187c03357 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Wed, 5 Feb 2014 15:22:37 +0000 Subject: [PATCH] Work towards conductors --- Stylet/Bootstrapper.cs | 2 + Stylet/Conductor.cs | 110 ++++++++++++++++++++++++++++++++ Stylet/Execute.cs | 37 +++++++++++ Stylet/IConductor.cs | 26 ++++++++ Stylet/IScreen.cs | 57 +++++++++++++++++ Stylet/PropertyChangedBase.cs | 43 +++++++++++++ Stylet/Screen.cs | 116 +++++++++++++++++++++++++++++----- Stylet/Stylet.csproj | 5 ++ Stylet/WindowManager.cs | 16 +++-- 9 files changed, 393 insertions(+), 19 deletions(-) create mode 100644 Stylet/Conductor.cs create mode 100644 Stylet/Execute.cs create mode 100644 Stylet/IConductor.cs create mode 100644 Stylet/IScreen.cs create mode 100644 Stylet/PropertyChangedBase.cs diff --git a/Stylet/Bootstrapper.cs b/Stylet/Bootstrapper.cs index 9dc93a3..ef0b3de 100644 --- a/Stylet/Bootstrapper.cs +++ b/Stylet/Bootstrapper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; @@ -20,6 +21,7 @@ namespace Stylet public void Start() { this.Application = Application.Current; + Execute.SynchronizationContext = SynchronizationContext.Current; this.Application.Startup += OnStartup; this.Application.Exit += OnExit; diff --git a/Stylet/Conductor.cs b/Stylet/Conductor.cs new file mode 100644 index 0000000..2113d71 --- /dev/null +++ b/Stylet/Conductor.cs @@ -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 : Screen, IConductor, IParent 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 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> ItemsThatCanCloseAsync(IEnumerable 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(); + } + + protected virtual Task CanCloseItem(T item) + { + var itemAsGuardClose = item as IGuardClose; + if (itemAsGuardClose != null) + return itemAsGuardClose.CanCloseAsync(); + else + return Task.FromResult(true); + } + } + + public class Conductor : ConductorWithActiveItem 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 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); + } + } +} diff --git a/Stylet/Execute.cs b/Stylet/Execute.cs new file mode 100644 index 0000000..bdf95a2 --- /dev/null +++ b/Stylet/Execute.cs @@ -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(); + SynchronizationContext.Post(_ => + { + try + { + action(); + tcs.SetResult(null); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, null); + return tcs.Task; + } + } +} diff --git a/Stylet/IConductor.cs b/Stylet/IConductor.cs new file mode 100644 index 0000000..00eb47e --- /dev/null +++ b/Stylet/IConductor.cs @@ -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 + { + IEnumerable GetChildren(); + } + + public interface IHaveActiveItem + { + T ActiveItem { get; set; } + } + + public interface IConductor : IParent + { + void ActivateItem(T item); + + void DeactivateItem(T item, bool close); + } +} diff --git a/Stylet/IScreen.cs b/Stylet/IScreen.cs new file mode 100644 index 0000000..00bfbcf --- /dev/null +++ b/Stylet/IScreen.cs @@ -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 Activated; + } + + public interface IDeactivate + { + void Deactivate(bool close); + event EventHandler 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 CanCloseAsync(); + } + + public interface IScreen : IViewAware, IHaveDisplayName, IActivate, IDeactivate, IChild + { + } + + + + public class DeactivationEventArgs : EventArgs + { + public bool WasClosed; + } +} diff --git a/Stylet/PropertyChangedBase.cs b/Stylet/PropertyChangedBase.cs new file mode 100644 index 0000000..72a5ddd --- /dev/null +++ b/Stylet/PropertyChangedBase.cs @@ -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(Expression> 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)); + } + }); + } + } +} diff --git a/Stylet/Screen.cs b/Stylet/Screen.cs index c182e21..c2874b2 100644 --- a/Stylet/Screen.cs +++ b/Stylet/Screen.cs @@ -4,25 +4,16 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Controls.Primitives; namespace Stylet { - public interface IViewAware - { - object View { get; } - void AttachView(object view); - } - - public interface IScreen : IViewAware - { - } - - public class Screen : IScreen + public class Screen : PropertyChangedBase, IScreen { public virtual void TryClose(bool? dialogResult = null) { // TODO: Check for parent conductor - var viewWindow = this.View as Window; + var viewWindow = this.view as Window; if (viewWindow != null) { if (dialogResult != null) @@ -31,19 +22,99 @@ namespace Stylet 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)); } + #region IHaveDisplayName + + private string _displayName; + public string DisplayName + { + get { return this._displayName; } + set + { + this._displayName = value; + this.NotifyOfPropertyChange(); + } + } + + #endregion + + #region IActivate + + public event EventHandler 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 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 - public object View { get; private set; } + private 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)); - this.View = view; + this.view = view; var viewAsFrameworkElement = view as FrameworkElement; if (viewAsFrameworkElement != null) @@ -58,5 +129,20 @@ namespace Stylet protected virtual void OnViewLoaded() { } #endregion + + #region IChild + + private object _parent; + public object Parent + { + get { return this._parent; } + set + { + this._parent = value; + this.NotifyOfPropertyChange(); + } + } + + #endregion } } diff --git a/Stylet/Stylet.csproj b/Stylet/Stylet.csproj index e3148bb..100af68 100644 --- a/Stylet/Stylet.csproj +++ b/Stylet/Stylet.csproj @@ -54,7 +54,12 @@ Designer + + + + + SubView.xaml diff --git a/Stylet/WindowManager.cs b/Stylet/WindowManager.cs index 1fb897a..601c940 100644 --- a/Stylet/WindowManager.cs +++ b/Stylet/WindowManager.cs @@ -4,23 +4,24 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Data; using System.Windows.Navigation; namespace Stylet { public interface IWindowManager { - void ShowWindow(object viewModel, IDictionary settings = null); + void ShowWindow(object viewModel); } public class WindowManager : IWindowManager { - public void ShowWindow(object viewModel, IDictionary 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 settings) + private Window CreateWindow(object viewModel, bool isDialog) { var view = ViewLocator.LocateForModel(viewModel) as Window; if (view == null) @@ -28,6 +29,13 @@ namespace Stylet 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) { var owner = this.InferOwnerOf(view);