From be8079137df3d93fee4c44cd86bf5054c7b0cff8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Feb 2016 19:33:17 +0100 Subject: [PATCH 01/31] Added Raised observable to RoutedEvent. Similar to PerspexProperty.Changed observable. Make AddClassHandler methods use this. --- src/Perspex.Interactivity/Interactive.cs | 2 +- src/Perspex.Interactivity/RoutedEvent.cs | 70 +++++++++++------------- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/Perspex.Interactivity/Interactive.cs b/src/Perspex.Interactivity/Interactive.cs index a6bfd0d94c..956b2c2722 100644 --- a/src/Perspex.Interactivity/Interactive.cs +++ b/src/Perspex.Interactivity/Interactive.cs @@ -175,7 +175,7 @@ namespace Perspex.Interactivity { Contract.Requires(e != null); - e.RoutedEvent.InvokeClassHandlers(this, e); + e.RoutedEvent.InvokeRaised(this, e); List subscriptions; diff --git a/src/Perspex.Interactivity/RoutedEvent.cs b/src/Perspex.Interactivity/RoutedEvent.cs index 3fa8f9aaf5..3040c867a4 100644 --- a/src/Perspex.Interactivity/RoutedEvent.cs +++ b/src/Perspex.Interactivity/RoutedEvent.cs @@ -2,8 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Linq.Expressions; +using System.Reactive.Subjects; using System.Reflection; using System.Runtime.ExceptionServices; @@ -19,7 +18,7 @@ namespace Perspex.Interactivity public class RoutedEvent { - private readonly List _subscriptions = new List(); + private Subject> _raised = new Subject>(); public RoutedEvent( string name, @@ -62,6 +61,8 @@ namespace Perspex.Interactivity private set; } + public IObservable> Raised => _raised; + public static RoutedEvent Register( string name, RoutingStrategies routingStrategy) @@ -83,27 +84,24 @@ namespace Perspex.Interactivity return new RoutedEvent(name, routingStrategy, ownerType); } - public void AddClassHandler(Type type, EventHandler handler, RoutingStrategies routes) + public IDisposable AddClassHandler( + Type targetType, + EventHandler handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false) { - _subscriptions.Add(new ClassEventSubscription + return Raised.Subscribe(args => { - TargetType = type, - Handler = handler, - Routes = routes, - }); - } + var sender = args.Item1; + var e = args.Item2; - internal void InvokeClassHandlers(object sender, RoutedEventArgs e) - { - foreach (var sub in _subscriptions) - { - if (sub.TargetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) && - ((e.Route == RoutingStrategies.Direct) || (e.Route & sub.Routes) != 0) && - (!e.Handled || sub.AlsoIfHandled)) + if (targetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) && + ((e.Route == RoutingStrategies.Direct) || (e.Route & routes) != 0) && + (!e.Handled || handledEventsToo)) { try { - sub.Handler.DynamicInvoke(sender, e); + handler.DynamicInvoke(sender, e); } catch (TargetInvocationException ex) { @@ -111,12 +109,12 @@ namespace Perspex.Interactivity ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } } - } + }); } - private class ClassEventSubscription : EventSubscription + internal void InvokeRaised(object sender, RoutedEventArgs e) { - public Type TargetType { get; set; } + _raised.OnNext(Tuple.Create(sender, e)); } } @@ -130,26 +128,24 @@ namespace Perspex.Interactivity Contract.Requires(ownerType != null); } - public void AddClassHandler( + public IDisposable AddClassHandler( Func> handler, - RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble) - where TTarget : class + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false) + where TTarget : class, IInteractive { - AddClassHandler(typeof(TTarget), (s, e) => ClassHandlerAdapter(s, e, handler), routes); - } + EventHandler adapter = (sender, e) => + { + var target = sender as TTarget; + var args = e as TEventArgs; - private static void ClassHandlerAdapter( - object sender, - RoutedEventArgs e, - Func> handler) where TTarget : class - { - var target = sender as TTarget; - var args = e as TEventArgs; + if (target != null && args != null) + { + handler(target)(args); + } + }; - if (target != null && args != null) - { - handler(target)(args); - } + return AddClassHandler(typeof(TTarget), adapter, routes, handledEventsToo); } } } From 719a5519b50c87737de9960402da6cd053f42946 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 13 Feb 2016 13:01:50 +0100 Subject: [PATCH 02/31] Fix exception in HtmlControl. --- src/Perspex.HtmlRenderer/HtmlControl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index 97d7c517e5..e5e8864933 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -549,7 +549,11 @@ namespace Perspex.Controls.Html htmlContainer.SetHtml((string) e.NewValue, control._baseCssData); control.InvalidateMeasure(); control.InvalidateVisual(); - control.InvokeMouseMove(); + + if (control.VisualRoot != null) + { + control.InvokeMouseMove(); + } } } } From 8eb4f30c6d97bd0b9dcff21a6aaa3c2db9b58305 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 13 Feb 2016 17:39:51 +0100 Subject: [PATCH 03/31] Added Tapped and DoubleTapped gestures. --- src/Perspex.Input/Gestures.cs | 59 +++++++++++++ src/Perspex.Input/InputElement.cs | 28 +++++++ src/Perspex.Input/Perspex.Input.csproj | 1 + src/Perspex.Input/PointerEventArgs.cs | 31 +++++++ src/Perspex.Interactivity/Interactive.cs | 3 + src/Perspex.Interactivity/RoutedEvent.cs | 7 ++ .../GestureTests.cs | 82 +++++++++++++++++++ .../Perspex.Interactivity.UnitTests.csproj | 13 +++ 8 files changed, 224 insertions(+) create mode 100644 src/Perspex.Input/Gestures.cs create mode 100644 tests/Perspex.Interactivity.UnitTests/GestureTests.cs diff --git a/src/Perspex.Input/Gestures.cs b/src/Perspex.Input/Gestures.cs new file mode 100644 index 0000000000..48e9d87f86 --- /dev/null +++ b/src/Perspex.Input/Gestures.cs @@ -0,0 +1,59 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Perspex.Interactivity; + +namespace Perspex.Input +{ + public static class Gestures + { + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( + "Tapped", + RoutingStrategies.Bubble, + typeof(Gestures)); + + public static readonly RoutedEvent DoubleTappedEvent = RoutedEvent.Register( + "DoubleTapped", + RoutingStrategies.Bubble, + typeof(Gestures)); + + private static IInteractive s_lastPress; + + static Gestures() + { + InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); + InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); + } + + private static void PointerPressed(RoutedEventArgs ev) + { + if (ev.Route == RoutingStrategies.Bubble) + { + var e = (PointerPressedEventArgs)ev; + + if (e.ClickCount <= 1) + { + s_lastPress = e.Source; + } + else if (e.ClickCount == 2 && s_lastPress == e.Source) + { + e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent)); + } + } + } + + private static void PointerReleased(RoutedEventArgs ev) + { + if (ev.Route == RoutingStrategies.Bubble) + { + var e = (PointerReleasedEventArgs)ev; + + if (s_lastPress == e.Source) + { + s_lastPress.RaiseEvent(new RoutedEventArgs(TappedEvent)); + } + } + } + } +} diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs index dab429cbdf..00e53df646 100644 --- a/src/Perspex.Input/InputElement.cs +++ b/src/Perspex.Input/InputElement.cs @@ -137,6 +137,16 @@ namespace Perspex.Input "PointerWheelChanged", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; + + /// + /// Defines the event. + /// + public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; + private bool _isFocused; private bool _isPointerOver; @@ -259,6 +269,24 @@ namespace Perspex.Input remove { RemoveHandler(PointerWheelChangedEvent, value); } } + /// + /// Occurs when a tap gesture occurs on the control. + /// + public event EventHandler Tapped + { + add { AddHandler(TappedEvent, value); } + remove { RemoveHandler(TappedEvent, value); } + } + + /// + /// Occurs when a double-tap gesture occurs on the control. + /// + public event EventHandler DoubleTapped + { + add { AddHandler(DoubleTappedEvent, value); } + remove { RemoveHandler(DoubleTappedEvent, value); } + } + /// /// Gets or sets a value indicating whether the control can receive focus. /// diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index 264eda7473..80e1566f1c 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Perspex.Input/PointerEventArgs.cs b/src/Perspex.Input/PointerEventArgs.cs index fab4c2a9c6..25fbf7f26d 100644 --- a/src/Perspex.Input/PointerEventArgs.cs +++ b/src/Perspex.Input/PointerEventArgs.cs @@ -8,6 +8,17 @@ namespace Perspex.Input { public class PointerEventArgs : RoutedEventArgs { + public PointerEventArgs() + { + + } + + public PointerEventArgs(RoutedEvent routedEvent) + : base(routedEvent) + { + + } + public IPointerDevice Device { get; set; } public InputModifiers InputModifiers { get; set; } @@ -28,12 +39,32 @@ namespace Perspex.Input public class PointerPressedEventArgs : PointerEventArgs { + public PointerPressedEventArgs() + : base(InputElement.PointerPressedEvent) + { + } + + public PointerPressedEventArgs(RoutedEvent routedEvent) + : base(routedEvent) + { + } + public int ClickCount { get; set; } public MouseButton MouseButton { get; set; } } public class PointerReleasedEventArgs : PointerEventArgs { + public PointerReleasedEventArgs() + : base(InputElement.PointerReleasedEvent) + { + } + + public PointerReleasedEventArgs(RoutedEvent routedEvent) + : base(routedEvent) + { + } + public MouseButton MouseButton { get; set; } } } diff --git a/src/Perspex.Interactivity/Interactive.cs b/src/Perspex.Interactivity/Interactive.cs index 956b2c2722..c348a3ed83 100644 --- a/src/Perspex.Interactivity/Interactive.cs +++ b/src/Perspex.Interactivity/Interactive.cs @@ -122,16 +122,19 @@ namespace Perspex.Interactivity { e.Route = RoutingStrategies.Direct; RaiseEventImpl(e); + e.RoutedEvent.InvokeRouteFinished(e); } if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Tunnel) != 0) { TunnelEvent(e); + e.RoutedEvent.InvokeRouteFinished(e); } if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Bubble) != 0) { BubbleEvent(e); + e.RoutedEvent.InvokeRouteFinished(e); } } diff --git a/src/Perspex.Interactivity/RoutedEvent.cs b/src/Perspex.Interactivity/RoutedEvent.cs index 3040c867a4..9b5dee61f5 100644 --- a/src/Perspex.Interactivity/RoutedEvent.cs +++ b/src/Perspex.Interactivity/RoutedEvent.cs @@ -19,6 +19,7 @@ namespace Perspex.Interactivity public class RoutedEvent { private Subject> _raised = new Subject>(); + private Subject _routeFinished = new Subject(); public RoutedEvent( string name, @@ -62,6 +63,7 @@ namespace Perspex.Interactivity } public IObservable> Raised => _raised; + public IObservable RouteFinished => _routeFinished; public static RoutedEvent Register( string name, @@ -116,6 +118,11 @@ namespace Perspex.Interactivity { _raised.OnNext(Tuple.Create(sender, e)); } + + internal void InvokeRouteFinished(RoutedEventArgs e) + { + _routeFinished.OnNext(e); + } } public class RoutedEvent : RoutedEvent diff --git a/tests/Perspex.Interactivity.UnitTests/GestureTests.cs b/tests/Perspex.Interactivity.UnitTests/GestureTests.cs new file mode 100644 index 0000000000..9712ea480e --- /dev/null +++ b/tests/Perspex.Interactivity.UnitTests/GestureTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Collections.Generic; +using Perspex.Controls; +using Perspex.Input; +using Xunit; + +namespace Perspex.Interactivity.UnitTests +{ + public class GestureTests + { + [Fact] + public void Tapped_Should_Follow_Pointer_Pressed_Released() + { + Border border; + var decorator = new Decorator + { + Child = border = new Border() + }; + var result = new List(); + + decorator.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("dp")); + decorator.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("dr")); + decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt")); + border.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("bp")); + border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br")); + border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); + + border.RaiseEvent(new PointerPressedEventArgs()); + border.RaiseEvent(new PointerReleasedEventArgs()); + + Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result); + } + + [Fact] + public void Tapped_Should_Be_Raised_Even_When_PointerPressed_Handled() + { + Border border; + var decorator = new Decorator + { + Child = border = new Border() + }; + var result = new List(); + + border.AddHandler(Border.PointerPressedEvent, (s, e) => e.Handled = true); + decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt")); + border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); + + border.RaiseEvent(new PointerPressedEventArgs()); + border.RaiseEvent(new PointerReleasedEventArgs()); + + Assert.Equal(new[] { "bt", "dt" }, result); + } + + [Fact] + public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() + { + Border border; + var decorator = new Decorator + { + Child = border = new Border() + }; + var result = new List(); + + decorator.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("dp")); + decorator.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("dr")); + decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt")); + decorator.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("ddt")); + border.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("bp")); + border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br")); + border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); + border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); + + border.RaiseEvent(new PointerPressedEventArgs()); + border.RaiseEvent(new PointerReleasedEventArgs()); + border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 }); + + Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result); + } + } +} diff --git a/tests/Perspex.Interactivity.UnitTests/Perspex.Interactivity.UnitTests.csproj b/tests/Perspex.Interactivity.UnitTests/Perspex.Interactivity.UnitTests.csproj index 69137a9d35..9532487e13 100644 --- a/tests/Perspex.Interactivity.UnitTests/Perspex.Interactivity.UnitTests.csproj +++ b/tests/Perspex.Interactivity.UnitTests/Perspex.Interactivity.UnitTests.csproj @@ -59,6 +59,7 @@ + @@ -74,6 +75,14 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + {6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B} Perspex.Interactivity @@ -86,6 +95,10 @@ {EB582467-6ABB-43A1-B052-E981BA910E3A} Perspex.SceneGraph + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + From ec9da47d6ac1258c6f21c3d786465c857ca6b6d7 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 14:27:26 +0000 Subject: [PATCH 04/31] supporting null or string.empty to trigger property notifications. --- .../Data/Plugins/InpcPropertyAccessorPlugin.cs | 2 +- .../Data/ExpressionObserverTests_Property.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs index 13970a9054..c52d1a3a26 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs @@ -105,7 +105,7 @@ namespace Perspex.Markup.Data.Plugins private void PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == _property.Name) + if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { _changed(Value); } diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index fe9336ebe6..7730c18303 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -101,7 +101,10 @@ namespace Perspex.Markup.UnitTests.Data var sub = target.Subscribe(x => result.Add(x)); data.Foo = "bar"; - Assert.Equal(new[] { "foo", "bar" }, result); + data.RaisePropertyChanged(string.Empty); + data.RaisePropertyChanged(null); + + Assert.Equal(new[] { "foo", "bar", "bar", "bar" }, result); sub.Dispose(); @@ -333,6 +336,11 @@ namespace Perspex.Markup.UnitTests.Data RaisePropertyChanged(nameof(Next)); } } + + public void RaisePropertyChanged(string propertyName) + { + base.RaisePropertyChanged(propertyName); + } } private class Class2 : NotifyingBase, INext From 8c7412232cdda159d0ace29903985e8005ceb8da Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 14:48:41 +0000 Subject: [PATCH 05/31] Seperated testing of null and empty string triggering PropertyChangedEvent. --- .../Data/ExpressionObserverTests_Property.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index 7730c18303..ebf98a363e 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -101,10 +101,35 @@ namespace Perspex.Markup.UnitTests.Data var sub = target.Subscribe(x => result.Add(x)); data.Foo = "bar"; + Assert.Equal(new[] { "foo", "bar" }, result); + + sub.Dispose(); + + Assert.Equal(0, data.SubscriptionCount); + } + + [Fact] + public void Should_Trigger_PropertyChanged_On_Null_Or_Empty_String() + { + var data = new Class1 { Bar = "foo" }; + var target = new ExpressionObserver(data, "Bar"); + var result = new List(); + + var sub = target.Subscribe(x => result.Add(x)); + + Assert.Equal(new[] { "foo" }, result); + + data.Bar = "bar"; + + Assert.Equal(new[] { "foo" }, result); + data.RaisePropertyChanged(string.Empty); + + Assert.Equal(new[] { "foo", "bar" }, result); + data.RaisePropertyChanged(null); - Assert.Equal(new[] { "foo", "bar", "bar", "bar" }, result); + Assert.Equal(new[] { "foo", "bar", "bar" }, result); sub.Dispose(); @@ -327,6 +352,13 @@ namespace Perspex.Markup.UnitTests.Data } } + private string _bar; + public string Bar + { + get { return _bar; } + set { _bar = value; } + } + public INext Next { get { return _next; } From 202f5839b2c2938bed765be5fb9d1f3c502779ef Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 15:56:03 +0000 Subject: [PATCH 06/31] fixed textblock so fontstyle, font weight, etc triggers re-render. --- src/Perspex.Controls/TextBlock.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index d9e1c5520d..29a760b11f 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -102,6 +102,9 @@ namespace Perspex.Controls static TextBlock() { AffectsRender(ForegroundProperty); + AffectsRender(FontWeightProperty); + AffectsRender(FontSizeProperty); + AffectsRender(FontStyleProperty); } /// @@ -113,7 +116,8 @@ namespace Perspex.Controls this.GetObservable(TextProperty).Select(_ => Unit.Default), this.GetObservable(TextAlignmentProperty).Select(_ => Unit.Default), this.GetObservable(FontSizeProperty).Select(_ => Unit.Default), - this.GetObservable(FontStyleProperty).Select(_ => Unit.Default)) + this.GetObservable(FontStyleProperty).Select(_ => Unit.Default), + this.GetObservable(FontWeightProperty).Select(_=>Unit.Default)) .Subscribe(_ => { InvalidateFormattedText(); From f6c7513490426e93fccb7de85a38e605ca5fa314 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2016 20:53:51 +0100 Subject: [PATCH 07/31] Added CheckBox page to ControlCatalog. --- samples/ControlCatalog/ControlCatalog.csproj | 6 ++++++ samples/ControlCatalog/MainWindow.paml | 1 + samples/ControlCatalog/Pages/CheckBoxPage.paml | 15 +++++++++++++++ .../ControlCatalog/Pages/CheckBoxPage.paml.cs | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 samples/ControlCatalog/Pages/CheckBoxPage.paml create mode 100644 samples/ControlCatalog/Pages/CheckBoxPage.paml.cs diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index ff3e6df64f..afc456584a 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -60,6 +60,9 @@ MainWindow.paml + + CheckBoxPage.paml + BorderPage.paml @@ -102,6 +105,9 @@ + + Designer + diff --git a/samples/ControlCatalog/MainWindow.paml b/samples/ControlCatalog/MainWindow.paml index c692b34fc6..ac900e7516 100644 --- a/samples/ControlCatalog/MainWindow.paml +++ b/samples/ControlCatalog/MainWindow.paml @@ -9,6 +9,7 @@ + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.paml b/samples/ControlCatalog/Pages/CheckBoxPage.paml new file mode 100644 index 0000000000..0f7003efb4 --- /dev/null +++ b/samples/ControlCatalog/Pages/CheckBoxPage.paml @@ -0,0 +1,15 @@ + + + CheckBox + A check box control + + + Unchecked + Checked + Disabled + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.paml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.paml.cs new file mode 100644 index 0000000000..38ca6d3c3d --- /dev/null +++ b/samples/ControlCatalog/Pages/CheckBoxPage.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class CheckBoxPage : UserControl + { + public CheckBoxPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} From 8375a155e9bba11c1d2cd542eea3948f1dadeaf7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2016 20:57:22 +0100 Subject: [PATCH 08/31] Added disabled state to CheckBox. --- .../ControlCatalog/Pages/CheckBoxPage.paml | 2 +- src/Perspex.Themes.Default/CheckBox.paml | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.paml b/samples/ControlCatalog/Pages/CheckBoxPage.paml index 0f7003efb4..bb1d991980 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.paml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.paml @@ -9,7 +9,7 @@ Gap="16"> Unchecked Checked - Disabled + Disabled \ No newline at end of file diff --git a/src/Perspex.Themes.Default/CheckBox.paml b/src/Perspex.Themes.Default/CheckBox.paml index b45210cfdb..cea4b2d114 100644 --- a/src/Perspex.Themes.Default/CheckBox.paml +++ b/src/Perspex.Themes.Default/CheckBox.paml @@ -5,19 +5,21 @@ - - + VerticalAlignment="Center"> + + + \ No newline at end of file From 9a3f08ce9d7efb8502ef56fee0a7a4c2e247bd52 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 20:03:55 +0000 Subject: [PATCH 09/31] Basic implementation of BrowseFolderDialog and Win32 Implementation. --- src/Perspex.Controls/Platform/ISystemDialogImpl.cs | 2 ++ src/Perspex.Controls/SystemDialog.cs | 9 +++++++-- src/Windows/Perspex.Win32/Perspex.Win32.csproj | 4 ++++ src/Windows/Perspex.Win32/SystemDialogImpl.cs | 13 +++++++++++++ src/Windows/Perspex.Win32/packages.config | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs index d3af982f95..f45eec8f95 100644 --- a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs +++ b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs @@ -18,5 +18,7 @@ namespace Perspex.Controls.Platform /// The parent window. /// A task returning the selected filenames. Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); + + Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent); } } diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index 6eae1e9e9d..b61833390b 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -12,10 +12,9 @@ namespace Perspex.Controls public string InitialDirectory { get; set; } } - public class SaveFileDialog : FileDialog { - public string DefaultExtension { get; set; } + public string DefaultExtension { get; set; } public async Task ShowAsync(Window window = null) => @@ -31,6 +30,12 @@ namespace Perspex.Controls => PerspexLocator.Current.GetService().ShowFileDialogAsync(this, window?.PlatformImpl); } + public class OpenFolderDialog : SystemDialog + { + public Task ShowAsync(Window window = null) + => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); + } + public abstract class SystemDialog { public string Title { get; set; } diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 0cd573d7b8..d62e03783c 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,6 +38,10 @@ CS1591 + + ..\..\..\packages\Ookii.Dialogs.1.0\lib\net35\Ookii.Dialogs.Wpf.dll + True + diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index 69f394c75c..7ed1c83d34 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,6 +10,7 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; +using Ookii.Dialogs.Wpf; namespace Perspex.Win32 { @@ -128,5 +129,17 @@ namespace Perspex.Win32 return files.Select(f => Path.Combine(dir, f)).ToArray(); }); } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog(); + + return Task.Factory.StartNew(() => + { + fbd.ShowDialog(); + + return fbd.SelectedPath; + }); + } } } diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 571e4fe023..7ea589649f 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,5 +1,6 @@  + From 90f4672be0cbf12357fb3af07e02d4e3e45f2645 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 20:58:00 +0000 Subject: [PATCH 10/31] removed dependency from ookii dialogs and implemented using Windows Code pack. --- src/Perspex.Controls/SystemDialog.cs | 10 ++++-- .../Perspex.Win32/Perspex.Win32.csproj | 12 +++++-- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 31 +++++++++++++++---- src/Windows/Perspex.Win32/packages.config | 3 +- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index b61833390b..2c4ec92147 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -5,10 +5,14 @@ using Perspex.Controls.Platform; namespace Perspex.Controls { - public abstract class FileDialog : SystemDialog + public abstract class FileDialog : FileSystemDialog { public List Filters { get; set; } = new List(); - public string InitialFileName { get; set; } + public string InitialFileName { get; set; } + } + + public abstract class FileSystemDialog : SystemDialog + { public string InitialDirectory { get; set; } } @@ -30,7 +34,7 @@ namespace Perspex.Controls => PerspexLocator.Current.GetService().ShowFileDialogAsync(this, window?.PlatformImpl); } - public class OpenFolderDialog : SystemDialog + public class OpenFolderDialog : FileSystemDialog { public Task ShowAsync(Window window = null) => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index d62e03783c..36b5cf098b 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,8 +38,16 @@ CS1591 - - ..\..\..\packages\Ookii.Dialogs.1.0\lib\net35\Ookii.Dialogs.Wpf.dll + + ..\..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.2\lib\Microsoft.WindowsAPICodePack.dll + True + + + ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll + True + + + ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll True diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index 7ed1c83d34..d870515de8 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,7 +10,7 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; -using Ookii.Dialogs.Wpf; +using Microsoft.WindowsAPICodePack.Dialogs; namespace Perspex.Win32 { @@ -131,14 +131,33 @@ namespace Perspex.Win32 } public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - { - VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog(); - + { return Task.Factory.StartNew(() => { - fbd.ShowDialog(); + var dlg = new CommonOpenFileDialog(); + dlg.Title = dialog.Title; + dlg.IsFolderPicker = true; + dlg.InitialDirectory = dialog.InitialDirectory; + + dlg.AddToMostRecentlyUsedList = false; + dlg.AllowNonFileSystemItems = false; + + dlg.EnsureFileExists = true; + dlg.EnsurePathExists = true; + dlg.EnsureReadOnly = false; + dlg.EnsureValidNames = true; + dlg.Multiselect = false; + dlg.ShowPlacesList = true; + dlg.DefaultFileName = ""; + + string result = string.Empty; + + if (dlg.ShowDialog() == CommonFileDialogResult.Ok) + { + result = dlg.FileName; + } - return fbd.SelectedPath; + return result; }); } } diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 7ea589649f..4f6923eb53 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,6 +1,7 @@  - + + From b87c4a8d662e15c035ef181f280b9edb5138dd9c Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 21:35:35 +0000 Subject: [PATCH 11/31] Win32 implementation of folder browse dialog. --- src/Perspex.Controls/SystemDialog.cs | 2 + .../Perspex.Win32/Interop/UnmanagedMethods.cs | 153 +++++++++++++++++- .../Perspex.Win32/Perspex.Win32.csproj | 12 -- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 70 +++++--- src/Windows/Perspex.Win32/packages.config | 2 - 5 files changed, 203 insertions(+), 36 deletions(-) diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index 2c4ec92147..bac791dd36 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -36,6 +36,8 @@ namespace Perspex.Controls public class OpenFolderDialog : FileSystemDialog { + public string DefaultDirectory { get; set; } + public Task ShowAsync(Window window = null) => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); } diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 5bb2038813..2a0a000226 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -703,6 +704,131 @@ namespace Perspex.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + #region Constants + + public const uint FOS_PICKFOLDERS = 0x00000020; + public const uint FOS_FORCEFILESYSTEM = 0x00000040; + public const uint FOS_NOVALIDATE = 0x00000100; + public const uint FOS_NOTESTFILECREATE = 0x00010000; + public const uint FOS_DONTADDTORECENT = 0x02000000; + + public const uint S_OK = 0x0000; + + public const uint SIGDN_FILESYSPATH = 0x80058000; + + #endregion + + + #region COM + + [ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] + internal class FileOpenDialogRCW { } + + + [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IFileDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Close([MarshalAs(UnmanagedType.Error)] uint hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint ClearClientData(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } + + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } + + #endregion + + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + [DllImport("user32.dll", SetLastError = true)] public static extern bool OpenClipboard(IntPtr hWndOwner); @@ -873,6 +999,31 @@ namespace Perspex.Win32.Interop } + [Flags] + internal enum FOS : uint + { + FOS_OVERWRITEPROMPT = 0x00000002, + FOS_STRICTFILETYPES = 0x00000004, + FOS_NOCHANGEDIR = 0x00000008, + FOS_PICKFOLDERS = 0x00000020, + FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. + FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. + FOS_NOVALIDATE = 0x00000100, + FOS_ALLOWMULTISELECT = 0x00000200, + FOS_PATHMUSTEXIST = 0x00000800, + FOS_FILEMUSTEXIST = 0x00001000, + FOS_CREATEPROMPT = 0x00002000, + FOS_SHAREAWARE = 0x00004000, + FOS_NOREADONLYRETURN = 0x00008000, + FOS_NOTESTFILECREATE = 0x00010000, + FOS_HIDEMRUPLACES = 0x00020000, + FOS_HIDEPINNEDPLACES = 0x00040000, + FOS_NODEREFERENCELINKS = 0x00100000, + FOS_DONTADDTORECENT = 0x02000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000 + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct OpenFileName { @@ -899,6 +1050,6 @@ namespace Perspex.Win32.Interop public IntPtr reservedPtr; public int reservedInt; public int flagsEx; - } + } } } diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 36b5cf098b..0cd573d7b8 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,18 +38,6 @@ CS1591 - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.2\lib\Microsoft.WindowsAPICodePack.dll - True - - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll - True - - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll - True - diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index d870515de8..d8473f75c2 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,10 +10,10 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; -using Microsoft.WindowsAPICodePack.Dialogs; namespace Perspex.Win32 { + class SystemDialogImpl : ISystemDialogImpl { public unsafe Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) @@ -45,16 +45,15 @@ namespace Perspex.Win32 dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length); string userSelectedExt = null; - + fixed (char* pFileBuffer = fileBuffer) fixed (char* pFilterBuffer = filterBuffer) fixed (char* pDefExt = defExt) fixed (char* pInitDir = dialog.InitialDirectory) fixed (char* pTitle = dialog.Title) { - var ofn = new UnmanagedMethods.OpenFileName() - { + { hwndOwner = hWnd, hInstance = IntPtr.Zero, lCustData = IntPtr.Zero, @@ -134,27 +133,56 @@ namespace Perspex.Win32 { return Task.Factory.StartNew(() => { - var dlg = new CommonOpenFileDialog(); - dlg.Title = dialog.Title; - dlg.IsFolderPicker = true; - dlg.InitialDirectory = dialog.InitialDirectory; + string result = string.Empty; - dlg.AddToMostRecentlyUsedList = false; - dlg.AllowNonFileSystemItems = false; - - dlg.EnsureFileExists = true; - dlg.EnsurePathExists = true; - dlg.EnsureReadOnly = false; - dlg.EnsureValidNames = true; - dlg.Multiselect = false; - dlg.ShowPlacesList = true; - dlg.DefaultFileName = ""; + var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; + var frm = (UnmanagedMethods.IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW()); + uint options; + frm.GetOptions(out options); + options |= UnmanagedMethods.FOS_PICKFOLDERS | UnmanagedMethods.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS_NOVALIDATE | UnmanagedMethods.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS_DONTADDTORECENT; + frm.SetOptions(options); - string result = string.Empty; + if (dialog.InitialDirectory != null) + { + UnmanagedMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + { + frm.SetFolder(directoryShellItem); + } + } - if (dlg.ShowDialog() == CommonFileDialogResult.Ok) + if (dialog.DefaultDirectory != null) { - result = dlg.FileName; + UnmanagedMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + { + frm.SetDefaultFolder(directoryShellItem); + } + } + + if (frm.Show(hWnd) == UnmanagedMethods.S_OK) + { + UnmanagedMethods.IShellItem shellItem; + if (frm.GetResult(out shellItem) == UnmanagedMethods.S_OK) + { + IntPtr pszString; + if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == UnmanagedMethods.S_OK) + { + if (pszString != IntPtr.Zero) + { + try + { + result = Marshal.PtrToStringAuto(pszString); + } + finally + { + Marshal.FreeCoTaskMem(pszString); + } + } + } + } } return result; diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 4f6923eb53..571e4fe023 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,7 +1,5 @@  - - From 2577855b098e6d2cc6cb1ea03f3ab1f745bfd7b5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2016 22:54:48 +0100 Subject: [PATCH 12/31] Correctly resolve attached properties. Design.Width/Height was getting written to Width/Height. Should fix #428. --- .../Context/PropertyAccessor.cs | 29 ++++--------------- src/Perspex.Base/PerspexPropertyRegistry.cs | 6 ++-- src/Perspex.Controls/ScrollViewer.cs | 2 +- src/Perspex.Controls/TextBox.cs | 10 +++++++ src/Perspex.Themes.Default/ContextMenu.paml | 4 +-- .../PerspexPropertyRegistryTests.cs | 18 +++++++++++- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs b/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs index 0634e848ac..944b3301ec 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs @@ -43,36 +43,19 @@ namespace Perspex.Markup.Xaml.Context private static PerspexProperty FindPerspexProperty(object instance, MutableMember member) { - var target = instance as IPerspexObject; - var attached = member as PerspexAttachableXamlMember; + var target = instance as PerspexObject; if (target == null) { return null; } - PerspexProperty property; - string propertyName; - - if (attached == null) - { - propertyName = member.Name; - property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)target) - .FirstOrDefault(x => x.Name == propertyName); - } - else - { - // Ensure the OwnerType's static ctor has been run. - RuntimeHelpers.RunClassConstructor(attached.DeclaringType.UnderlyingType.TypeHandle); - - propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + member.Name; - - property = PerspexPropertyRegistry.Instance.GetRegistered((PerspexObject)target) - .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType) - .FirstOrDefault(x => x.Name == member.Name); - } + var attached = member as PerspexAttachableXamlMember; + var propertyName = attached == null ? + member.Name : + member.DeclaringType.Name + "." + member.Name; - return property; + return PerspexPropertyRegistry.Instance.FindRegistered(target, propertyName); } private static void SetBinding( diff --git a/src/Perspex.Base/PerspexPropertyRegistry.cs b/src/Perspex.Base/PerspexPropertyRegistry.cs index 9e24fd7a2f..871837331a 100644 --- a/src/Perspex.Base/PerspexPropertyRegistry.cs +++ b/src/Perspex.Base/PerspexPropertyRegistry.cs @@ -92,7 +92,7 @@ namespace Perspex } /// - /// Finds registered on a type. + /// Finds a registered on a type. /// /// The type. /// The property. @@ -155,6 +155,7 @@ namespace Perspex Contract.Requires(name != null); var parts = name.Split('.'); + var types = GetImplementedTypes(type).ToList(); if (parts.Length < 1 || parts.Length > 2) { @@ -167,11 +168,10 @@ namespace Perspex if (parts.Length == 1) { propertyName = parts[0]; + results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name)); } else { - var types = GetImplementedTypes(type); - if (!types.Contains(parts[0])) { results = results.Where(x => x.OwnerType.Name == parts[0]); diff --git a/src/Perspex.Controls/ScrollViewer.cs b/src/Perspex.Controls/ScrollViewer.cs index 99bf9e8933..8b5920b8e2 100644 --- a/src/Perspex.Controls/ScrollViewer.cs +++ b/src/Perspex.Controls/ScrollViewer.cs @@ -85,7 +85,7 @@ namespace Perspex.Controls /// in the control's template. /// public static readonly AttachedProperty HorizontalScrollBarVisibilityProperty = - PerspexProperty.RegisterAttached( + PerspexProperty.RegisterAttached( nameof(HorizontalScrollBarVisibility), ScrollBarVisibility.Auto); diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 8a0aece63d..72f32aff1c 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -26,6 +26,16 @@ namespace Perspex.Controls public static readonly StyledProperty AcceptsTabProperty = PerspexProperty.Register("AcceptsTab"); + // TODO: Remove these when "(attached.property)" syntax is supported in bindings. They are + // bound to in the TextBox template, but ideally they should just be bound to the attached + // property. + public static readonly StyledProperty CanScrollHorizontally = + ScrollViewer.CanScrollHorizontallyProperty.AddOwner(); + public static readonly StyledProperty HorizontalScrollBarVisibilityProperty = + ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(); + public static readonly StyledProperty VerticalScrollBarVisibilityProperty = + ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(); + // TODO: Should CaretIndex, SelectionStart/End and Text be direct properties? public static readonly StyledProperty CaretIndexProperty = PerspexProperty.Register("CaretIndex", validate: ValidateCaretIndex); diff --git a/src/Perspex.Themes.Default/ContextMenu.paml b/src/Perspex.Themes.Default/ContextMenu.paml index ed491ebe24..07cb3bfe91 100644 --- a/src/Perspex.Themes.Default/ContextMenu.paml +++ b/src/Perspex.Themes.Default/ContextMenu.paml @@ -2,8 +2,8 @@ - - + + AttachedProperty = PerspexProperty.RegisterAttached("Attached"); From 0f0800958f0c2984bd2b4e60d365cf18c1798dce Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 21:56:36 +0000 Subject: [PATCH 13/31] tidied UnmanagedMethods and put Interfaces out side of class scope. --- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 230 +++++++++--------- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 20 +- 2 files changed, 120 insertions(+), 130 deletions(-) diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 2a0a000226..4d87d0e033 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -703,129 +703,11 @@ namespace Perspex.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } - - #region Constants - - public const uint FOS_PICKFOLDERS = 0x00000020; - public const uint FOS_FORCEFILESYSTEM = 0x00000040; - public const uint FOS_NOVALIDATE = 0x00000100; - public const uint FOS_NOTESTFILECREATE = 0x00010000; - public const uint FOS_DONTADDTORECENT = 0x02000000; - - public const uint S_OK = 0x0000; - - public const uint SIGDN_FILESYSPATH = 0x80058000; - - #endregion - - - #region COM - + [ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] internal class FileOpenDialogRCW { } - - [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IFileDialog - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [PreserveSig()] - uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow - - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileTypeIndex([In] uint iFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFileTypeIndex(out uint piFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Unadvise([In] uint dwCookie); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetOptions([In] uint fos); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetOptions(out uint fos); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Close([MarshalAs(UnmanagedType.Error)] uint hr); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetClientGuid([In] ref Guid guid); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint ClearClientData(); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); - } - - - [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IShellItem - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); - } - - #endregion - - + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); @@ -999,6 +881,16 @@ namespace Perspex.Win32.Interop } + public enum HRESULT : long + { + S_FALSE = 0x0001, + S_OK = 0x0000, + E_INVALIDARG = 0x80070057, + E_OUTOFMEMORY = 0x8007000E + } + + public const uint SIGDN_FILESYSPATH = 0x80058000; + [Flags] internal enum FOS : uint { @@ -1052,4 +944,102 @@ namespace Perspex.Win32.Interop public int flagsEx; } } + + [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IFileDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Close([MarshalAs(UnmanagedType.Error)] uint hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint ClearClientData(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } + + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } } diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index d8473f75c2..84aa78a2a7 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -136,17 +136,17 @@ namespace Perspex.Win32 string result = string.Empty; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; - var frm = (UnmanagedMethods.IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW()); + var frm = (IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW()); uint options; frm.GetOptions(out options); - options |= UnmanagedMethods.FOS_PICKFOLDERS | UnmanagedMethods.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS_NOVALIDATE | UnmanagedMethods.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS_DONTADDTORECENT; + options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); frm.SetOptions(options); if (dialog.InitialDirectory != null) { - UnmanagedMethods.IShellItem directoryShellItem; + IShellItem directoryShellItem; var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetFolder(directoryShellItem); } @@ -154,21 +154,21 @@ namespace Perspex.Win32 if (dialog.DefaultDirectory != null) { - UnmanagedMethods.IShellItem directoryShellItem; + IShellItem directoryShellItem; var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetDefaultFolder(directoryShellItem); } } - if (frm.Show(hWnd) == UnmanagedMethods.S_OK) + if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) { - UnmanagedMethods.IShellItem shellItem; - if (frm.GetResult(out shellItem) == UnmanagedMethods.S_OK) + IShellItem shellItem; + if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { IntPtr pszString; - if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == UnmanagedMethods.S_OK) + if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) { if (pszString != IntPtr.Zero) { From e7ae829e5cbf79f6aede02d8f1c79322242429a8 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 22:20:33 +0000 Subject: [PATCH 14/31] added placeholder implementations for system dialogs. --- src/Android/Perspex.Android/SystemDialogImpl.cs | 5 +++++ src/Gtk/Perspex.Gtk/SystemDialogImpl.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Android/Perspex.Android/SystemDialogImpl.cs b/src/Android/Perspex.Android/SystemDialogImpl.cs index 60f9de914c..d60fad8230 100644 --- a/src/Android/Perspex.Android/SystemDialogImpl.cs +++ b/src/Android/Perspex.Android/SystemDialogImpl.cs @@ -12,5 +12,10 @@ namespace Perspex.Android { throw new NotImplementedException(); } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs index 869bd2eb70..304de86fc3 100644 --- a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs +++ b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs @@ -53,5 +53,10 @@ namespace Perspex.Gtk dlg.Show(); return tcs.Task; } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + throw new NotImplementedException(); + } } } From b6c60cf334c8f9c41555b22adfd8e4001f4fca37 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2016 23:51:15 +0100 Subject: [PATCH 15/31] Added FontWeight to TemplatedControl. --- .../Primitives/TemplatedControl.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 7c3d7dd7cf..c688d667b5 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -56,6 +56,12 @@ namespace Perspex.Controls.Primitives public static readonly StyledProperty FontStyleProperty = TextBlock.FontStyleProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty FontWeightProperty = + TextBlock.FontWeightProperty.AddOwner(); + /// /// Defines the property. /// @@ -177,6 +183,15 @@ namespace Perspex.Controls.Primitives set { SetValue(FontStyleProperty, value); } } + /// + /// Gets or sets the font weight used to draw the control's text. + /// + public FontWeight FontWeihght + { + get { return GetValue(FontWeightProperty); } + set { SetValue(FontWeightProperty, value); } + } + /// /// Gets or sets the brush used to draw the control's text and other foreground elements. /// From d85a4eae1630e9357fb098636690991786ffd5d1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Feb 2016 23:58:50 +0100 Subject: [PATCH 16/31] Fixed typo. --- src/Perspex.Controls/Primitives/TemplatedControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index c688d667b5..7bf2edfbe7 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -186,7 +186,7 @@ namespace Perspex.Controls.Primitives /// /// Gets or sets the font weight used to draw the control's text. /// - public FontWeight FontWeihght + public FontWeight FontWeight { get { return GetValue(FontWeightProperty); } set { SetValue(FontWeightProperty, value); } From 8ac14fd82ad5ae51b8479c58e1c6a264b876beca Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2016 19:25:05 +0100 Subject: [PATCH 17/31] Make Color immutable. --- src/Perspex.SceneGraph/Media/Color.cs | 45 ++++++++++++--------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/Perspex.SceneGraph/Media/Color.cs b/src/Perspex.SceneGraph/Media/Color.cs index dbc5a108ac..a7b9ec9016 100644 --- a/src/Perspex.SceneGraph/Media/Color.cs +++ b/src/Perspex.SceneGraph/Media/Color.cs @@ -16,22 +16,30 @@ namespace Perspex.Media /// /// Gets or sets the Alpha component of the color. /// - public byte A { get; set; } + public byte A { get; } /// /// Gets or sets the Red component of the color. /// - public byte R { get; set; } + public byte R { get; } /// /// Gets or sets the Green component of the color. /// - public byte G { get; set; } + public byte G { get; } /// /// Gets or sets the Blue component of the color. /// - public byte B { get; set; } + public byte B { get; } + + public Color(byte a, byte r, byte g, byte b) + { + A = a; + R = r; + G = g; + B = b; + } /// /// Creates a from alpha, red, green and blue components. @@ -43,13 +51,7 @@ namespace Perspex.Media /// The color. public static Color FromArgb(byte a, byte r, byte g, byte b) { - return new Color - { - A = a, - R = r, - G = g, - B = b, - }; + return new Color(a, r, g, b); } /// @@ -61,13 +63,7 @@ namespace Perspex.Media /// The color. public static Color FromRgb(byte r, byte g, byte b) { - return new Color - { - A = 0xff, - R = r, - G = g, - B = b, - }; + return new Color(0xff, r, g, b); } /// @@ -77,13 +73,12 @@ namespace Perspex.Media /// The color. public static Color FromUInt32(uint value) { - return new Color - { - A = (byte)((value >> 24) & 0xff), - R = (byte)((value >> 16) & 0xff), - G = (byte)((value >> 8) & 0xff), - B = (byte)(value & 0xff), - }; + return new Color( + (byte)((value >> 24) & 0xff), + (byte)((value >> 16) & 0xff), + (byte)((value >> 8) & 0xff), + (byte)(value & 0xff) + ); } /// From 4ccd560b8e0a3c077cda8fc54553765303dc3f57 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2016 20:22:02 +0100 Subject: [PATCH 18/31] Reinstated perspex's XAML member value plugin. As the extension point previously added to OmniXAML has been removed. --- .../Context/PerspexAttachableXamlMember.cs | 8 +++---- .../Context/PerspexMemberValuePlugin.cs | 24 +++++++++++++++++++ .../Context/PerspexXamlMember.cs | 4 ++-- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 src/Markup/Perspex.Markup.Xaml/Context/PerspexMemberValuePlugin.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs index a43d9a8be9..8b48e8e871 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs @@ -20,14 +20,14 @@ namespace Perspex.Markup.Xaml.Context { } - public override void SetValue(object instance, object value) + public override string ToString() { - PropertyAccessor.SetValue(instance, this, value); + return "Perspex Attachable XAML Member " + base.ToString(); } - public override string ToString() + protected override IMemberValuePlugin LookupXamlMemberValueConnector() { - return "Perspex Attachable XAML Member " + base.ToString(); + return new PerspexMemberValuePlugin(this); } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexMemberValuePlugin.cs new file mode 100644 index 0000000000..277326fcb9 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexMemberValuePlugin.cs @@ -0,0 +1,24 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using OmniXaml.Typing; + +namespace Perspex.Markup.Xaml.Context +{ + public class PerspexMemberValuePlugin : MemberValuePlugin + { + private readonly MutableMember _xamlMember; + + public PerspexMemberValuePlugin(MutableMember xamlMember) + : base(xamlMember) + { + _xamlMember = xamlMember; + } + + public override void SetValue(object instance, object value) + { + PropertyAccessor.SetValue(instance, _xamlMember, value); + } + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs index ba88ca5c35..b7e85d73ab 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs @@ -18,9 +18,9 @@ namespace Perspex.Markup.Xaml.Context { } - public override void SetValue(object instance, object value) + protected override IMemberValuePlugin LookupXamlMemberValueConnector() { - PropertyAccessor.SetValue(instance, this, value); + return new PerspexMemberValuePlugin(this); } public override string ToString() diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index a0b7a7f7a4..854ae604fd 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit a0b7a7f7a415409497539f69e39deb457f7ca629 +Subproject commit 854ae604fd6f7fd4d1aca495ccbd14e56d1305ff From bee35d75dd93eefff0ae06991b3731b7c5f998dd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2016 22:41:06 +0100 Subject: [PATCH 19/31] File missed from .csproj. --- src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index ea64071aa3..984762d50f 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -42,6 +42,7 @@ + From dfecd327ffe6ba4b69532a22e637727090e1d91d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2016 22:42:59 +0100 Subject: [PATCH 20/31] Updated OmniXAML. Using perspex-master branch which is upstream master with all non-merged Perspex changes. --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 854ae604fd..27005c8b4d 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 854ae604fd6f7fd4d1aca495ccbd14e56d1305ff +Subproject commit 27005c8b4d41f2a6d3b85da7eb6ed994b57e0138 From 8233f4df185516db9a469611c737efc582c4311c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2016 23:10:33 +0100 Subject: [PATCH 21/31] Support default binding mode on direct properties. --- src/Perspex.Base/DirectProperty.cs | 6 ++++-- src/Perspex.Base/PerspexProperty.cs | 7 +++++-- tests/Perspex.Base.UnitTests/DirectPropertyTests.cs | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Perspex.Base/DirectProperty.cs b/src/Perspex.Base/DirectProperty.cs index c926f28240..7ae19ec520 100644 --- a/src/Perspex.Base/DirectProperty.cs +++ b/src/Perspex.Base/DirectProperty.cs @@ -24,11 +24,13 @@ namespace Perspex /// The name of the property. /// Gets the current value of the property. /// Sets the value of the property. May be null. + /// The property metadata. public DirectProperty( string name, Func getter, - Action setter = null) - : base(name, typeof(TOwner), new PropertyMetadata()) + Action setter, + PropertyMetadata metadata) + : base(name, typeof(TOwner), metadata) { Contract.Requires(getter != null); diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 4c0b6fd724..8998627849 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -342,16 +342,19 @@ namespace Perspex /// The name of the property. /// Gets the current value of the property. /// Sets the value of the property. + /// The default binding mode for the property. /// A public static DirectProperty RegisterDirect( string name, Func getter, - Action setter = null) + Action setter = null, + BindingMode defaultBindingMode = BindingMode.OneWay) where TOwner : IPerspexObject { Contract.Requires(name != null); - var result = new DirectProperty(name, getter, setter); + var metadata = new PropertyMetadata(defaultBindingMode); + var result = new DirectProperty(name, getter, setter, metadata); PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; } diff --git a/tests/Perspex.Base.UnitTests/DirectPropertyTests.cs b/tests/Perspex.Base.UnitTests/DirectPropertyTests.cs index 68e1c62cf3..9e23e59f80 100644 --- a/tests/Perspex.Base.UnitTests/DirectPropertyTests.cs +++ b/tests/Perspex.Base.UnitTests/DirectPropertyTests.cs @@ -30,7 +30,11 @@ namespace Perspex.Base.UnitTests [Fact] public void IsDirect_Property_Returns_True() { - var target = new DirectProperty("test", o => null); + var target = new DirectProperty( + "test", + o => null, + null, + new PropertyMetadata()); Assert.True(target.IsDirect); } From 979c5affb898b26e55c3bb0c6a7baae916424928 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 16 Feb 2016 22:30:57 +0000 Subject: [PATCH 22/31] corrected default binding mode for checkbox, treeview itemms and selected items. --- src/Perspex.Controls/Primitives/SelectingItemsControl.cs | 2 +- src/Perspex.Controls/Primitives/ToggleButton.cs | 3 ++- src/Perspex.Controls/TreeViewItem.cs | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index 9ec708dd6b..f6e405a24a 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives PerspexProperty.RegisterDirect( nameof(SelectedItem), o => o.SelectedItem, - (o, v) => o.SelectedItem = v); + (o, v) => o.SelectedItem = v, BindingMode.TwoWay); /// /// Defines the property. diff --git a/src/Perspex.Controls/Primitives/ToggleButton.cs b/src/Perspex.Controls/Primitives/ToggleButton.cs index 8d464464ec..c0aedf00ba 100644 --- a/src/Perspex.Controls/Primitives/ToggleButton.cs +++ b/src/Perspex.Controls/Primitives/ToggleButton.cs @@ -3,6 +3,7 @@ using System; using Perspex.Interactivity; +using Perspex.Data; namespace Perspex.Controls.Primitives { @@ -12,7 +13,7 @@ namespace Perspex.Controls.Primitives PerspexProperty.RegisterDirect( "IsChecked", o => o.IsChecked, - (o,v) => o.IsChecked = v); + (o,v) => o.IsChecked = v, BindingMode.TwoWay); private bool _isChecked; diff --git a/src/Perspex.Controls/TreeViewItem.cs b/src/Perspex.Controls/TreeViewItem.cs index fe74ebfd93..d5369840ad 100644 --- a/src/Perspex.Controls/TreeViewItem.cs +++ b/src/Perspex.Controls/TreeViewItem.cs @@ -20,7 +20,7 @@ namespace Perspex.Controls /// Defines the property. /// public static readonly StyledProperty IsExpandedProperty = - PerspexProperty.Register("IsExpanded"); + PerspexProperty.Register("IsExpanded", default(bool), false, Data.BindingMode.TwoWay); /// /// Defines the property. @@ -30,7 +30,7 @@ namespace Perspex.Controls private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel - { + { [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, }); @@ -40,7 +40,7 @@ namespace Perspex.Controls /// Initializes static members of the class. /// static TreeViewItem() - { + { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); From 127133eb89d2a14380a26475bad9f9c049836203 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 17 Feb 2016 18:59:27 +0100 Subject: [PATCH 23/31] Added enum conversions to DefaultValueConverter. --- .../Perspex.Markup/DefaultValueConverter.cs | 35 ++++++++++++++- src/Perspex.Base/Utilities/TypeUtilities.cs | 10 +++-- .../DefaultValueConverterTests.cs | 43 +++++++++++++++++-- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/Markup/Perspex.Markup/DefaultValueConverter.cs b/src/Markup/Perspex.Markup/DefaultValueConverter.cs index e49c1c3ae6..23bb0f774c 100644 --- a/src/Markup/Perspex.Markup/DefaultValueConverter.cs +++ b/src/Markup/Perspex.Markup/DefaultValueConverter.cs @@ -3,6 +3,8 @@ using System; using System.Globalization; +using System.Linq; +using System.Reflection; using Perspex.Utilities; namespace Perspex.Markup @@ -30,7 +32,9 @@ namespace Perspex.Markup { object result; - if (value != null && TypeUtilities.TryConvert(targetType, value, culture, out result)) + if (value != null && + (TypeUtilities.TryConvert(targetType, value, culture, out result) || + TryConvertEnum(value, targetType, culture, out result))) { return result; } @@ -52,5 +56,34 @@ namespace Perspex.Markup { return Convert(value, targetType, parameter, culture); } + + private bool TryConvertEnum(object value, Type targetType, CultureInfo cultur, out object result) + { + var valueTypeInfo = value.GetType().GetTypeInfo(); + var targetTypeInfo = targetType.GetTypeInfo(); + + if (valueTypeInfo.IsEnum && !targetTypeInfo.IsEnum) + { + var enumValue = (int)value; + + if (TypeUtilities.TryCast(targetType, enumValue, out result)) + { + return true; + } + } + else if (!valueTypeInfo.IsEnum && targetTypeInfo.IsEnum) + { + object intValue; + + if (TypeUtilities.TryCast(typeof(int), value, out intValue)) + { + result = Enum.ToObject(targetType, intValue); + return true; + } + } + + result = null; + return false; + } } } diff --git a/src/Perspex.Base/Utilities/TypeUtilities.cs b/src/Perspex.Base/Utilities/TypeUtilities.cs index 3d19de5f97..c6d16d56b2 100644 --- a/src/Perspex.Base/Utilities/TypeUtilities.cs +++ b/src/Perspex.Base/Utilities/TypeUtilities.cs @@ -119,6 +119,12 @@ namespace Perspex.Utilities return true; } + if (to == typeof(string)) + { + result = Convert.ToString(value); + return true; + } + if (to.GetTypeInfo().IsEnum && from == typeof(string)) { if (Enum.IsDefined(to, (string)value)) @@ -131,9 +137,7 @@ namespace Perspex.Utilities bool containsFrom = Conversions.ContainsKey(from); bool containsTo = Conversions.ContainsKey(to); - if ((containsFrom && containsTo) || - (from == typeof(string) && containsTo) || - (to == typeof(string) && containsFrom)) + if ((containsFrom && containsTo) || (from == typeof(string) && containsTo)) { try { diff --git a/tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs b/tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs index b438d6af1d..55034a3c53 100644 --- a/tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs +++ b/tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Globalization; +using Perspex.Controls; using Xunit; namespace Perspex.Markup.UnitTests @@ -44,6 +45,18 @@ namespace Perspex.Markup.UnitTests Assert.Equal(TestEnum.Bar, result); } + [Fact] + public void Can_Convert_Int_To_Enum() + { + var result = DefaultValueConverter.Instance.Convert( + 1, + typeof(TestEnum), + null, + CultureInfo.InvariantCulture); + + Assert.Equal(TestEnum.Bar, result); + } + [Fact] public void Can_Convert_Double_To_String() { @@ -57,15 +70,27 @@ namespace Perspex.Markup.UnitTests } [Fact] - public void Can_Convert_Double_To_Int() + public void Can_Convert_Enum_To_Int() { var result = DefaultValueConverter.Instance.Convert( - 5.0, + TestEnum.Bar, typeof(int), null, CultureInfo.InvariantCulture); - Assert.Equal(5, result); + Assert.Equal(1, result); + } + + [Fact] + public void Can_Convert_Enum_To_String() + { + var result = DefaultValueConverter.Instance.Convert( + TestEnum.Bar, + typeof(string), + null, + CultureInfo.InvariantCulture); + + Assert.Equal("Bar", result); } [Fact] @@ -80,6 +105,18 @@ namespace Perspex.Markup.UnitTests Assert.Equal(5.0, result); } + [Fact] + public void Cannot_Convert_Between_Different_Enum_Types() + { + var result = DefaultValueConverter.Instance.Convert( + TestEnum.Foo, + typeof(Orientation), + null, + CultureInfo.InvariantCulture); + + Assert.Equal(PerspexProperty.UnsetValue, result); + } + private enum TestEnum { Foo, From 8b9693d99621386a93200748ac91fc3981540e4b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Feb 2016 11:37:04 +0100 Subject: [PATCH 24/31] Don't throw in LogicalNotNode.SetValue. Instead return false to indicate the write failed. --- src/Markup/Perspex.Markup/Data/LogicalNotNode.cs | 2 +- .../Data/ExpressionObserverTests_Negation.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs index 2447aee4f2..a2941684b4 100644 --- a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs +++ b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs @@ -11,7 +11,7 @@ namespace Perspex.Markup.Data { public override bool SetValue(object value) { - throw new NotSupportedException("Cannot set a negated binding."); + return false; } public override IDisposable Subscribe(IObserver observer) diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs index 3581a71563..9dc40ece00 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs @@ -81,12 +81,12 @@ namespace Perspex.Markup.UnitTests.Data } [Fact] - public void SetValue_Should_Throw() + public void SetValue_Should_Return_False() { var data = new { Foo = "foo" }; var target = new ExpressionObserver(data, "!Foo"); - Assert.Throws(() => target.SetValue("bar")); + Assert.False(target.SetValue("bar")); } } } From 03c7cb545670ba9f7ae7b0ea15d41996cb43a79a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Feb 2016 11:51:39 +0100 Subject: [PATCH 25/31] Handle inserts into ItemsControl items. Fixes #431. --- .../Generators/IItemContainerGenerator.cs | 7 +++++++ .../Generators/ItemContainerGenerator.cs | 6 ++++++ .../Presenters/ItemsPresenter.cs | 18 ++++++++++++++++- .../Presenters/ItemsPresenterTests.cs | 20 +++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs index 85628e3374..490ee8899b 100644 --- a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs @@ -52,6 +52,13 @@ namespace Perspex.Controls.Generators /// The removed containers. IEnumerable Dematerialize(int startingIndex, int count); + /// + /// Inserts space for newly inserted containers in the index. + /// + /// The index at which space should be inserted. + /// The number of blank spaces to create. + void InsertSpace(int index, int count); + /// /// Removes a set of created containers and updates the index of later containers to fill /// the gap. diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs index 2733de5770..f1f49affbf 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs @@ -85,6 +85,12 @@ namespace Perspex.Controls.Generators return result; } + /// + public virtual void InsertSpace(int index, int count) + { + _containers.InsertRange(index, Enumerable.Repeat(null, count)); + } + /// public virtual IEnumerable RemoveRange(int startingIndex, int count) { diff --git a/src/Perspex.Controls/Presenters/ItemsPresenter.cs b/src/Perspex.Controls/Presenters/ItemsPresenter.cs index 29e796fd75..909a1e011f 100644 --- a/src/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/src/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Perspex.Controls.Generators; using Perspex.Controls.Templates; +using Perspex.Controls.Utils; using Perspex.Input; using Perspex.Styling; @@ -237,6 +238,11 @@ namespace Perspex.Controls.Presenters switch (e.Action) { case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex + e.NewItems.Count < this.Items.Count()) + { + generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count); + } + AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector)); break; @@ -281,7 +287,17 @@ namespace Perspex.Controls.Presenters { if (i.ContainerControl != null) { - this.Panel.Children.Add(i.ContainerControl); + if (i.Index < this.Panel.Children.Count) + { + // HACK: This will insert at the wrong place when there are null items, + // but all of this will need to be rewritten when we implement + // virtualization so hope no-one notices until then :) + this.Panel.Children.Insert(i.Index, i.ContainerControl); + } + else + { + this.Panel.Children.Add(i.ContainerControl); + } } } } diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index dd7b41caee..10737ad4d1 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -147,6 +147,26 @@ namespace Perspex.Controls.UnitTests.Presenters Assert.Equal(new[] { "foo", "baz", "bar" }, text); } + [Fact] + public void Inserting_Items_Should_Update_Containers() + { + var items = new ObservableCollection { "foo", "bar", "baz" }; + var target = new ItemsPresenter + { + Items = items, + }; + + target.ApplyTemplate(); + items.Insert(2, "insert"); + + var text = target.Panel.Children + .OfType() + .Select(x => x.Text) + .ToList(); + + Assert.Equal(new[] { "foo", "bar", "insert", "baz" }, text); + } + [Fact] public void Setting_Items_To_Null_Should_Remove_Containers() { From 92bca08e3bf24cafa16c908c0db0be7862e61b82 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Feb 2016 18:38:53 +0100 Subject: [PATCH 26/31] Correctly maintain TreeContainerIndex. Fixes #434 --- src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 9feaab3d25..11a56aa556 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -68,6 +68,7 @@ namespace Perspex.Controls.Generators } else if (container != null) { + Index.Add(item, container); return container; } else From d0efd0c6d46153909cd213c70c845f95ffcb1cea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Feb 2016 19:12:03 +0100 Subject: [PATCH 27/31] Clear TemplatedParent of ex-template children. This prevents them still being bound to their ex-TemplatedParent. Fixes #435. --- .../Primitives/TemplatedControl.cs | 15 ++++++++- .../Primitives/TemplatedControlTests.cs | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 7bf2edfbe7..10e312bb2b 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -251,7 +251,15 @@ namespace Perspex.Controls.Primitives { if (!_templateApplied) { - VisualChildren.Clear(); + if (VisualChildren.Count > 0) + { + foreach (var child in this.GetTemplateChildren()) + { + child.SetValue(TemplatedParentProperty, null); + } + + VisualChildren.Clear(); + } if (Template != null) { @@ -318,6 +326,11 @@ namespace Perspex.Controls.Primitives /// The event args. protected virtual void OnTemplateChanged(PerspexPropertyChangedEventArgs e) { + if (_templateApplied && VisualChildren.Count > 0) + { + _templateApplied = false; + } + _templateApplied = false; InvalidateMeasure(); } diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 4de5d8fd3b..a93c6b3026 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -311,6 +311,39 @@ namespace Perspex.Controls.UnitTests.Primitives Assert.True(raised); } + [Fact] + public void Applying_New_Template_Clears_TemplatedParent_Of_Old_Template_Children() + { + var target = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => new Decorator + { + Child = new Border(), + }) + }; + + target.ApplyTemplate(); + + var decorator = (Decorator)target.GetVisualChildren().Single(); + var border = (Border)decorator.Child; + + Assert.Equal(target, decorator.TemplatedParent); + Assert.Equal(target, border.TemplatedParent); + + target.Template = new FuncControlTemplate(_ => new Canvas()); + + // Templated children should not be removed here: the control may be re-added + // somewhere with the same template, so they could still be of use. + Assert.Same(decorator, target.GetVisualChildren().Single()); + Assert.Equal(target, decorator.TemplatedParent); + Assert.Equal(target, border.TemplatedParent); + + target.ApplyTemplate(); + + Assert.Null(decorator.TemplatedParent); + Assert.Null(border.TemplatedParent); + } + private static IControl ScrollingContentControlTemplate(ContentControl control) { return new Border From 8da396c518af4374658d76ce1e49e21997606323 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 19 Feb 2016 11:13:09 +0000 Subject: [PATCH 28/31] fixed code that crashes mono compiler. --- src/Perspex.Controls/Utils/UndoRedoHelper.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Controls/Utils/UndoRedoHelper.cs b/src/Perspex.Controls/Utils/UndoRedoHelper.cs index 9de2ca9b35..70e5921d8d 100644 --- a/src/Perspex.Controls/Utils/UndoRedoHelper.cs +++ b/src/Perspex.Controls/Utils/UndoRedoHelper.cs @@ -37,7 +37,12 @@ namespace Perspex.Controls.Utils public void Undo() { - _host.UndoRedoState= (_currentNode = _currentNode?.Previous ?? _currentNode).Value; + if (_currentNode != null) + { + _currentNode = _currentNode.Previous; + } + + _host.UndoRedoState = _currentNode?.Value; } public bool IsLastState => _currentNode.Next == null; @@ -62,8 +67,12 @@ namespace Perspex.Controls.Utils } public void Redo() - { - _host.UndoRedoState = (_currentNode = _currentNode?.Next ?? _currentNode).Value; + { + if (_currentNode != null) { + _currentNode = _currentNode.Next; + } + + _host.UndoRedoState = _currentNode?.Value; } public void Snapshot() From dc3c4d79d61bd544376e51cf754336a9442c2fb9 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Fri, 19 Feb 2016 11:29:11 +0000 Subject: [PATCH 29/31] Added open file and open folder dialogs to test app. --- .../ViewModels/MainWindowViewModel.cs | 22 +++++++++++++++++++ .../Views/MainWindow.paml | 6 +++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs index 9ac24a622e..3ae72e138c 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using ReactiveUI; +using Perspex.Controls; namespace XamlTestApplication.ViewModels { @@ -59,6 +60,23 @@ namespace XamlTestApplication.ViewModels CollapseNodesCommand.Subscribe(_ => ExpandNodes(false)); ExpandNodesCommand = ReactiveCommand.Create(); ExpandNodesCommand.Subscribe(_ => ExpandNodes(true)); + + OpenFileCommand = ReactiveCommand.Create(); + OpenFileCommand.Subscribe(_ => + { + var ofd = new OpenFileDialog(); + + ofd.ShowAsync(); + }); + + OpenFolderCommand = ReactiveCommand.Create(); + OpenFolderCommand.Subscribe(_ => + { + var ofd = new OpenFolderDialog(); + + ofd.ShowAsync(); + }); + } public List Items { get; } @@ -68,6 +86,10 @@ namespace XamlTestApplication.ViewModels public ReactiveCommand ExpandNodesCommand { get; } + public ReactiveCommand OpenFileCommand { get; } + + public ReactiveCommand OpenFolderCommand { get; } + public void ExpandNodes(bool expanded) { foreach (var node in Nodes) diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index cadb70edac..e5155b0b95 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -6,11 +6,13 @@ Title="Perspex Test Application" Width="800" Height="600"> - + + + - + From 08ef760431821a0982e34eb1f4f29f5da80dbd0a Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Fri, 19 Feb 2016 11:33:48 +0000 Subject: [PATCH 30/31] fixed undo redo helper. --- src/Perspex.Controls/Utils/UndoRedoHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Utils/UndoRedoHelper.cs b/src/Perspex.Controls/Utils/UndoRedoHelper.cs index 70e5921d8d..60b6839dad 100644 --- a/src/Perspex.Controls/Utils/UndoRedoHelper.cs +++ b/src/Perspex.Controls/Utils/UndoRedoHelper.cs @@ -42,7 +42,7 @@ namespace Perspex.Controls.Utils _currentNode = _currentNode.Previous; } - _host.UndoRedoState = _currentNode?.Value; + _host.UndoRedoState = _currentNode.Value; } public bool IsLastState => _currentNode.Next == null; @@ -72,7 +72,7 @@ namespace Perspex.Controls.Utils _currentNode = _currentNode.Next; } - _host.UndoRedoState = _currentNode?.Value; + _host.UndoRedoState = _currentNode.Value; } public void Snapshot() From b845680b2e270c4028cc53eda71c17b277779390 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Fri, 19 Feb 2016 13:27:42 +0000 Subject: [PATCH 31/31] Font scaling on linux, and FolderBrowse implementation for linux. --- .../ViewModels/MainWindowViewModel.cs | 22 +++++++++++++ .../Views/MainWindow.paml | 6 ++-- .../Perspex.Cairo/Media/FormattedTextImpl.cs | 9 ++++-- src/Gtk/Perspex.Gtk/SystemDialogImpl.cs | 32 +++++++++++++++++-- src/Perspex.Controls/Utils/UndoRedoHelper.cs | 15 +++++++-- 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs index 9ac24a622e..3ae72e138c 100644 --- a/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs +++ b/samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using ReactiveUI; +using Perspex.Controls; namespace XamlTestApplication.ViewModels { @@ -59,6 +60,23 @@ namespace XamlTestApplication.ViewModels CollapseNodesCommand.Subscribe(_ => ExpandNodes(false)); ExpandNodesCommand = ReactiveCommand.Create(); ExpandNodesCommand.Subscribe(_ => ExpandNodes(true)); + + OpenFileCommand = ReactiveCommand.Create(); + OpenFileCommand.Subscribe(_ => + { + var ofd = new OpenFileDialog(); + + ofd.ShowAsync(); + }); + + OpenFolderCommand = ReactiveCommand.Create(); + OpenFolderCommand.Subscribe(_ => + { + var ofd = new OpenFolderDialog(); + + ofd.ShowAsync(); + }); + } public List Items { get; } @@ -68,6 +86,10 @@ namespace XamlTestApplication.ViewModels public ReactiveCommand ExpandNodesCommand { get; } + public ReactiveCommand OpenFileCommand { get; } + + public ReactiveCommand OpenFolderCommand { get; } + public void ExpandNodes(bool expanded) { foreach (var node in Nodes) diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index cadb70edac..e5155b0b95 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -6,11 +6,13 @@ Title="Perspex Test Application" Width="800" Height="600"> - + + + - + diff --git a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs index 956a522002..e8fc9bc924 100644 --- a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs @@ -15,6 +15,11 @@ namespace Perspex.Cairo.Media private Size _size; private readonly string _text; + static double CorrectScale(double input) + { + return input * 0.75; + } + public FormattedTextImpl( Pango.Context context, string text, @@ -25,14 +30,14 @@ namespace Perspex.Cairo.Media FontWeight fontWeight) { Contract.Requires(context != null); - Contract.Requires (text != null); + Contract.Requires(text != null); Layout = new Pango.Layout(context); _text = text; Layout.SetText(text); Layout.FontDescription = new Pango.FontDescription { Family = fontFamily, - Size = Pango.Units.FromDouble(fontSize), + Size = Pango.Units.FromDouble(CorrectScale(fontSize)), Style = (Pango.Style)fontStyle, Weight = fontWeight.ToCairo() }; diff --git a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs index 304de86fc3..9789beb7c7 100644 --- a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs +++ b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs @@ -15,7 +15,7 @@ namespace Perspex.Gtk public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { var tcs = new TaskCompletionSource(); - var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImpl) parent), + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImpl)parent), dialog is OpenFileDialog ? FileChooserAction.Open : FileChooserAction.Save, @@ -44,7 +44,7 @@ namespace Perspex.Gtk dlg.Hide(); dlg.Dispose(); }; - + dlg.Close += delegate { tcs.TrySetResult(null); @@ -56,7 +56,33 @@ namespace Perspex.Gtk public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) { - throw new NotImplementedException(); + var tcs = new TaskCompletionSource(); + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImpl)parent), + FileChooserAction.SelectFolder, + "Cancel", ResponseType.Cancel, + "Select Folder", ResponseType.Accept) + { + + }; + + dlg.Modal = true; + + dlg.Response += (_, args) => + { + if (args.ResponseId == ResponseType.Accept) + tcs.TrySetResult(dlg.Filename); + + dlg.Hide(); + dlg.Dispose(); + }; + + dlg.Close += delegate + { + tcs.TrySetResult(null); + dlg.Dispose(); + }; + dlg.Show(); + return tcs.Task; } } } diff --git a/src/Perspex.Controls/Utils/UndoRedoHelper.cs b/src/Perspex.Controls/Utils/UndoRedoHelper.cs index 9de2ca9b35..60b6839dad 100644 --- a/src/Perspex.Controls/Utils/UndoRedoHelper.cs +++ b/src/Perspex.Controls/Utils/UndoRedoHelper.cs @@ -37,7 +37,12 @@ namespace Perspex.Controls.Utils public void Undo() { - _host.UndoRedoState= (_currentNode = _currentNode?.Previous ?? _currentNode).Value; + if (_currentNode != null) + { + _currentNode = _currentNode.Previous; + } + + _host.UndoRedoState = _currentNode.Value; } public bool IsLastState => _currentNode.Next == null; @@ -62,8 +67,12 @@ namespace Perspex.Controls.Utils } public void Redo() - { - _host.UndoRedoState = (_currentNode = _currentNode?.Next ?? _currentNode).Value; + { + if (_currentNode != null) { + _currentNode = _currentNode.Next; + } + + _host.UndoRedoState = _currentNode.Value; } public void Snapshot()