diff --git a/Perspex.Base/Binding.cs b/Perspex.Base/Binding.cs index 88e2d96b65..dc050b7e92 100644 --- a/Perspex.Base/Binding.cs +++ b/Perspex.Base/Binding.cs @@ -7,7 +7,7 @@ namespace Perspex { using System; - using System.Reactive.Linq; + using System.Reactive; public enum BindingMode { @@ -18,7 +18,7 @@ namespace Perspex OneWayToSource, } - public class Binding : IObservable + public class Binding : ObservableBase, IDescription { public BindingMode Mode { @@ -44,6 +44,8 @@ namespace Perspex set; } + public string Description => string.Format("{0}.{1}", this.Source?.GetType().Name, this.Property.Name); + public static Binding operator !(Binding binding) { return binding.WithMode(BindingMode.TwoWay); @@ -54,11 +56,6 @@ namespace Perspex return binding.WithMode(BindingMode.TwoWay); } - public IDisposable Subscribe(IObserver observer) - { - return this.Source.GetObservable(this.Property).Subscribe(observer); - } - public Binding WithMode(BindingMode mode) { this.Mode = mode; @@ -70,5 +67,10 @@ namespace Perspex this.Priority = priority; return this; } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return this.Source.GetObservable(this.Property).Subscribe(observer); + } } } diff --git a/Perspex.Base/IObservableDescription.cs b/Perspex.Base/IObservableDescription.cs deleted file mode 100644 index fb7fb3e415..0000000000 --- a/Perspex.Base/IObservableDescription.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2014 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex -{ - public interface IObservableDescription - { - string Description { get; } - } -} diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index fdba33905a..d6c261cee3 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -46,7 +46,6 @@ - @@ -57,6 +56,7 @@ + diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs index 564698a54b..a91d06b3f0 100644 --- a/Perspex.Base/PerspexObject.cs +++ b/Perspex.Base/PerspexObject.cs @@ -15,7 +15,7 @@ namespace Perspex using Perspex.Diagnostics; using Splat; using System.Reactive.Disposables; - + using Perspex.Reactive; /// /// The priority of a binding. @@ -306,7 +306,7 @@ namespace Perspex { Contract.Requires(property != null); - return Observable.Create(observer => + return new PerspexObservable(observer => { EventHandler handler = (s, e) => { @@ -316,21 +316,23 @@ namespace Perspex } }; + observer.OnNext(this.GetValue(property)); + this.PropertyChanged += handler; - return () => + return Disposable.Create(() => { this.PropertyChanged -= handler; - }; - }).StartWith(this.GetValue(property)); + }); + }, this.GetObservableDescription(property)); } /// /// Gets an observable for a . /// - /// - /// - /// + /// The property type. + /// The property. + /// An observable. public IObservable GetObservable(PerspexProperty property) { Contract.Requires(property != null); @@ -346,7 +348,7 @@ namespace Perspex /// public IObservable> GetObservableWithHistory(PerspexProperty property) { - return Observable.Create>(observer => + return new PerspexObservable>(observer => { EventHandler handler = (s, e) => { @@ -358,11 +360,11 @@ namespace Perspex this.PropertyChanged += handler; - return () => + return Disposable.Create(() => { this.PropertyChanged -= handler; - }; - }); + }); + }, this.GetObservableDescription(property)); } /// @@ -759,6 +761,16 @@ namespace Perspex } } + /// + /// Gets a description of a property that van be used in observables. + /// + /// The property + /// The description. + private string GetObservableDescription(PerspexProperty property) + { + return string.Format("{0}.{1}", this.GetType().Name, property.Name); + } + /// /// Raises the event. /// diff --git a/Perspex.Base/PriorityBindingEntry.cs b/Perspex.Base/PriorityBindingEntry.cs index 5e2ff6a018..1463f21a63 100644 --- a/Perspex.Base/PriorityBindingEntry.cs +++ b/Perspex.Base/PriorityBindingEntry.cs @@ -24,6 +24,8 @@ namespace Perspex this.Index = index; } + public IObservable Observable { get; private set; } + /// /// Gets a description of the binding. /// @@ -67,6 +69,7 @@ namespace Perspex throw new Exception("PriorityValue.Entry.Start() called more than once."); } + this.Observable = binding; this.Value = PerspexProperty.UnsetValue; if (binding is IDescription) diff --git a/Perspex.Base/Reactive/PerspexObservable.cs b/Perspex.Base/Reactive/PerspexObservable.cs new file mode 100644 index 0000000000..ad5b1568bb --- /dev/null +++ b/Perspex.Base/Reactive/PerspexObservable.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Reactive +{ + using System; + using System.Reactive; + using System.Reactive.Disposables; + + public sealed class PerspexObservable : ObservableBase, IDescription + { + private readonly Func, IDisposable> subscribe; + + public string Description { get; } + + public PerspexObservable(Func, IDisposable> subscribe, string description) + { + if (subscribe == null) + { + throw new ArgumentNullException("subscribe"); + } + + this.subscribe = subscribe; + this.Description = description; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + return this.subscribe(observer) ?? Disposable.Empty; + } + } +} \ No newline at end of file diff --git a/Perspex.Controls/Primitives/ScrollBar.cs b/Perspex.Controls/Primitives/ScrollBar.cs index 523edba956..4b36ff8a51 100644 --- a/Perspex.Controls/Primitives/ScrollBar.cs +++ b/Perspex.Controls/Primitives/ScrollBar.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls.Primitives using System; using System.Reactive; using System.Reactive.Linq; + using Perspex.Controls.Templates; public class ScrollBar : TemplatedControl { @@ -88,6 +89,18 @@ namespace Perspex.Controls.Primitives return base.MeasureOverride(availableSize); } + protected override void OnTemplateApplied() + { + base.OnTemplateApplied(); + + // Binding between this.Value and track.Value must be done explicitly like this rather + // than using standard bindings as it shouldn't be able to to be overridden by binding + // e.g. ScrollBar.Value. + var track = this.GetTemplateChild("track"); + track.GetObservable(ValueProperty).Subscribe(x => this.Value = x); + this.GetObservable(ValueProperty).Subscribe(x => track.Value = x); + } + private bool CalculateIsVisible() { switch (this.Visibility) diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index f1cba06010..df76a45e54 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -70,16 +70,19 @@ namespace Perspex.Diagnostics } }; - var detailsView = new ContentControl + var detailsView = new ScrollViewer { - DataTemplates = new DataTemplates + Content = new ContentControl { - new DataTemplate(CreateDetailsView), - }, - [!ContentControl.ContentProperty] = treeView[!TreeView.SelectedItemProperty] + DataTemplates = new DataTemplates + { + new DataTemplate(CreateDetailsView), + }, + [!ContentControl.ContentProperty] = treeView[!TreeView.SelectedItemProperty] .Where(x => x != null) .Cast() .Select(x => new ControlDetails(x.Control)), + }, [Grid.ColumnProperty] = 2, }; diff --git a/Perspex.Input/KeyboardDevice.cs b/Perspex.Input/KeyboardDevice.cs index 999db93ee5..e31172507d 100644 --- a/Perspex.Input/KeyboardDevice.cs +++ b/Perspex.Input/KeyboardDevice.cs @@ -48,26 +48,29 @@ namespace Perspex.Input public void SetFocusedElement(IInputElement element, bool keyboardNavigated) { - var interactive = this.FocusedElement as IInteractive; - - if (interactive != null) + if (element != this.FocusedElement) { - interactive.RaiseEvent(new RoutedEventArgs + var interactive = this.FocusedElement as IInteractive; + + if (interactive != null) { - RoutedEvent = InputElement.LostFocusEvent, - }); - } + interactive.RaiseEvent(new RoutedEventArgs + { + RoutedEvent = InputElement.LostFocusEvent, + }); + } - this.FocusedElement = element; - interactive = element as IInteractive; + this.FocusedElement = element; + interactive = element as IInteractive; - if (interactive != null) - { - interactive.RaiseEvent(new GotFocusEventArgs + if (interactive != null) { - RoutedEvent = InputElement.GotFocusEvent, - KeyboardNavigated = keyboardNavigated, - }); + interactive.RaiseEvent(new GotFocusEventArgs + { + RoutedEvent = InputElement.GotFocusEvent, + KeyboardNavigated = keyboardNavigated, + }); + } } } diff --git a/Perspex.Styling/StyleBinding.cs b/Perspex.Styling/StyleBinding.cs index 622f667017..b9e7b8a35f 100644 --- a/Perspex.Styling/StyleBinding.cs +++ b/Perspex.Styling/StyleBinding.cs @@ -7,7 +7,7 @@ namespace Perspex.Styling { using System; - using System.Reactive.Subjects; + using System.Reactive; /// /// Provides an observable for a style. @@ -17,7 +17,7 @@ namespace Perspex.Styling /// a bool. When the activator produces true, this observable will produce . /// When the activator produces false it will produce . /// - internal class StyleBinding : IObservable, IObservableDescription + internal class StyleBinding : ObservableBase, IDescription { /// /// The activator. @@ -63,7 +63,7 @@ namespace Perspex.Styling /// /// The observer. /// IDisposable object used to unsubscribe from the observable sequence. - public IDisposable Subscribe(IObserver observer) + protected override IDisposable SubscribeCore(IObserver observer) { Contract.Requires(observer != null); return this.activator.Subscribe( diff --git a/Perspex.Themes.Default/ScrollBarStyle.cs b/Perspex.Themes.Default/ScrollBarStyle.cs index 21c5ba10a7..452a7228b3 100644 --- a/Perspex.Themes.Default/ScrollBarStyle.cs +++ b/Perspex.Themes.Default/ScrollBarStyle.cs @@ -66,9 +66,9 @@ namespace Perspex.Themes.Default Background = Brushes.Silver, Content = new Track { + Id = "track", [~Track.MinimumProperty] = control[~ScrollBar.MinimumProperty], [~Track.MaximumProperty] = control[~ScrollBar.MaximumProperty], - [~~Track.ValueProperty] = control[~ScrollBar.ValueProperty], [~Track.ViewportSizeProperty] = control[~ScrollBar.ViewportSizeProperty], [~Track.OrientationProperty] = control[~ScrollBar.OrientationProperty], Thumb = new Thumb diff --git a/Perspex.Themes.Default/ScrollViewerStyle.cs b/Perspex.Themes.Default/ScrollViewerStyle.cs index 7a14e1cd65..00c65fcb69 100644 --- a/Perspex.Themes.Default/ScrollViewerStyle.cs +++ b/Perspex.Themes.Default/ScrollViewerStyle.cs @@ -31,7 +31,9 @@ namespace Perspex.Themes.Default private Control Template(ScrollViewer control) { - return new Grid + ScrollBar vert; + + var result = new Grid { ColumnDefinitions = new ColumnDefinitions { @@ -59,23 +61,27 @@ namespace Perspex.Themes.Default Id = "horizontalScrollBar", Orientation = Orientation.Horizontal, [~ScrollBar.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty], - [~~ScrollBar.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty], + //[~~ScrollBar.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty], [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty], [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty], [Grid.RowProperty] = 1, }, - new ScrollBar + (vert = new ScrollBar { Id = "verticalScrollBar", Orientation = Orientation.Vertical, [~ScrollBar.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty], - [~~ScrollBar.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty], + //[~~ScrollBar.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty], [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty], [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty], [Grid.ColumnProperty] = 1, - }, + }), }, }; + + vert[~~ScrollBar.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty]; + + return result; } } } diff --git a/Perspex.Themes.Default/TextBoxStyle.cs b/Perspex.Themes.Default/TextBoxStyle.cs index c9ff7f6605..8b341f616d 100644 --- a/Perspex.Themes.Default/TextBoxStyle.cs +++ b/Perspex.Themes.Default/TextBoxStyle.cs @@ -60,7 +60,7 @@ namespace Perspex.Themes.Default [~TextPresenter.CaretIndexProperty] = control[~TextBox.CaretIndexProperty], [~TextPresenter.SelectionStartProperty] = control[~TextBox.SelectionStartProperty], [~TextPresenter.SelectionEndProperty] = control[~TextBox.SelectionEndProperty], - [~~TextPresenter.TextProperty] = control[~~TextBox.TextProperty], + [~TextPresenter.TextProperty] = control[~TextBox.TextProperty], [~TextPresenter.TextWrappingProperty] = control[~TextBox.TextWrappingProperty], } }