From 70440517ca2dda3f46d87d94f9e7f326d79299ca Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 20 Jun 2018 23:53:54 +0200 Subject: [PATCH 01/11] Initial implementation of StringFormat. XAML tests failing because of https://github.com/cwensley/Portable.Xaml/pull/106 --- .../Data/Converters/StringFormatConverter.cs | 30 ++++ .../MarkupExtensions/BindingExtension.cs | 3 + src/Markup/Avalonia.Markup/Data/Binding.cs | 21 ++- .../Data/BindingTests_Converters.cs | 146 ++++++++++++++++++ .../Xaml/BindingTests.cs | 22 +++ 5 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Data/Converters/StringFormatConverter.cs create mode 100644 tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs diff --git a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs new file mode 100644 index 0000000000..67ecacd1c7 --- /dev/null +++ b/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; + +namespace Avalonia.Data.Converters +{ + public class StringFormatConverter : IValueConverter + { + public StringFormatConverter(string format, IValueConverter inner) + { + Contract.Requires(format != null); + + Format = format; + Inner = inner; + } + + public IValueConverter Inner { get; } + public string Format { get; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + value = Inner?.Convert(value, targetType, parameter, culture) ?? value; + return string.Format(Format, value, culture); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Two way bindings are not supported with a string format"); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 98203deebe..4349b5e82e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -45,6 +45,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Path = pathInfo.Path, Priority = Priority, Source = Source, + StringFormat = StringFormat, RelativeSource = pathInfo.RelativeSource ?? RelativeSource, DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider)) }; @@ -226,6 +227,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public object Source { get; set; } + public string StringFormat { get; set; } + public RelativeSource RelativeSource { get; set; } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 96fc2986e8..a39dbe72cd 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -84,6 +84,11 @@ namespace Avalonia.Data /// public object Source { get; set; } + /// + /// Gets or sets the string format. + /// + public string StringFormat { get; set; } + public WeakReference DefaultAnchor { get; set; } /// @@ -158,11 +163,23 @@ namespace Avalonia.Data fallback = null; } + var converter = Converter; + var targetType = targetProperty?.PropertyType ?? typeof(object); + + // We only respect `StringFormat` if the type of the property we're assigning to will + // accept a string. Note that this is slightly different to WPF in that WPF only applies + // `StringFormat` for target type `string` (not `object`). + if (!string.IsNullOrWhiteSpace(StringFormat) && + (targetType == typeof(string) || targetType == typeof(object))) + { + converter = new StringFormatConverter(StringFormat, converter); + } + var subject = new BindingExpression( observer, - targetProperty?.PropertyType ?? typeof(object), + targetType, fallback, - Converter ?? DefaultValueConverter.Instance, + converter ?? DefaultValueConverter.Instance, ConverterParameter, Priority); diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs new file mode 100644 index 0000000000..814618d764 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -0,0 +1,146 @@ +// 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; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class BindingTests_Converters + { + [Fact] + public void Converter_Should_Be_Used() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + Converter = StringConverters.NullOrEmpty, + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TextProperty).Observable; + + Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter); + } + + public class When_Binding_To_String + { + [Fact] + public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TextProperty).Observable; + + Assert.IsType(expressionObserver.Converter); + } + } + + public class When_Binding_To_Object + { + [Fact] + public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TagProperty).Observable; + + Assert.IsType(expressionObserver.Converter); + } + } + + public class When_Binding_To_Non_String_Or_Object + { + [Fact] + public void StringFormatConverter_Should_Not_Be_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.MarginProperty).Observable; + + Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter); + } + } + + [Fact] + public void StringFormat_Should_Be_Applied() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + textBlock.Bind(TextBlock.TextProperty, target); + + Assert.Equal("Hello foo", textBlock.Text); + } + + [Fact] + public void StringFormat_Should_Be_Applied_After_Converter() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + Converter = StringConverters.NotNullOrEmpty, + StringFormat = "Hello {0}", + }; + + textBlock.Bind(TextBlock.TextProperty, target); + + Assert.Equal("Hello True", textBlock.Text); + } + + private class Class1 + { + public string Foo { get; set; } = "foo"; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index 568c6482f5..14952bca09 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -281,5 +281,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); } } + + [Fact] + public void Binding_To_TextBlock_Text_With_StringConverter_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + textBlock.DataContext = new { Foo = "world" }; + window.ApplyTemplate(); + + Assert.Equal("Hello world", textBlock.Text); + } + } } } \ No newline at end of file From 740b6073f0de1f14da38b620302fb5797130077e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 16:42:24 +0200 Subject: [PATCH 02/11] Escape braces in format string. --- tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index 14952bca09..18cb3c74fc 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -291,7 +291,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - + "; var loader = new AvaloniaXamlLoader(); var window = (Window)loader.Load(xaml); From 6a14747eb658d59928a8ab3f9c294e29bfcfb949 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:24:18 +0200 Subject: [PATCH 03/11] StringFormatConverter -> StringFormatValueConverter --- ...StringFormatConverter.cs => StringFormatValueConverter.cs} | 4 ++-- src/Markup/Avalonia.Markup/Data/Binding.cs | 2 +- .../Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Avalonia.Base/Data/Converters/{StringFormatConverter.cs => StringFormatValueConverter.cs} (85%) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs similarity index 85% rename from src/Avalonia.Base/Data/Converters/StringFormatConverter.cs rename to src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 67ecacd1c7..3703929356 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -3,9 +3,9 @@ using System.Globalization; namespace Avalonia.Data.Converters { - public class StringFormatConverter : IValueConverter + public class StringFormatValueConverter : IValueConverter { - public StringFormatConverter(string format, IValueConverter inner) + public StringFormatValueConverter(string format, IValueConverter inner) { Contract.Requires(format != null); diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index a39dbe72cd..62cff4d768 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -172,7 +172,7 @@ namespace Avalonia.Data if (!string.IsNullOrWhiteSpace(StringFormat) && (targetType == typeof(string) || targetType == typeof(object))) { - converter = new StringFormatConverter(StringFormat, converter); + converter = new StringFormatValueConverter(StringFormat, converter); } var subject = new BindingExpression( diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 814618d764..e98f9f895c 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -51,7 +51,7 @@ namespace Avalonia.Markup.UnitTests.Data textBlock, TextBlock.TextProperty).Observable; - Assert.IsType(expressionObserver.Converter); + Assert.IsType(expressionObserver.Converter); } } @@ -74,7 +74,7 @@ namespace Avalonia.Markup.UnitTests.Data textBlock, TextBlock.TagProperty).Observable; - Assert.IsType(expressionObserver.Converter); + Assert.IsType(expressionObserver.Converter); } } From 09454783577847b58b780bdd695a3d08a80e5150 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:38:39 +0200 Subject: [PATCH 04/11] Fix grammar. --- .../Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index e98f9f895c..123aadfda5 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -35,7 +35,7 @@ namespace Avalonia.Markup.UnitTests.Data public class When_Binding_To_String { [Fact] - public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat() { var textBlock = new TextBlock { @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data public class When_Binding_To_Object { [Fact] - public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat() { var textBlock = new TextBlock { From 63a628731e533d5aa41c367b9cfae19c4db83944 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:56:37 +0200 Subject: [PATCH 05/11] Added XML docs for StringFormatValueConverter. --- .../Converters/StringFormatValueConverter.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 3703929356..643f26a3c1 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -3,8 +3,18 @@ using System.Globalization; namespace Avalonia.Data.Converters { + /// + /// A value converter which calls + /// public class StringFormatValueConverter : IValueConverter { + /// + /// Initializes a new instance of the class. + /// + /// The format string. + /// + /// An optional inner converter to be called before the format takes place. + /// public StringFormatValueConverter(string format, IValueConverter inner) { Contract.Requires(format != null); @@ -13,15 +23,24 @@ namespace Avalonia.Data.Converters Inner = inner; } + /// + /// Gets an inner value converter which will be called before the string format takes place. + /// public IValueConverter Inner { get; } + + /// + /// Gets the format string. + /// public string Format { get; } + /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { value = Inner?.Convert(value, targetType, parameter, culture) ?? value; return string.Format(Format, value, culture); } + /// public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException("Two way bindings are not supported with a string format"); From d0bc9f19a3067f0fbf0891f2c45be731baecb58f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 22 Jun 2018 09:56:27 +0200 Subject: [PATCH 06/11] Use correct parameter order! --- src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 643f26a3c1..fc695762b8 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -37,7 +37,7 @@ namespace Avalonia.Data.Converters public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { value = Inner?.Convert(value, targetType, parameter, culture) ?? value; - return string.Format(Format, value, culture); + return string.Format(culture, Format, value); } /// From 5aa219be00c9776b7462114b16932319cee0dcd9 Mon Sep 17 00:00:00 2001 From: WojciechKrysiak Date: Mon, 13 Aug 2018 07:23:04 +0200 Subject: [PATCH 07/11] Fix windows for Mono.Mac (#1804) - handle closing - handle showing dialogs --- src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 25 +++++++--- src/OSX/Avalonia.MonoMac/WindowImpl.cs | 58 +++++++++++++++------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index 89cef59b53..e7d82ae25e 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -14,6 +14,8 @@ namespace Avalonia.MonoMac private readonly ManagedWindowResizeDragHelper _managedDrag; public CustomWindow Window { get; private set; } + private bool _closed; + public WindowBaseImpl() { _managedDrag = new ManagedWindowResizeDragHelper(this, _ => { }, ResizeForManagedDrag); @@ -78,9 +80,8 @@ namespace Avalonia.MonoMac public override void WillClose(global::MonoMac.Foundation.NSNotification notification) { - _impl.Window.Dispose(); - _impl.Window = null; - _impl.Dispose(); + _impl._closed = true; + _impl.Closed?.Invoke(); } public override CGRect WillUseStandardFrame(NSWindow window, CGRect newFrame) @@ -94,8 +95,19 @@ namespace Avalonia.MonoMac { return true; } + + public override void DidResize(NSNotification notification) + { + _impl.OnResized(); + } } + /// + /// As you can't combine NSWindowDelegate overrides and events this is a workaround + /// + protected virtual void OnResized() + { + } public Point Position { @@ -185,12 +197,11 @@ namespace Avalonia.MonoMac return cocoaScreenPoint.ConvertPointY().ToAvaloniaPoint(); } - - public override void Dispose() { - Window?.Close(); - Window?.Dispose(); + if (!_closed) + Window.Close(); + Window.Dispose(); base.Dispose(); } } diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index 3900af8709..89b5550688 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -26,18 +26,19 @@ namespace Avalonia.MonoMac }); Window.SetCanBecomeKeyAndMain(); - - Window.DidResize += delegate - { - var windowState = Window.IsMiniaturized ? WindowState.Minimized - : (IsZoomed ? WindowState.Maximized : WindowState.Normal); + } - if (windowState != _lastWindowState) - { - _lastWindowState = windowState; - WindowStateChanged?.Invoke(windowState); - } - }; + + protected override void OnResized() + { + var windowState = Window.IsMiniaturized ? WindowState.Minimized + : (IsZoomed ? WindowState.Maximized : WindowState.Normal); + + if (windowState != _lastWindowState) + { + _lastWindowState = windowState; + WindowStateChanged?.Invoke(windowState); + } } public WindowState WindowState @@ -129,24 +130,47 @@ namespace Avalonia.MonoMac class ModalDisposable : IDisposable { readonly WindowImpl _impl; + readonly IntPtr _modalSession; + bool disposed; - public ModalDisposable(WindowImpl impl) + public ModalDisposable(WindowImpl impl, IntPtr modalSession) { _impl = impl; + _modalSession = modalSession; + } + + public void Continue() + { + if (disposed) + return; + + var response = (NSRunResponse)NSApplication.SharedApplication.RunModalSession(_modalSession); + if (response == NSRunResponse.Continues) + { + Dispatcher.UIThread.Post(Continue, DispatcherPriority.ContextIdle); + } + else + { + Logging.Logger.Log(Logging.LogEventLevel.Debug, "MonoMac", this, "Modal session ended"); + } } public void Dispose() { + Logging.Logger.Log(Logging.LogEventLevel.Debug, "MonoMac", this, "ModalDisposable disposed"); _impl.Window.OrderOut(_impl.Window); + NSApplication.SharedApplication.EndModalSession(_modalSession); + disposed = true; } } public IDisposable ShowDialog() { - //TODO: Investigate how to return immediately. - // May be add some magic to our run loop or something - NSApplication.SharedApplication.RunModalForWindow(Window); - return new ModalDisposable(this); + var session = NSApplication.SharedApplication.BeginModalSession(Window); + var disposable = new ModalDisposable(this, session); + Dispatcher.UIThread.Post(disposable.Continue, DispatcherPriority.ContextIdle); + + return disposable; } } -} \ No newline at end of file +} From c5a21590211e45e345d052340f3f9cde8cfbc5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 13 Aug 2018 14:48:09 +0200 Subject: [PATCH 08/11] Add support for Direct2D1 for Avalonia.DotNetCoreRuntime --- src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj index f80462e958..0aed0a9717 100644 --- a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj +++ b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj @@ -10,6 +10,7 @@ + From 9c6e69914dbc8f6003e319d0d2c2f847d5fd089c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Aug 2018 18:06:42 +0100 Subject: [PATCH 09/11] fixes an occasional crash with popup root "SnapInsideScreenEdges" --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 457a7bd4b4..996eb6204d 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -83,9 +83,7 @@ namespace Avalonia.Controls.Primitives /// public void SnapInsideScreenEdges() { - var window = this.GetSelfAndLogicalAncestors().OfType().First(); - - var screen = window.Screens.ScreenFromPoint(Position); + var screen = Application.Current.MainWindow.Screens.ScreenFromPoint(Position); var screenX = Position.X + Bounds.Width - screen.Bounds.X; var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; From 7d28d1b2abfecfd6e6ca8e1f44739a256c4fe09d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Aug 2018 18:21:02 +0100 Subject: [PATCH 10/11] fix failing unit test. --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 23 +++++++++++-------- .../ContextMenuTests.cs | 5 +++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 996eb6204d..0ae4be5550 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -83,19 +83,22 @@ namespace Avalonia.Controls.Primitives /// public void SnapInsideScreenEdges() { - var screen = Application.Current.MainWindow.Screens.ScreenFromPoint(Position); + var screen = Application.Current.MainWindow?.Screens.ScreenFromPoint(Position); - var screenX = Position.X + Bounds.Width - screen.Bounds.X; - var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; - - if (screenX > screen.Bounds.Width) + if (screen != null) { - Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); - } + var screenX = Position.X + Bounds.Width - screen.Bounds.X; + var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; - if (screenY > screen.Bounds.Height) - { - Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + if (screenX > screen.Bounds.Width) + { + Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + } + + if (screenY > screen.Bounds.Height) + { + Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 94d5caa720..66f0cc3a40 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -64,7 +64,10 @@ namespace Avalonia.Controls.UnitTests { ContextMenu = sut }; - new Window { Content = target }; + + var window = new Window { Content = target }; + + Avalonia.Application.Current.MainWindow = window; target.RaiseEvent(new PointerReleasedEventArgs { From dbe8f748c963f201d398da7bd9b4f44c97b5d046 Mon Sep 17 00:00:00 2001 From: WojciechKrysiak Date: Mon, 13 Aug 2018 20:46:22 +0200 Subject: [PATCH 11/11] Single menu context menus working. (#1808) --- src/OSX/Avalonia.MonoMac/PopupImpl.cs | 14 +++++++++++++- src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 16 ++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/PopupImpl.cs b/src/OSX/Avalonia.MonoMac/PopupImpl.cs index ba4b7f0eac..267e47a6a0 100644 --- a/src/OSX/Avalonia.MonoMac/PopupImpl.cs +++ b/src/OSX/Avalonia.MonoMac/PopupImpl.cs @@ -8,11 +8,23 @@ namespace Avalonia.MonoMac public PopupImpl() { UpdateStyle(); + Window.Level = NSWindowLevel.PopUpMenu; } protected override NSWindowStyle GetStyle() { return NSWindowStyle.Borderless; } + + protected override CustomWindow CreateCustomWindow() => new CustomPopupWindow(this); + + private class CustomPopupWindow : CustomWindow + { + public CustomPopupWindow(WindowBaseImpl impl) + : base(impl) + { } + + public override bool WorksWhenModal() => true; + } } -} \ No newline at end of file +} diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index e7d82ae25e..d3053a6af1 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -19,14 +19,13 @@ namespace Avalonia.MonoMac public WindowBaseImpl() { _managedDrag = new ManagedWindowResizeDragHelper(this, _ => { }, ResizeForManagedDrag); - Window = new CustomWindow(this) - { - StyleMask = NSWindowStyle.Titled, - BackingType = NSBackingStore.Buffered, - ContentView = View, - // ReSharper disable once VirtualMemberCallInConstructor - Delegate = CreateWindowDelegate() - }; + // ReSharper disable once VirtualMemberCallInConstructor + Window = CreateCustomWindow(); + Window.StyleMask = NSWindowStyle.Titled; + Window.BackingType = NSBackingStore.Buffered; + Window.ContentView = View; + // ReSharper disable once VirtualMemberCallInConstructor + Window.Delegate = CreateWindowDelegate(); } public class CustomWindow : NSWindow @@ -57,6 +56,7 @@ namespace Avalonia.MonoMac public void SetCanBecomeKeyAndMain() => _canBecomeKeyAndMain = true; } + protected virtual CustomWindow CreateCustomWindow() => new CustomWindow(this); protected virtual NSWindowDelegate CreateWindowDelegate() => new WindowBaseDelegate(this); public class WindowBaseDelegate : NSWindowDelegate