diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10645.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10645.cs new file mode 100644 index 000000000000..b1cbd54d6f7d --- /dev/null +++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue10645.cs @@ -0,0 +1,24 @@ +using Microsoft.Maui.Controls; + +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 10645, "Image is not centered in AspectFill mode", PlatformAffected.UWP)] + public class Issue10645 : TestContentPage + { + protected override void Init() + { + Content = + new Grid() + { + new Image() + { + AutomationId = "AspectFillImage", + Aspect = Microsoft.Maui.Aspect.AspectFill, + WidthRequest = 100, + HeightRequest = 200, + Source = "dotnet_bot.png", + } + }; + } + } +} diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue10645.cs b/src/Controls/tests/UITests/Tests/Issues/Issue10645.cs new file mode 100644 index 000000000000..c39f59913c61 --- /dev/null +++ b/src/Controls/tests/UITests/Tests/Issues/Issue10645.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.AppiumTests.Issues +{ + public class Issue10645 : _IssuesUITest + { + public Issue10645(TestDevice device) : base(device) + { + } + + public override string Issue => "Image is not centered in AspectFill mode"; + + [Test] + [Category(UITestCategories.ActionSheet)] + public void Issue10645Test() + { + this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Android, TestDevice.Mac, TestDevice.iOS }, "Only affects Windows."); + + App.WaitForElement("AspectFillImage", timeout: TimeSpan.FromSeconds(4)); + + VerifyScreenshot(); + } + } +} diff --git a/src/Controls/tests/UITests/snapshots/windows/Issue10645Test.png b/src/Controls/tests/UITests/snapshots/windows/Issue10645Test.png new file mode 100644 index 000000000000..0ad02ca2c9e3 Binary files /dev/null and b/src/Controls/tests/UITests/snapshots/windows/Issue10645Test.png differ diff --git a/src/Core/src/Handlers/Image/ImageHandler.Windows.cs b/src/Core/src/Handlers/Image/ImageHandler.Windows.cs index 233f26e2cce3..ba33708a3921 100644 --- a/src/Core/src/Handlers/Image/ImageHandler.Windows.cs +++ b/src/Core/src/Handlers/Image/ImageHandler.Windows.cs @@ -8,8 +8,10 @@ namespace Microsoft.Maui.Handlers { public partial class ImageHandler : ViewHandler { + /// protected override WImage CreatePlatformView() => new WImage(); + /// protected override void ConnectHandler(WImage platformView) { platformView.ImageOpened += OnImageOpened; @@ -17,6 +19,7 @@ protected override void ConnectHandler(WImage platformView) base.ConnectHandler(platformView); } + /// protected override void DisconnectHandler(WImage platformView) { platformView.ImageOpened -= OnImageOpened; @@ -25,25 +28,113 @@ protected override void DisconnectHandler(WImage platformView) SourceLoader.Reset(); } + /// public override bool NeedsContainer => VirtualView?.Background != null || + VirtualView?.Aspect == Aspect.AspectFill || base.NeedsContainer; + /// + protected override void SetupContainer() + { + base.SetupContainer(); + + // VerticalAlignment only works when the child's Height is Auto + PlatformView.Height = Primitives.Dimension.Unset; + + UpdateValue(nameof(IView.Height)); + UpdateValue(nameof(IView.Width)); + } + + /// + protected override void RemoveContainer() + { + base.RemoveContainer(); + + UpdateValue(nameof(IView.Height)); + UpdateValue(nameof(IView.Width)); + } + + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. + public static void MapHeight(IImageHandler handler, IImage view) + { + // VerticalAlignment only works when the container's Height is set and the child's Height is Auto. The child's Height + // is set to Auto when the container is introduced. + if (handler.ContainerView is FrameworkElement container) + { + container.Height = view.Height; + handler.PlatformView.Height = Primitives.Dimension.Unset; + } + else + { + ViewHandler.MapHeight(handler, view); + } + } + + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. + public static void MapWidth(IImageHandler handler, IImage view) + { + if (handler.ContainerView is FrameworkElement container) + { + container.Width = view.Width; + } + else + { + ViewHandler.MapWidth(handler, view); + } + } + + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. public static void MapBackground(IImageHandler handler, IImage image) { handler.UpdateValue(nameof(IViewHandler.ContainerView)); handler.ToPlatform().UpdateBackground(image); } - public static void MapAspect(IImageHandler handler, IImage image) => + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. + public static void MapAspect(IImageHandler handler, IImage image) + { + handler.UpdateValue(nameof(IViewHandler.ContainerView)); handler.PlatformView?.UpdateAspect(image); + } + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. public static void MapIsAnimationPlaying(IImageHandler handler, IImage image) => handler.PlatformView?.UpdateIsAnimationPlaying(image); + /// + /// Maps the abstract property to the platform-specific implementations. + /// + /// The associated handler. + /// The associated instance. public static void MapSource(IImageHandler handler, IImage image) => MapSourceAsync(handler, image).FireAndForget(handler); + /// + /// Maps the abstract property to the platform-specific implementations as an asynchronous operation. + /// + /// The associated handler. + /// The associated instance. public static Task MapSourceAsync(IImageHandler handler, IImage image) => handler.SourceLoader.UpdateImageSourceAsync(); @@ -66,4 +157,4 @@ public override void SetImageSource(ImageSource? platformImage) } } } -} \ No newline at end of file +} diff --git a/src/Core/src/Handlers/Image/ImageHandler.cs b/src/Core/src/Handlers/Image/ImageHandler.cs index 07cf5eff7b69..cfcc20d83514 100644 --- a/src/Core/src/Handlers/Image/ImageHandler.cs +++ b/src/Core/src/Handlers/Image/ImageHandler.cs @@ -20,6 +20,10 @@ public partial class ImageHandler : IImageHandler { #if __ANDROID__ || WINDOWS || TIZEN [nameof(IImage.Background)] = MapBackground, +#endif +#if WINDOWS + [nameof(IImage.Height)] = MapHeight, + [nameof(IImage.Width)] = MapWidth, #endif [nameof(IImage.Aspect)] = MapAspect, [nameof(IImage.IsAnimationPlaying)] = MapIsAnimationPlaying, diff --git a/src/Core/src/Platform/Windows/ImageViewExtensions.cs b/src/Core/src/Platform/Windows/ImageViewExtensions.cs index 0a2b957909d6..fbcd994371c5 100644 --- a/src/Core/src/Platform/Windows/ImageViewExtensions.cs +++ b/src/Core/src/Platform/Windows/ImageViewExtensions.cs @@ -1,4 +1,5 @@ #nullable enable +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Imaging; using WImage = Microsoft.UI.Xaml.Controls.Image; @@ -14,6 +15,12 @@ public static void Clear(this WImage imageView) public static void UpdateAspect(this WImage imageView, IImage image) { imageView.Stretch = image.Aspect.ToStretch(); + + if (image.Aspect == Aspect.AspectFill) + { + imageView.VerticalAlignment = VerticalAlignment.Center; + imageView.HorizontalAlignment = HorizontalAlignment.Center; + } } public static void UpdateIsAnimationPlaying(this WImage imageView, IImageSourcePart image) diff --git a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 9106ef6dac6b..6020d8d9d8d2 100644 --- a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -45,6 +45,8 @@ Microsoft.Maui.SizeRequest.Equals(Microsoft.Maui.SizeRequest other) -> bool Microsoft.Maui.SoftInputExtensions override Microsoft.Maui.Handlers.ContentViewHandler.DisconnectHandler(Microsoft.Maui.Platform.ContentPanel! platformView) -> void override Microsoft.Maui.Handlers.ImageHandler.ConnectHandler(Microsoft.UI.Xaml.Controls.Image! platformView) -> void +override Microsoft.Maui.Handlers.ImageHandler.RemoveContainer() -> void +override Microsoft.Maui.Handlers.ImageHandler.SetupContainer() -> void override Microsoft.Maui.Handlers.MenuFlyoutHandler.DisconnectHandler(Microsoft.UI.Xaml.Controls.MenuFlyout! platformView) -> void override Microsoft.Maui.Layouts.FlexBasis.Equals(object? obj) -> bool override Microsoft.Maui.Layouts.FlexBasis.GetHashCode() -> int @@ -64,6 +66,8 @@ static Microsoft.Maui.GridLength.operator ==(Microsoft.Maui.GridLength left, Mic static Microsoft.Maui.Handlers.EditorHandler.MapIsSpellCheckEnabled(Microsoft.Maui.Handlers.IEditorHandler! handler, Microsoft.Maui.IEditor! editor) -> void static Microsoft.Maui.Handlers.EntryHandler.MapIsSpellCheckEnabled(Microsoft.Maui.Handlers.IEntryHandler! handler, Microsoft.Maui.IEntry! entry) -> void static Microsoft.Maui.Handlers.LayoutHandler.MapInputTransparent(Microsoft.Maui.ILayoutHandler! handler, Microsoft.Maui.ILayout! layout) -> void +static Microsoft.Maui.Handlers.ImageHandler.MapHeight(Microsoft.Maui.Handlers.IImageHandler! handler, Microsoft.Maui.IImage! view) -> void +static Microsoft.Maui.Handlers.ImageHandler.MapWidth(Microsoft.Maui.Handlers.IImageHandler! handler, Microsoft.Maui.IImage! view) -> void static Microsoft.Maui.Handlers.SearchBarHandler.MapIsSpellCheckEnabled(Microsoft.Maui.Handlers.ISearchBarHandler! handler, Microsoft.Maui.ISearchBar! searchBar) -> void static Microsoft.Maui.Handlers.SwipeItemMenuItemHandler.MapSourceAsync(Microsoft.Maui.Handlers.ISwipeItemMenuItemHandler! handler, Microsoft.Maui.ISwipeItemMenuItem! image) -> System.Threading.Tasks.Task! static Microsoft.Maui.Hosting.MauiHandlersCollectionExtensions.AddHandler(this Microsoft.Maui.Hosting.IMauiHandlersCollection! handlersCollection, System.Func! handlerImplementationFactory) -> Microsoft.Maui.Hosting.IMauiHandlersCollection!