From 12167dfea9254e3c22b76f8eabac0cbb37ebda1e Mon Sep 17 00:00:00 2001 From: "M. ter Woord" Date: Mon, 21 Aug 2017 18:22:04 +0200 Subject: [PATCH 01/14] Show descriptive error when no rendering or windowing subsystem found --- src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs index 9a54cdbab0..bae97f503d 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs @@ -59,7 +59,11 @@ namespace Avalonia from attribute in assembly.GetCustomAttributes() where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker) orderby attribute.Priority ascending - select attribute).First(); + select attribute).FirstOrDefault(); + if (windowingSubsystemAttribute == null) + { + throw new InvalidOperationException("No windowing subsystem found. Are you missing assembly references?"); + } var renderingSubsystemAttribute = (from assembly in RuntimePlatform.GetLoadedAssemblies() from attribute in assembly.GetCustomAttributes() @@ -67,7 +71,12 @@ namespace Avalonia where attribute.RequiresWindowingSubsystem == null || attribute.RequiresWindowingSubsystem == windowingSubsystemAttribute.Name orderby attribute.Priority ascending - select attribute).First(); + select attribute).FirstOrDefault(); + + if (renderingSubsystemAttribute == null) + { + throw new InvalidOperationException("No rendering subsystem found. Are you missing assembly references?"); + } UseWindowingSubsystem(() => windowingSubsystemAttribute.InitializationType .GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), From a16014f87559fb0ad64d8514358206f0a3f123c7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Aug 2017 13:04:16 -0500 Subject: [PATCH 02/14] Upgrade Moq to reduce build warnings --- build/Moq.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Moq.props b/build/Moq.props index 55242d922e..7de9b6b6ba 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + From 58b3ff90ab99743912d5ee7ab7ce21070f9e5da4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Aug 2017 14:19:34 -0500 Subject: [PATCH 03/14] Reduce build warnings by setting a few flags and not using deprecated overloads. --- build/UnitTests.NetCore.targets | 19 ----------- .../Avalonia.HtmlRenderer.csproj | 1 + .../NativeUnsafeMethods.cs | 1 + .../AppHost/HostedAppModel.cs | 1 - .../Interop/UnmanagedMethods.cs | 4 +-- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++-- .../Avalonia.Input.UnitTests.csproj | 32 ------------------- .../Avalonia.Styling.UnitTests.csproj | 1 + 9 files changed, 8 insertions(+), 59 deletions(-) diff --git a/build/UnitTests.NetCore.targets b/build/UnitTests.NetCore.targets index 13bb4ed230..42da8e4ab1 100644 --- a/build/UnitTests.NetCore.targets +++ b/build/UnitTests.NetCore.targets @@ -3,25 +3,6 @@ false true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - MinimumRecommendedRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - MinimumRecommendedRules.ruleset - diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj index f715217e42..e7822324a9 100644 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj +++ b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj @@ -4,6 +4,7 @@ False False false + CS0436 true diff --git a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs index ad8def369d..fbbf036b74 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs @@ -72,6 +72,7 @@ namespace Avalonia.LinuxFramebuffer FB_VBLANK_HAVE_VSYNC = 0x100 /* verical syncs can be detected */ } + [StructLayout(LayoutKind.Sequential)] unsafe struct fb_vblank { public VBlankFlags flags; /* FB_VBLANK flags */ __u32 count; /* counter of retraces since boot */ diff --git a/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs b/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs index a64304619a..b5d0687baa 100644 --- a/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs +++ b/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs @@ -86,7 +86,6 @@ namespace Avalonia.Designer.AppHost } double _currentScalingFactor = 1; - private string _color; private string _background; public event PropertyChangedEventHandler PropertyChanged; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index d5a6f1a7a1..5473ef9bea 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -922,9 +922,7 @@ namespace Avalonia.Win32.Interop [StructLayout(LayoutKind.Sequential)] internal class MONITORINFO { -#pragma warning disable CS0618 // Type or member is obsolete - public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); -#pragma warning restore CS0618 // Type or member is obsolete + public int cbSize = Marshal.SizeOf(); public RECT rcMonitor = new RECT(); public RECT rcWork = new RECT(); public int dwFlags = 0; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 584a5ba39e..d8e9256156 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -167,7 +167,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)), + cbSize = Marshal.SizeOf(), lpfnWndProc = _wndProcDelegate, hInstance = UnmanagedMethods.GetModuleHandle(null), lpszClassName = "AvaloniaMessageWindow " + Guid.NewGuid(), diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index feb7bdc2ee..4a30d48878 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -426,7 +426,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_DPICHANGED: var dpi = ToInt32(wParam) & 0xffff; - var newDisplayRect = (UnmanagedMethods.RECT)Marshal.PtrToStructure(lParam, typeof(UnmanagedMethods.RECT)); + var newDisplayRect = Marshal.PtrToStructure(lParam); Position = new Point(newDisplayRect.left, newDisplayRect.top); _scaling = dpi / 96.0; ScalingChanged?.Invoke(_scaling); @@ -494,7 +494,7 @@ namespace Avalonia.Win32 { var tm = new UnmanagedMethods.TRACKMOUSEEVENT { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.TRACKMOUSEEVENT)), + cbSize = Marshal.SizeOf(), dwFlags = 2, hwndTrack = _hwnd, dwHoverTime = 0, @@ -619,7 +619,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)), + cbSize = Marshal.SizeOf(), style = 0, lpfnWndProc = _wndProcDelegate, hInstance = UnmanagedMethods.GetModuleHandle(null), diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 186d293b96..710a818bcd 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -3,35 +3,6 @@ net461;netcoreapp1.1 Library - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Input.UnitTests.XML - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - @@ -48,7 +19,4 @@ - - - \ No newline at end of file diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index 8dd8faf9db..3f9213b91f 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -2,6 +2,7 @@ net461;netcoreapp1.1 Library + CS0067 From 090a1ec8cfd192da9fbb6f4f64e5979cb4f24a3b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 11:54:23 +0200 Subject: [PATCH 04/14] Lazily initialize Styles. Added an `IStyleHost.IsStylesInitialized` property to prevent the need for allocating empty `Styles` collections for many controls. --- src/Avalonia.Controls/Application.cs | 6 +++++- src/Avalonia.Controls/Control.cs | 13 ++++++------ .../Views/ControlDetailsView.cs | 2 +- src/Avalonia.Styling/Styling/IStyleHost.cs | 12 ++++++++++- .../Styling/StyleExtensions.cs | 11 ++++++---- src/Avalonia.Styling/Styling/Styler.cs | 5 ++++- .../Primitives/PopupTests.cs | 1 + .../Primitives/TemplatedControlTests.cs | 8 ++++---- .../TabControlTests.cs | 2 +- .../UserControlTests.cs | 2 +- .../FullLayoutTests.cs | 1 + .../ResourceTests.cs | 20 +++++++++---------- 12 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 3d13608226..6237a859a9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -39,6 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); + private Styles _styles; /// /// Initializes a new instance of the class. @@ -109,13 +110,16 @@ namespace Avalonia /// /// Global styles apply to all windows in the application. /// - public Styles Styles { get; } = new Styles(); + public Styles Styles => _styles ?? (_styles = new Styles()); /// /// Gets the styling parent of the application, which is null. /// IStyleHost IStyleHost.StylingParent => null; + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index eca5967a58..83c66d6349 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,8 +97,8 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; - private Styles _styles; private bool _styled; + private Styles _styles; private Subject _styleDetach = new Subject(); /// @@ -259,18 +259,14 @@ namespace Avalonia.Controls public bool IsInitialized { get; private set; } /// - /// Gets or sets the styles for the control. + /// Gets the styles for the control. /// /// /// Styles for the entire application are added to the Application.Styles collection, but /// each control may in addition define its own styles which are applied to the control /// itself and its children. /// - public Styles Styles - { - get { return _styles ?? (_styles = new Styles()); } - set { _styles = value; } - } + public Styles Styles => _styles ?? (_styles = new Styles()); /// /// Gets the control's logical parent. @@ -336,6 +332,9 @@ namespace Avalonia.Controls /// IObservable IStyleable.StyleDetach => _styleDetach; + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + /// IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; diff --git a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs index d7bd6fd128..e58818d31d 100644 --- a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs +++ b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs @@ -42,7 +42,7 @@ namespace Avalonia.Diagnostics.Views { Content = _grid = new SimpleGrid { - Styles = new Styles + Styles = { new Style(x => x.Is()) { diff --git a/src/Avalonia.Styling/Styling/IStyleHost.cs b/src/Avalonia.Styling/Styling/IStyleHost.cs index 8422f18b46..b225419c2d 100644 --- a/src/Avalonia.Styling/Styling/IStyleHost.cs +++ b/src/Avalonia.Styling/Styling/IStyleHost.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Styling { /// @@ -8,6 +10,15 @@ namespace Avalonia.Styling /// public interface IStyleHost { + /// + /// Gets a value indicating whether is initialized. + /// + /// + /// The property may be lazily initialized, if so this property + /// indicates whether it has been initialized. + /// + bool IsStylesInitialized { get; } + /// /// Gets the styles for the element. /// @@ -17,6 +28,5 @@ namespace Avalonia.Styling /// Gets the parent style host element. /// IStyleHost StylingParent { get; } - } } diff --git a/src/Avalonia.Styling/Styling/StyleExtensions.cs b/src/Avalonia.Styling/Styling/StyleExtensions.cs index e1073335a0..d53d00aed3 100644 --- a/src/Avalonia.Styling/Styling/StyleExtensions.cs +++ b/src/Avalonia.Styling/Styling/StyleExtensions.cs @@ -23,11 +23,14 @@ namespace Avalonia.Styling while (control != null) { - var result = control.Styles.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) + if (control.IsStylesInitialized) { - return result; + var result = control.Styles.FindResource(name); + + if (result != AvaloniaProperty.UnsetValue) + { + return result; + } } control = control.StylingParent; diff --git a/src/Avalonia.Styling/Styling/Styler.cs b/src/Avalonia.Styling/Styling/Styler.cs index 880e435b58..7ac5c89005 100644 --- a/src/Avalonia.Styling/Styling/Styler.cs +++ b/src/Avalonia.Styling/Styling/Styler.cs @@ -29,7 +29,10 @@ namespace Avalonia.Styling ApplyStyles(control, parentContainer); } - styleHost.Styles.Attach(control, styleHost); + if (styleHost.IsStylesInitialized) + { + styleHost.Styles.Attach(control, styleHost); + } } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index f192e87f08..ccbf04533d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -265,6 +265,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; var globalStyles = new Mock(); + globalStyles.Setup(x => x.IsStylesInitialized).Returns(true); globalStyles.Setup(x => x.Styles).Returns(styles); var renderInterface = new Mock(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 72c8073f21..be3c34ac2e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -399,7 +399,7 @@ namespace Avalonia.Controls.UnitTests.Primitives TestTemplatedControl target; var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -435,7 +435,7 @@ namespace Avalonia.Controls.UnitTests.Primitives TestTemplatedControl target; var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -474,7 +474,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -494,7 +494,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var root2 = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 2bbd08bf42..d11c989261 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -135,7 +135,7 @@ namespace Avalonia.Controls.UnitTests { var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs index c74ffab223..738c54594e 100644 --- a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests var target = new UserControl(); var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 6b7c73da2a..cdfb253bf4 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -193,6 +193,7 @@ namespace Avalonia.Layout.UnitTests .Bind().ToConstant(new Avalonia.Controls.UnitTests.WindowingPlatformMock(() => windowImpl.Object)); var theme = new DefaultTheme(); + globalStyles.Setup(x => x.IsStylesInitialized).Returns(true); globalStyles.Setup(x => x.Styles).Returns(theme); } } diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index a2535e0fb5..1f9b925eb1 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -16,7 +16,7 @@ namespace Avalonia.Styling.UnitTests var tree = new Decorator { - Styles = new Styles + Styles = { new Style { @@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests }, Child = target = new Border { - Styles = new Styles + Styles = { new Style { @@ -60,16 +60,16 @@ namespace Avalonia.Styling.UnitTests var tree = target = new Border { - Styles = new Styles + Styles = + { + new Style { - new Style + Resources = new StyleResources { - Resources = new StyleResources - { - { "Foo", "foo" }, - } - }, - } + { "Foo", "foo" }, + } + }, + } }; Assert.Equal(AvaloniaProperty.UnsetValue, target.FindStyleResource("Baz")); From 22bda08a901fa2a6fd4c284ad3dfc574d00686ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 12:28:39 +0200 Subject: [PATCH 05/14] Lazily initialize DataTemplates. Added an `IDataTemplateHost` interface with a `IsDataTemplatesInitialized` property to prevent the need for allocating empty `DataTemplates` collections for many controls. --- src/Avalonia.Controls/Application.cs | 9 +++--- src/Avalonia.Controls/Control.cs | 9 +++--- src/Avalonia.Controls/IControl.cs | 14 ++++++---- src/Avalonia.Controls/IGlobalDataTemplates.cs | 6 +--- .../Templates/DataTemplateExtensions.cs | 24 ++++++++++------ .../Templates/IDataTemplateHost.cs | 27 ++++++++++++++++++ src/Avalonia.Diagnostics/DevTools.xaml.cs | 2 +- .../ItemsControlTests.cs | 2 +- .../ListBoxTests.cs | 8 +++--- .../ContentPresenterTests_Unrooted.cs | 2 +- .../TabControlTests.cs | 2 +- .../TreeViewTests.cs | 28 +++++++++---------- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- 13 files changed, 82 insertions(+), 53 deletions(-) create mode 100644 src/Avalonia.Controls/Templates/IDataTemplateHost.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6237a859a9..18eb1f7606 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -66,11 +66,7 @@ namespace Avalonia /// /// The application's global data templates. /// - public DataTemplates DataTemplates - { - get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } - set { _dataTemplates = value; } - } + public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); /// /// Gets the application's focus manager. @@ -112,6 +108,9 @@ namespace Avalonia /// public Styles Styles => _styles ?? (_styles = new Styles()); + /// + bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// /// Gets the styling parent of the application, which is null. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 83c66d6349..4913037ea4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -243,11 +243,7 @@ namespace Avalonia.Controls /// Each control may define data templates which are applied to the control itself and its /// children. /// - public DataTemplates DataTemplates - { - get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } - set { _dataTemplates = value; } - } + public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); /// /// Gets a value that indicates whether the element has finished initialization. @@ -300,6 +296,9 @@ namespace Avalonia.Controls internal set { SetValue(TemplatedParentProperty, value); } } + /// + bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// /// Gets a value indicating whether the element is attached to a rooted logical tree. /// diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 3f5bd3fcac..41436c6d87 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,14 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost + public interface IControl : IVisual, + IDataTemplateHost, + ILogical, + ILayoutable, + IInputElement, + INamed, + IStyleable, + IStyleHost { /// /// Occurs when the control has finished initialization. @@ -31,11 +38,6 @@ namespace Avalonia.Controls /// object DataContext { get; set; } - /// - /// Gets the data templates for the control. - /// - DataTemplates DataTemplates { get; } - /// /// Gets a value that indicates whether the element has finished initialization. /// diff --git a/src/Avalonia.Controls/IGlobalDataTemplates.cs b/src/Avalonia.Controls/IGlobalDataTemplates.cs index a20c7379a3..248615de0d 100644 --- a/src/Avalonia.Controls/IGlobalDataTemplates.cs +++ b/src/Avalonia.Controls/IGlobalDataTemplates.cs @@ -8,11 +8,7 @@ namespace Avalonia.Controls /// /// Defines the application-global data templates. /// - public interface IGlobalDataTemplates + public interface IGlobalDataTemplates : IDataTemplateHost { - /// - /// Gets the application-global data templates. - /// - DataTemplates DataTemplates { get; } } } diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index df25733524..559cb9b776 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -17,8 +17,8 @@ namespace Avalonia.Controls.Templates /// The control searching for the data template. /// The data. /// - /// An optional primary template that can will be tried before the - /// in the tree are searched. + /// An optional primary template that can will be tried before the DataTemplates in the + /// tree are searched. /// /// The data template or null if no matching data template was found. public static IDataTemplate FindDataTemplate( @@ -31,13 +31,16 @@ namespace Avalonia.Controls.Templates return primary; } - foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) + foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) { - foreach (IDataTemplate dt in i.DataTemplates) + if (i.IsDataTemplatesInitialized) { - if (dt.Match(data)) + foreach (IDataTemplate dt in i.DataTemplates) { - return dt; + if (dt.Match(data)) + { + return dt; + } } } } @@ -46,11 +49,14 @@ namespace Avalonia.Controls.Templates if (global != null) { - foreach (IDataTemplate dt in global.DataTemplates) + if (global.IsDataTemplatesInitialized) { - if (dt.Match(data)) + foreach (IDataTemplate dt in global.DataTemplates) { - return dt; + if (dt.Match(data)) + { + return dt; + } } } } diff --git a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs new file mode 100644 index 0000000000..5cc12581d4 --- /dev/null +++ b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs @@ -0,0 +1,27 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Controls.Templates +{ + /// + /// Defines an element that has a collection. + /// + public interface IDataTemplateHost + { + /// + /// Gets the data templates for the element. + /// + DataTemplates DataTemplates { get; } + + /// + /// Gets a value indicating whether is initialized. + /// + /// + /// The property may be lazily initialized, if so this property + /// indicates whether it has been initialized. + /// + bool IsDataTemplatesInitialized { get; } + } +} diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index 6593a8cd42..be0863954a 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -71,7 +71,7 @@ namespace Avalonia.Diagnostics Width = 1024, Height = 512, Content = devTools, - DataTemplates = new DataTemplates + DataTemplates = { new ViewLocator(), } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index e88d1881e6..f01eecf647 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -388,7 +388,7 @@ namespace Avalonia.Controls.UnitTests { Template = GetTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index e58542bfb4..a5f5f8d328 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -109,10 +109,10 @@ namespace Avalonia.Controls.UnitTests { Template = ListBoxTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates - { - new FuncDataTemplate(x => new Button { Content = x }) - }, + DataTemplates = + { + new FuncDataTemplate(x => new Button { Content = x }) + }, Items = items, }; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs index 3585109dee..5268c9ac0d 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs @@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests.Presenters root.Child = null; root = new TestRoot { - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Decorator()), }, diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index d11c989261..638d773cd0 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests { Template = new FuncControlTemplate(CreateTabControlTemplate), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 44ef7192ff..29eac3d8f5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -25,9 +25,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = CreateTestTreeData(), - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); @@ -69,9 +69,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = CreateTestTreeData(), - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; @@ -87,7 +87,6 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs @@ -95,6 +94,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(); root.Child = target; + CreateNodeDataTemplate(target); ApplyTemplates(target); var container = target.ItemContainerGenerator.Index.ContainerFromItem( @@ -116,11 +116,12 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; var visualRoot = new TestRoot(); visualRoot.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = tree[0].Children[1].Children[0]; @@ -146,11 +147,12 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; var visualRoot = new TestRoot(); visualRoot.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = tree[0].Children[1].Children[0]; @@ -191,12 +193,13 @@ namespace Avalonia.Controls.UnitTests var target = new TreeView { Template = CreateTreeViewTemplate(), - DataTemplates = CreateNodeDataTemplate(), Items = tree, }; var root = new TestRoot(); root.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(5, target.ItemContainerGenerator.Index.Items.Count()); @@ -221,7 +224,7 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, @@ -291,9 +294,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = data, - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); @@ -328,7 +331,6 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = data, - DataTemplates = CreateNodeDataTemplate(), }; var button = new Button(); @@ -341,6 +343,7 @@ namespace Avalonia.Controls.UnitTests } }; + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = data[0].Children[0]; @@ -411,12 +414,9 @@ namespace Avalonia.Controls.UnitTests }; } - private DataTemplates CreateNodeDataTemplate() + private void CreateNodeDataTemplate(IControl control) { - return new DataTemplates - { - new TestTreeDataTemplate() - }; + control.DataTemplates.Add(new TestTreeDataTemplate()); } private IControlTemplate CreateTreeViewTemplate() diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index e0b0d4a4c0..12ac8d3af6 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -276,7 +276,7 @@ namespace Avalonia.LeakTests { Content = target = new TreeView { - DataTemplates = new DataTemplates + DataTemplates = { new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name }, From 4bba1ab0fbaffc8abea9f96632e09b685d333109 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 13:14:46 +0200 Subject: [PATCH 06/14] Lazy initialize Interactive._eventHandlers. --- src/Avalonia.Interactivity/Interactive.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index e38b348179..78df6f6151 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -16,14 +16,18 @@ namespace Avalonia.Interactivity /// public class Interactive : Layoutable, IInteractive { - private readonly Dictionary> _eventHandlers = - new Dictionary>(); + private Dictionary> _eventHandlers; /// /// Gets the interactive parent of the object for bubbling and tunnelling events. /// IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; + private Dictionary> EventHandlers + { + get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } + } + /// /// Adds a handler for the specified routed event. /// @@ -43,10 +47,10 @@ namespace Avalonia.Interactivity List subscriptions; - if (!_eventHandlers.TryGetValue(routedEvent, out subscriptions)) + if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) { subscriptions = new List(); - _eventHandlers.Add(routedEvent, subscriptions); + EventHandlers.Add(routedEvent, subscriptions); } var sub = new EventSubscription @@ -89,9 +93,9 @@ namespace Avalonia.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List subscriptions; + List subscriptions = null; - if (_eventHandlers.TryGetValue(routedEvent, out subscriptions)) + if (_eventHandlers?.TryGetValue(routedEvent, out subscriptions) == true) { subscriptions.RemoveAll(x => x.Handler == handler); } @@ -181,9 +185,9 @@ namespace Avalonia.Interactivity e.RoutedEvent.InvokeRaised(this, e); - List subscriptions; + List subscriptions = null; - if (_eventHandlers.TryGetValue(e.RoutedEvent, out subscriptions)) + if (_eventHandlers?.TryGetValue(e.RoutedEvent, out subscriptions) == true) { foreach (var sub in subscriptions.ToList()) { From 426cd8c9ddeee12685cea2e5e534b19f4e694dec Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 25 Aug 2017 12:55:36 +0300 Subject: [PATCH 07/14] ToolTip: IsOpen, Placement, Offset --- samples/ControlCatalog/Pages/ToolTipPage.xaml | 48 ++-- src/Avalonia.Controls/Primitives/Popup.cs | 26 +- src/Avalonia.Controls/ToolTip.cs | 236 ++++++++++++------ src/Avalonia.Controls/ToolTipService.cs | 98 ++++++++ 4 files changed, 300 insertions(+), 108 deletions(-) create mode 100644 src/Avalonia.Controls/ToolTipService.cs diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 29df11510c..79114bc9de 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -1,22 +1,34 @@ - - ToolTip - A control which pops up a hint when a control is hovered + + ToolTip + A control which pops up a hint when a control is hovered - - - - - ToolTip - A control which pops up a hint when a control is hovered - - - Hover Here - + + + + + + ToolTip + A control which pops up a hint when a control is hovered + + + Hover Here + + + And Here + + - \ No newline at end of file diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index daea187a69..5cd3b22fc9 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -277,7 +277,7 @@ namespace Avalonia.Controls.Primitives { base.OnDetachedFromLogicalTree(e); _topLevel = null; - + if (_popupRoot != null) { ((ISetLogicalParent)_popupRoot).SetParent(null); @@ -327,34 +327,40 @@ namespace Avalonia.Controls.Primitives /// /// The popup's position in screen coordinates. protected virtual Point GetPosition() + { + return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + HorizontalOffset, VerticalOffset); + } + + internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) { var zero = default(Point); - var mode = PlacementMode; - var target = PlacementTarget ?? this.GetVisualParent(); + var mode = placement; if (target?.GetVisualRoot() == null) { mode = PlacementMode.Pointer; - } + } switch (mode) { case PlacementMode.Pointer: - if(PopupRoot != null) + if (popupRoot != null) { // Scales the Horizontal and Vertical offset to screen co-ordinates. - var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling); - return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset; + var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling, + verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling); + return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset; } return default(Point); case PlacementMode.Bottom: - - return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero; + return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? + zero; case PlacementMode.Right: - return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero; + return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero; default: throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index e1b69637af..e45f30f818 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -3,11 +3,7 @@ using System; using System.Reactive.Linq; -using System.Reactive.Subjects; using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -29,29 +25,50 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterAttached("Tip"); /// - /// The popup window used to display the active tooltip. + /// Defines the ToolTip.IsOpen attached property. /// - private static PopupRoot s_popup; + public static readonly AttachedProperty IsOpenProperty = + AvaloniaProperty.RegisterAttached("IsOpen"); /// - /// The control that the currently visible tooltip is attached to. + /// Defines the ToolTip.Placement property. /// - private static Control s_current; + public static readonly AttachedProperty PlacementProperty = + AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer); /// - /// Observable fired when a tooltip should be displayed for a control. The output from this - /// observable is throttled and calls when the time - /// period expires. + /// Defines the ToolTip.HorizontalOffset property. /// - private static readonly Subject s_show = new Subject(); + public static readonly AttachedProperty HorizontalOffsetProperty = + AvaloniaProperty.RegisterAttached("HorizontalOffset"); + + /// + /// Defines the ToolTip.VerticalOffset property. + /// + public static readonly AttachedProperty VerticalOffsetProperty = + AvaloniaProperty.RegisterAttached("VerticalOffset", 20); + + /// + /// Defines the ToolTip.ShowDelay property. + /// + public static readonly AttachedProperty ShowDelayProperty = + AvaloniaProperty.RegisterAttached("ShowDelay", 400); + + /// + /// Stores the curernt instance in the control. + /// + private static readonly AttachedProperty ToolTipProperty = + AvaloniaProperty.RegisterAttached("ToolTip"); + + private PopupRoot _popup; /// /// Initializes static members of the class. /// static ToolTip() { - TipProperty.Changed.Subscribe(TipChanged); - s_show.Throttle(TimeSpan.FromSeconds(0.5), AvaloniaScheduler.Instance).Subscribe(ShowToolTip); + TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); + IsOpenProperty.Changed.Subscribe(IsOpenChanged); } /// @@ -77,101 +94,160 @@ namespace Avalonia.Controls } /// - /// called when the property changes on a control. + /// Gets the value of the ToolTip.IsOpen attached property. /// - /// The event args. - private static void TipChanged(AvaloniaPropertyChangedEventArgs e) + /// The control to get the property from. + /// + /// A value indicating whether the tool tip is visible. + /// + public static bool GetIsOpen(Control element) { - var control = (Control)e.Sender; + return element.GetValue(IsOpenProperty); + } - if (e.OldValue != null) - { - control.PointerEnter -= ControlPointerEnter; - control.PointerLeave -= ControlPointerLeave; - } + /// + /// Sets the value of the ToolTip.IsOpen attached property. + /// + /// The control to get the property from. + /// A value indicating whether the tool tip is visible. + public static void SetIsOpen(Control element, bool value) + { + element.SetValue(IsOpenProperty, value); + } - if (e.NewValue != null) - { - control.PointerEnter += ControlPointerEnter; - control.PointerLeave += ControlPointerLeave; - } + /// + /// Gets the value of the ToolTip.Placement attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static PlacementMode GetPlacement(Control element) + { + return element.GetValue(PlacementProperty); } /// - /// Shows a tooltip for the specified control. + /// Sets the value of the ToolTip.Placement attached property. /// - /// The control. - private static void ShowToolTip(Control control) + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetPlacement(Control element, PlacementMode value) { - if (control != null && control.IsVisible && control.GetVisualRoot() != null) - { - var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control); + element.SetValue(PlacementProperty, value); + } - if (cp.HasValue && control.IsVisible && new Rect(control.Bounds.Size).Contains(cp.Value)) - { - var position = control.PointToScreen(cp.Value) + new Vector(0, 22); - - if (s_popup == null) - { - s_popup = new PopupRoot(); - s_popup.Content = new ToolTip(); - } - else - { - ((ISetLogicalParent)s_popup).SetParent(null); - } - - ((ISetLogicalParent)s_popup).SetParent(control); - ((ToolTip)s_popup.Content).Content = GetTip(control); - s_popup.Position = position; - s_popup.Show(); - - s_current = control; - } - } + /// + /// Gets the value of the ToolTip.HorizontalOffset attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static double GetHorizontalOffset(Control element) + { + return element.GetValue(HorizontalOffsetProperty); + } + + /// + /// Sets the value of the ToolTip.HorizontalOffset attached property. + /// + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetHorizontalOffset(Control element, double value) + { + element.SetValue(HorizontalOffsetProperty, value); + } + + /// + /// Gets the value of the ToolTip.VerticalOffset attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static double GetVerticalOffset(Control element) + { + return element.GetValue(VerticalOffsetProperty); + } + + /// + /// Sets the value of the ToolTip.VerticalOffset attached property. + /// + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetVerticalOffset(Control element, double value) + { + element.SetValue(VerticalOffsetProperty, value); } /// - /// Called when the pointer enters a control with an attached tooltip. + /// Gets the value of the ToolTip.ShowDelay attached property. /// - /// The event sender. - /// The event args. - private static void ControlPointerEnter(object sender, PointerEventArgs e) + /// The control to get the property from. + /// + /// A value indicating the time, in milliseconds, before a tool tip opens. + /// + public static int GetShowDelay(Control element) { - s_current = (Control)sender; - s_show.OnNext(s_current); + return element.GetValue(ShowDelayProperty); } /// - /// Called when the pointer leaves a control with an attached tooltip. + /// Sets the value of the ToolTip.ShowDelay attached property. /// - /// The event sender. - /// The event args. - private static void ControlPointerLeave(object sender, PointerEventArgs e) + /// The control to get the property from. + /// A value indicating the time, in milliseconds, before a tool tip opens. + public static void SetShowDelay(Control element, int value) { - var control = (Control)sender; + element.SetValue(ShowDelayProperty, value); + } + + private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; - if (control == s_current) + if ((bool)e.NewValue) { - if (s_popup != null) + var tip = GetTip(control); + if (tip == null) return; + + var toolTip = control.GetValue(ToolTipProperty); + if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) { - DisposeTooltip(); - s_show.OnNext(null); + toolTip?.Close(); + + toolTip = tip as ToolTip ?? new ToolTip { Content = tip }; + control.SetValue(ToolTipProperty, toolTip); } + + toolTip.Open(control); + } + else + { + var toolTip = control.GetValue(ToolTipProperty); + toolTip?.Close(); } } - private static void DisposeTooltip() + private void Open(Control control) { - if (s_popup != null) - { - // Clear the ToolTip's Content in case it has control content: this will - // reset its visual parent allowing it to be used again. - ((ToolTip)s_popup.Content).Content = null; + Close(); + + _popup = new PopupRoot { Content = this }; + ((ISetLogicalParent)_popup).SetParent(control); + _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup, + GetHorizontalOffset(control), GetVerticalOffset(control)); + _popup.Show(); + } - // Dispose of the popup. - s_popup.Dispose(); - s_popup = null; + private void Close() + { + if (_popup != null) + { + _popup.Content = null; + _popup.Hide(); + _popup = null; } } } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs new file mode 100644 index 0000000000..bfd7ef0f33 --- /dev/null +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -0,0 +1,98 @@ +using System; +using Avalonia.Input; +using Avalonia.Threading; + +namespace Avalonia.Controls +{ + /// + /// Handeles interaction with controls. + /// + internal sealed class ToolTipService + { + public static ToolTipService Instance { get; } = new ToolTipService(); + + private DispatcherTimer _timer; + + private ToolTipService() { } + + /// + /// called when the property changes on a control. + /// + /// The event args. + internal void TipChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + + if (e.OldValue != null) + { + control.PointerEnter -= ControlPointerEnter; + control.PointerLeave -= ControlPointerLeave; + } + + if (e.NewValue != null) + { + control.PointerEnter += ControlPointerEnter; + control.PointerLeave += ControlPointerLeave; + } + } + + /// + /// Called when the pointer enters a control with an attached tooltip. + /// + /// The event sender. + /// The event args. + private void ControlPointerEnter(object sender, PointerEventArgs e) + { + StopTimer(); + + var control = (Control)sender; + var showDelay = ToolTip.GetShowDelay(control); + if (showDelay == 0) + { + Open(control); + } + else + { + StartShowTimer(showDelay, control); + } + } + + /// + /// Called when the pointer leaves a control with an attached tooltip. + /// + /// The event sender. + /// The event args. + private void ControlPointerLeave(object sender, PointerEventArgs e) + { + var control = (Control)sender; + Close(control); + } + + private void StartShowTimer(int showDelay, Control control) + { + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) }; + _timer.Tick += (o, e) => Open(control); + _timer.Start(); + } + + private void Open(Control control) + { + StopTimer(); + + ToolTip.SetIsOpen(control, true); + } + + private void Close(Control control) + { + StopTimer(); + + ToolTip.SetIsOpen(control, false); + } + + private void StopTimer() + { + _timer?.Stop(); + _timer = null; + } + } +} \ No newline at end of file From 8c9dd5c81e7100df3a4de8e579da05ebf42f2440 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 01:06:28 +0200 Subject: [PATCH 08/14] Update moq to 4.7.99. This fixes a bunch of build warnings. --- build/Moq.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Moq.props b/build/Moq.props index 55242d922e..7de9b6b6ba 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + From 3b030f19d1ed47c344845581d5e5bcb1d24da6ce Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 02:19:56 +0200 Subject: [PATCH 09/14] Fix Avalonia.Input.UnitTests.csproj Previously this project was spewing loads of ``` Severity Code Description Project File Line Suppression State Warning CS1701 Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Avalonia.Controls' matches identity 'System.Runtime, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy Avalonia.Input.UnitTests(net461) D:\projects\Avalonia\tests\Avalonia.Input.UnitTests\CSC 1 Active` ``` Warnings. Not sure why this was, but updating the `csproj` to be in the same format as the other unit tests stops these warnings and reduces the warnings for a solution build from 5755(!) to 238. --- .../Avalonia.Input.UnitTests.csproj | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 186d293b96..64d0efe69b 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -3,35 +3,6 @@ net461;netcoreapp1.1 Library - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Input.UnitTests.XML - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - @@ -49,6 +20,6 @@ - + - \ No newline at end of file + From 5827f3c28428ab310745f79efca14eaa14bfa8e8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 16:22:34 +0200 Subject: [PATCH 10/14] Missed file from merge --- tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 52404e7ed2..99cf4e98d2 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -19,8 +19,7 @@ - - + \ No newline at end of file From 31c9446429423aaea3451528781105da7a22f353 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sat, 2 Sep 2017 15:11:44 +0300 Subject: [PATCH 11/14] Improve demo --- samples/ControlCatalog/Pages/ToolTipPage.xaml | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 79114bc9de..5cf7fee4d1 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -4,13 +4,26 @@ ToolTip A control which pops up a hint when a control is hovered - - + + + Hover Here + + A control which pops up a hint when a control is hovered - Hover Here - - - And Here + ToolTip bottom placement - + \ No newline at end of file From 1d4aabb739f62142d61ca3aa14fee66eb5c125b8 Mon Sep 17 00:00:00 2001 From: Jurjen Biewenga Date: Sun, 3 Sep 2017 19:39:32 +0200 Subject: [PATCH 12/14] Replaced Set/GetWindowLong with Set/GetWindowLongPtr for 64 bit support --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 4 ++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 5473ef9bea..a8c9ec101c 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -699,10 +699,10 @@ namespace Avalonia.Win32.Interop public static extern int GetSystemMetrics(SystemMetric smIndex); [DllImport("user32.dll", SetLastError = true)] - public static extern uint GetWindowLong(IntPtr hWnd, int nIndex); + public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll", SetLastError = true)] - public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint value); + public static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value); [DllImport("user32.dll", SetLastError = true)] public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4a30d48878..4e67b47a36 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -66,8 +66,8 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, -16); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20); + var style = UnmanagedMethods.GetWindowLongPtr(_hwnd, -16); + var exStyle = UnmanagedMethods.GetWindowLongPtr(_hwnd, -20); var padding = new UnmanagedMethods.RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) @@ -219,7 +219,7 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLongPtr(_hwnd, -16); style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; @@ -235,7 +235,7 @@ namespace Avalonia.Win32 Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + UnmanagedMethods.SetWindowLongPtr(_hwnd, -16, (uint)style); if (value) { From 8f006ff655d37687f3676dc6c83802a151196660 Mon Sep 17 00:00:00 2001 From: Jurjen Biewenga Date: Sun, 3 Sep 2017 20:44:08 +0200 Subject: [PATCH 13/14] Added utility functions to pinvoke the correct function based on bitness --- .../Interop/UnmanagedMethods.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index a8c9ec101c..4f69e022e2 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -701,9 +701,39 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLong")] + public static extern uint GetWindowLong32b(IntPtr hWnd, int nIndex); + + public static uint GetWindowLong(IntPtr hWnd, int nIndex) + { + if(IntPtr.Size == 4) + { + return GetWindowLong32b(hWnd, nIndex); + } + else + { + return GetWindowLongPtr(hWnd, nIndex); + } + } + + [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")] + public static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value); + [DllImport("user32.dll", SetLastError = true)] public static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value); + public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value) + { + if (IntPtr.Size == 4) + { + return SetWindowLong32b(hWnd, nIndex, value); + } + else + { + return SetWindowLongPtr(hWnd, nIndex, value); + } + } + [DllImport("user32.dll", SetLastError = true)] public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); From 7605909c79c4483530e611196b7ac5950a94df7b Mon Sep 17 00:00:00 2001 From: Jurjen Biewenga Date: Sun, 3 Sep 2017 21:14:47 +0200 Subject: [PATCH 14/14] Made the pinvoke calls private Changed SetWindowLongPtr calls to SetWindowLong in Win32/WindowImpl --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 4 ++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 4f69e022e2..f58beadc0b 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -717,10 +717,10 @@ namespace Avalonia.Win32.Interop } [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")] - public static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value); + private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value); [DllImport("user32.dll", SetLastError = true)] - public static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value); + private static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value); public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value) { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4e67b47a36..4a30d48878 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -66,8 +66,8 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLongPtr(_hwnd, -16); - var exStyle = UnmanagedMethods.GetWindowLongPtr(_hwnd, -20); + var style = UnmanagedMethods.GetWindowLong(_hwnd, -16); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20); var padding = new UnmanagedMethods.RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) @@ -219,7 +219,7 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLongPtr(_hwnd, -16); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; @@ -235,7 +235,7 @@ namespace Avalonia.Win32 Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLongPtr(_hwnd, -16, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); if (value) {