diff --git a/docs/design/FeatureSwitches.md b/docs/design/FeatureSwitches.md index 206992762b3e..51bff1177302 100644 --- a/docs/design/FeatureSwitches.md +++ b/docs/design/FeatureSwitches.md @@ -1,23 +1,28 @@ # Feature Switches -Certain features of MAUI can be enabled or disabled using feature switches. The easiest way to control the features is by putting the corresponding MSBuild property into the app's project file. Disabling unnecessary features can help reducing the app size when combined with the [`full` trimming mode](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options). +Certain features of MAUI can be enabled or disabled using feature switches. The easiest way to control the features is by putting the corresponding MSBuild property into the app's project file. Disabling unnecessary features can help reducing the app size when combined with the [`full` trimming mode](https://learn.microsoft.com/dotnet/core/deploying/trimming/trimming-options). | MSBuild Property Name | AppContext Setting | Description | |-|-|-| -| MauiXamlRuntimeParsingSupport | Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported | When disabled, all XAML loading at runtime will throw an exception. This will affect usage of APIs such as the `LoadFromXaml` extension method. This feature can be safely turned off when all XAML resources are compiled using XamlC (see [XAML compilation](https://learn.microsoft.com/en-us/dotnet/maui/xaml/xamlc)). This feature is enabled by default for all configurations except for NativeAOT. | +| MauiXamlRuntimeParsingSupport | Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported | When disabled, all XAML loading at runtime will throw an exception. This will affect usage of APIs such as the `LoadFromXaml` extension method. This feature can be safely turned off when all XAML resources are compiled using XamlC (see [XAML compilation](https://learn.microsoft.com/dotnet/maui/xaml/xamlc)). This feature is enabled by default for all configurations except for NativeAOT. | | MauiEnableIVisualAssemblyScanning | Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled | When enabled, MAUI will scan assemblies for types implementing `IVisual` and for `[assembly: Visual(...)]` attributes and register these types. | +| MauiShellSearchResultsRendererDisplayMemberNameSupported | Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported | When disabled, it is necessary to always set `ItemTemplate` of any `SearchHandler`. Displaying search results through `DisplayMemberName` will not work. | | MauiQueryPropertyAttributeSupport | Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported | When disabled, the `[QueryProperty(...)]` attributes won't be used to set values to properties when navigating. | ## MauiXamlRuntimeParsingSupport When this feature is disabled, the following APIs are affected: -- [`LoadFromXaml` extension methods](https://learn.microsoft.com/en-us/dotnet/maui/xaml/runtime-load) will throw runtime exceptions. -- [Disabling XAML compilation](https://learn.microsoft.com/en-us/dotnet/maui/xaml/xamlc#disable-xaml-compilation) using `[XamlCompilation(XamlCompilationOptions.Skip)]` on pages and controls or whole assemblies will cause runtime exceptions. +- [`LoadFromXaml` extension methods](https://learn.microsoft.com/dotnet/maui/xaml/runtime-load) will throw runtime exceptions. +- [Disabling XAML compilation](https://learn.microsoft.com/dotnet/maui/xaml/xamlc#disable-xaml-compilation) using `[XamlCompilation(XamlCompilationOptions.Skip)]` on pages and controls or whole assemblies will cause runtime exceptions. ## MauiEnableIVisualAssemblyScanning When this feature is not enabled, custom and third party `IVisual` types will not be automatically discovered and registerd. +## MauiShellSearchResultsRendererDisplayMemberNameSupported + +When this feature is disabled, any value set to [`SearchHandler.DisplayMemberName`](https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.searchhandler.displaymembername) will be ignored. Consider implementing a custom `ItemTemplate` to define the appearance of search results (see [Shell search documentation](https://learn.microsoft.com/dotnet/maui/fundamentals/shell/search#define-search-results-item-appearance)). + ## MauiQueryPropertyAttributeSupport When disabled, the `[QueryProperty(...)]` attributes won't be used to set values to properties when navigating. Instead, implement the [`IQueryAttributable`](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation#process-navigation-data-using-a-single-method) interface whenever you need to accept query parameters. diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets index fb1c4979598e..d2b9c457951a 100644 --- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets +++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets @@ -210,6 +210,7 @@ false false + false false @@ -221,6 +222,10 @@ Condition="'$(MauiEnableIVisualAssemblyScanning)' != ''" Value="$(MauiEnableIVisualAssemblyScanning)" Trim="true" /> + { var label = new Label(); - label.SetBinding(Label.TextProperty, _searchHandler.DisplayMemberName ?? "."); + + if (RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported) + { +#pragma warning disable CS0618 + label.SetBinding(Label.TextProperty, _searchHandler.DisplayMemberName ?? "."); +#pragma warning restore CS0618 + } + else + { +#pragma warning disable CS0618 + if (_searchHandler.DisplayMemberName is not null) + { + Application.Current?.FindMauiContext()?.CreateLogger()?.LogError(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning); + throw new InvalidOperationException(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning); + } +#pragma warning restore CS0618 + } + label.HorizontalTextAlignment = TextAlignment.Center; label.VerticalTextAlignment = TextAlignment.Center; diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSearchResultsRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSearchResultsRenderer.cs index d459b1b5f0ed..ea030bd6054a 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSearchResultsRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSearchResultsRenderer.cs @@ -3,6 +3,7 @@ using System.Collections.Specialized; using Foundation; using Microsoft.Maui.Controls.Internals; +using Microsoft.Extensions.Logging; using ObjCRuntime; using UIKit; @@ -37,7 +38,26 @@ DataTemplate DefaultTemplate return _defaultTemplate ?? (_defaultTemplate = new DataTemplate(() => { var label = new Label(); - label.SetBinding(Label.TextProperty, SearchHandler.DisplayMemberName ?? "."); + + if (RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported) + { +#pragma warning disable CS0618 + label.SetBinding(Label.TextProperty, SearchHandler.DisplayMemberName ?? "."); +#pragma warning restore CS0618 + } + else + { +#pragma warning disable CS0618 + if (SearchHandler.DisplayMemberName is not null) + { + Application.Current?.FindMauiContext()?.CreateLogger()?.LogError(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning); + throw new InvalidOperationException(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning); + } +#pragma warning restore CS0618 + + label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel(string.Empty, static (object o) => o)); + } + label.HorizontalTextAlignment = TextAlignment.Center; label.VerticalTextAlignment = TextAlignment.Center; @@ -88,7 +108,9 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index var template = SearchHandler.ItemTemplate; if (template == null) + { template = DefaultTemplate; + } var cellId = ((IDataTemplateController)template.SelectDataTemplate(context, _context.Shell)).IdString; diff --git a/src/Controls/src/Core/ContentConverter.cs b/src/Controls/src/Core/ContentConverter.cs index b27fb7847cd6..12b78d59d789 100644 --- a/src/Controls/src/Core/ContentConverter.cs +++ b/src/Controls/src/Core/ContentConverter.cs @@ -66,19 +66,22 @@ static Label ConvertToLabel(string textContent, ContentPresenter presenter) static void BindTextProperties(BindableObject content) { - BindProperty(content, TextElement.TextColorProperty, typeof(ITextElement)); - BindProperty(content, TextElement.CharacterSpacingProperty, typeof(ITextElement)); - BindProperty(content, TextElement.TextTransformProperty, typeof(ITextElement)); + BindProperty(content, TextElement.TextColorProperty, static (ITextElement te) => te.TextColor); + BindProperty(content, TextElement.CharacterSpacingProperty, static (ITextElement te) => te.CharacterSpacing); + BindProperty(content, TextElement.TextTransformProperty, static (ITextElement te) => te.TextTransform); } static void BindFontProperties(BindableObject content) { - BindProperty(content, FontElement.FontAttributesProperty, typeof(IFontElement)); - BindProperty(content, FontElement.FontSizeProperty, typeof(IFontElement)); - BindProperty(content, FontElement.FontFamilyProperty, typeof(IFontElement)); + BindProperty(content, FontElement.FontAttributesProperty, static (IFontElement fe) => fe.FontAttributes); + BindProperty(content, FontElement.FontSizeProperty, static (IFontElement fe) => fe.FontSize); + BindProperty(content, FontElement.FontFamilyProperty, static (IFontElement fe) => fe.FontFamily); } - static void BindProperty(BindableObject content, BindableProperty property, Type type) + static void BindProperty( + BindableObject content, + BindableProperty property, + Func getter) { if (content.IsSet(property) || content.GetIsBound(property)) { @@ -86,9 +89,8 @@ static void BindProperty(BindableObject content, BindableProperty property, Type return; } - content.SetBinding(property, - new Binding(property.PropertyName, - source: new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, type))); + content.SetBinding(property, TypedBinding.ForSingleNestingLevel( + property.PropertyName, getter, source: new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(TSource)))); } static bool HasTemplateAncestor(ContentPresenter presenter, Type type) diff --git a/src/Controls/src/Core/ContentPresenter.cs b/src/Controls/src/Core/ContentPresenter.cs index 6816bd1ddff5..d500c2032908 100644 --- a/src/Controls/src/Core/ContentPresenter.cs +++ b/src/Controls/src/Core/ContentPresenter.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; +using Microsoft.Maui.Controls.Internals; namespace Microsoft.Maui.Controls { @@ -16,8 +17,14 @@ public class ContentPresenter : Compatibility.Layout, IContentView /// public ContentPresenter() { - SetBinding(ContentProperty, new Binding(ContentProperty.PropertyName, source: RelativeBindingSource.TemplatedParent, - converterParameter: this, converter: new ContentConverter())); + SetBinding( + ContentProperty, + TypedBinding.ForSingleNestingLevel( + nameof(IContentView.Content), + static (IContentView view) => view.Content, + source: RelativeBindingSource.TemplatedParent, + converter: new ContentConverter(), + converterParameter: this)); } /// diff --git a/src/Controls/src/Core/Items/CarouselView.cs b/src/Controls/src/Core/Items/CarouselView.cs index 50594f8908a6..b15a5406b36a 100644 --- a/src/Controls/src/Core/Items/CarouselView.cs +++ b/src/Controls/src/Core/Items/CarouselView.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Windows.Input; +using Microsoft.Maui.Controls.Internals; namespace Microsoft.Maui.Controls { @@ -208,17 +209,17 @@ static void LinkToIndicatorView(CarouselView carouselView, IndicatorView indicat if (indicatorView == null) return; - indicatorView.SetBinding(IndicatorView.PositionProperty, new Binding - { - Path = nameof(CarouselView.Position), - Source = carouselView - }); - - indicatorView.SetBinding(IndicatorView.ItemsSourceProperty, new Binding - { - Path = nameof(ItemsView.ItemsSource), - Source = carouselView - }); + indicatorView.SetBinding(IndicatorView.PositionProperty, TypedBinding.ForSingleNestingLevel( + nameof(CarouselView.Position), + getter: static (CarouselView carousel) => carousel.Position, + setter: static (carousel, val) => carousel.Position = val, + source: carouselView)); + + indicatorView.SetBinding(IndicatorView.ItemsSourceProperty, TypedBinding.ForSingleNestingLevel( + nameof(CarouselView.ItemsSource), + getter: static (CarouselView carousel) => carousel.ItemsSource, + setter: static (carousel, val) => carousel.ItemsSource = val, + source: carouselView)); } /// diff --git a/src/Controls/src/Core/ListView/ListView.cs b/src/Controls/src/Core/ListView/ListView.cs index fe4053865259..f62bc3d5f84a 100644 --- a/src/Controls/src/Core/ListView/ListView.cs +++ b/src/Controls/src/Core/ListView/ListView.cs @@ -411,7 +411,13 @@ public void ScrollTo(object item, object group, ScrollToPosition position, bool protected override Cell CreateDefault(object item) { TextCell textCell = new TextCell(); - textCell.SetBinding(TextCell.TextProperty, ".", converter: _toStringValueConverter); + textCell.SetBinding( + TextCell.TextProperty, + TypedBinding.ForSingleNestingLevel( + propertyName: string.Empty, + getter: static (object cell) => cell, + mode: BindingMode.OneWay, + converter: _toStringValueConverter)); return textCell; } diff --git a/src/Controls/src/Core/RadioButton/RadioButton.cs b/src/Controls/src/Core/RadioButton/RadioButton.cs index c367ddc1ae5b..8f12955d06eb 100644 --- a/src/Controls/src/Core/RadioButton/RadioButton.cs +++ b/src/Controls/src/Core/RadioButton/RadioButton.cs @@ -444,15 +444,6 @@ void HandleRadioButtonGroupValueChanged(Element layout, RadioButtonGroupValueCha SetValue(IsCheckedProperty, true, specificity: SetterSpecificity.FromHandler); } - static void BindToTemplatedParent(BindableObject bindableObject, params BindableProperty[] properties) - { - foreach (var property in properties) - { - bindableObject.SetBinding(property, new Binding(property.PropertyName, - source: RelativeBindingSource.TemplatedParent)); - } - } - static View BuildDefaultTemplate() { Border border = new Border() @@ -460,21 +451,41 @@ static View BuildDefaultTemplate() Padding = 6 }; - BindToTemplatedParent(border, BackgroundColorProperty, HorizontalOptionsProperty, - MarginProperty, OpacityProperty, RotationProperty, ScaleProperty, ScaleXProperty, ScaleYProperty, - TranslationYProperty, TranslationXProperty, VerticalOptionsProperty); - - border.SetBinding(Border.StrokeProperty, - new Binding(BorderColorProperty.PropertyName, - source: RelativeBindingSource.TemplatedParent)); - - border.SetBinding(Border.StrokeShapeProperty, - new Binding(CornerRadiusProperty.PropertyName, converter: new CornerRadiusToShape(), - source: RelativeBindingSource.TemplatedParent)); + void BindToTemplatedParent( + BindableProperty property, + Func getter, + string radioButtonPropertyName = null, + IValueConverter converter = null) + { + border.SetBinding(property, TypedBinding.ForSingleNestingLevel(radioButtonPropertyName ?? property.PropertyName, + getter, source: RelativeBindingSource.TemplatedParent, converter: converter)); + } - border.SetBinding(Border.StrokeThicknessProperty, - new Binding(BorderWidthProperty.PropertyName, - source: RelativeBindingSource.TemplatedParent)); + BindToTemplatedParent(BackgroundColorProperty, static (RadioButton rb) => rb.BackgroundColor); + BindToTemplatedParent(HorizontalOptionsProperty, static (RadioButton rb) => rb.HorizontalOptions); + BindToTemplatedParent(MarginProperty, static (RadioButton rb) => rb.Margin); + BindToTemplatedParent(OpacityProperty, static (RadioButton rb) => rb.Opacity); + BindToTemplatedParent(RotationProperty, static (RadioButton rb) => rb.Rotation); + BindToTemplatedParent(ScaleProperty, static (RadioButton rb) => rb.Scale); + BindToTemplatedParent(ScaleXProperty, static (RadioButton rb) => rb.ScaleX); + BindToTemplatedParent(ScaleYProperty, static (RadioButton rb) => rb.ScaleY); + BindToTemplatedParent(TranslationYProperty, static (RadioButton rb) => rb.TranslationY); + BindToTemplatedParent(TranslationXProperty, static (RadioButton rb) => rb.TranslationX); + BindToTemplatedParent(VerticalOptionsProperty, static (RadioButton rb) => rb.VerticalOptions); + + BindToTemplatedParent( + Border.StrokeProperty, + static (RadioButton rb) => rb.BorderColor, + radioButtonPropertyName: nameof(RadioButton.BorderColor)); + BindToTemplatedParent( + Border.StrokeShapeProperty, + static (RadioButton rb) => rb.CornerRadius, + radioButtonPropertyName: nameof(RadioButton.CornerRadius), + converter: new CornerRadiusToShape()); + BindToTemplatedParent( + Border.StrokeThicknessProperty, + static (RadioButton rb) => rb.BorderWidth, + radioButtonPropertyName: nameof(RadioButton.BorderWidth)); var grid = new Grid { @@ -573,9 +584,20 @@ static View BuildDefaultTemplate() out checkMarkFillVisualStateDark); } - contentPresenter.SetBinding(MarginProperty, new Binding("Padding", source: RelativeBindingSource.TemplatedParent)); - contentPresenter.SetBinding(BackgroundColorProperty, new Binding(BackgroundColorProperty.PropertyName, - source: RelativeBindingSource.TemplatedParent)); + contentPresenter.SetBinding( + MarginProperty, + TypedBinding.ForSingleNestingLevel( + nameof(RadioButton.Padding), + static (RadioButton radio) => radio.Padding, + mode: BindingMode.OneWay, + source: RelativeBindingSource.TemplatedParent)); + contentPresenter.SetBinding( + BackgroundColorProperty, + TypedBinding.ForSingleNestingLevel( + nameof(RadioButton.BackgroundColor), + static (RadioButton radio) => radio.BackgroundColor, + mode: BindingMode.OneWay, + source: RelativeBindingSource.TemplatedParent)); grid.Add(normalEllipse); grid.Add(checkMark); diff --git a/src/Controls/src/Core/Shell/BaseShellItem.cs b/src/Controls/src/Core/Shell/BaseShellItem.cs index c1593aafcf2f..3712380373ad 100644 --- a/src/Controls/src/Core/Shell/BaseShellItem.cs +++ b/src/Controls/src/Core/Shell/BaseShellItem.cs @@ -339,7 +339,7 @@ BindableObject NonImplicitParent } } - internal static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, string iconBinding) + internal static DataTemplate CreateDefaultFlyoutItemCell(BindableObject bo) { return new DataTemplate(() => { @@ -426,9 +426,37 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, str columnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star }); defaultGridClass.Setters.Add(new Setter { Property = Grid.ColumnDefinitionsProperty, Value = columnDefinitions }); - Binding automationIdBinding = new Binding(Element.AutomationIdProperty.PropertyName); + BindingBase automationIdBinding = TypedBinding.ForSingleNestingLevel( + nameof(Element.AutomationId), + static (Element element) => element.AutomationId, + static (element, val) => element.AutomationId = val); defaultGridClass.Setters.Add(new Setter { Property = Element.AutomationIdProperty, Value = automationIdBinding }); + BindingBase imageBinding = null; + BindingBase labelBinding = null; + if (bo is MenuItem) + { + imageBinding = TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.IconImageSource), + getter: static (MenuItem item) => item.IconImageSource, + setter: static (item, val) => item.IconImageSource = val); + labelBinding = TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.Text), + getter: static (MenuItem item) => item.Text, + setter: static (item, val) => item.Text = val); + } + else + { + imageBinding = TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.FlyoutIcon), + getter: static (BaseShellItem item) => item.FlyoutIcon, + setter: static (item, val) => item.FlyoutIcon = val); + labelBinding = TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.Title), + getter: static (BaseShellItem item) => item.Title, + setter: static (item, val) => item.Title = val); + } + var image = new Image(); double sizeRequest = -1; @@ -453,13 +481,11 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, str defaultImageClass.Setters.Add(new Setter { Property = Image.MarginProperty, Value = new Thickness(12, 0, 12, 0) }); } - Binding imageBinding = new Binding(iconBinding); defaultImageClass.Setters.Add(new Setter { Property = Image.SourceProperty, Value = imageBinding }); grid.Add(image); var label = new Label(); - Binding labelBinding = new Binding(textBinding); defaultLabelClass.Setters.Add(new Setter { Property = Label.TextProperty, Value = labelBinding }); grid.Add(label, 1, 0); @@ -527,7 +553,12 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, str // just bind the semantic description to the title if (!g.IsSet(SemanticProperties.DescriptionProperty)) { - g.SetBinding(SemanticProperties.DescriptionProperty, TitleProperty.PropertyName); + g.SetBinding( + SemanticProperties.DescriptionProperty, + TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.Title), + static (BaseShellItem item) => item.Title, + static (item, val) => item.Title = val)); } } } diff --git a/src/Controls/src/Core/Shell/MenuShellItem.cs b/src/Controls/src/Core/Shell/MenuShellItem.cs index a0dcdaaf9d59..802304eb9dcd 100644 --- a/src/Controls/src/Core/Shell/MenuShellItem.cs +++ b/src/Controls/src/Core/Shell/MenuShellItem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; +using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.StyleSheets; namespace Microsoft.Maui.Controls @@ -13,10 +14,34 @@ internal MenuShellItem(MenuItem menuItem) MenuItem = menuItem; MenuItem.Parent = this; Shell.SetFlyoutItemIsVisible(this, Shell.GetFlyoutItemIsVisible(menuItem)); - SetBinding(TitleProperty, new Binding(nameof(MenuItem.Text), BindingMode.OneWay, source: menuItem)); - SetBinding(IconProperty, new Binding(nameof(MenuItem.IconImageSource), BindingMode.OneWay, source: menuItem)); - SetBinding(FlyoutIconProperty, new Binding(nameof(MenuItem.IconImageSource), BindingMode.OneWay, source: menuItem)); - SetBinding(AutomationIdProperty, new Binding(nameof(MenuItem.AutomationId), BindingMode.OneWay, source: menuItem)); + SetBinding( + TitleProperty, + TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.Text), + static (MenuItem item) => item.Text, + mode: BindingMode.OneWay, + source: menuItem)); + SetBinding( + IconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.IconImageSource), + static (MenuItem item) => item.IconImageSource, + mode: BindingMode.OneWay, + source: menuItem)); + SetBinding( + FlyoutIconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.IconImageSource), + static (MenuItem item) => item.IconImageSource, + mode: BindingMode.OneWay, + source: menuItem)); + SetBinding( + AutomationIdProperty, + TypedBinding.ForSingleNestingLevel( + nameof(MenuItem.AutomationId), + static (MenuItem item) => item.AutomationId, + mode: BindingMode.OneWay, + source: menuItem)); MenuItem.PropertyChanged += OnMenuItemPropertyChanged; } diff --git a/src/Controls/src/Core/Shell/SearchHandler.cs b/src/Controls/src/Core/Shell/SearchHandler.cs index ff6c8507dcc1..bf6e893d5197 100644 --- a/src/Controls/src/Core/Shell/SearchHandler.cs +++ b/src/Controls/src/Core/Shell/SearchHandler.cs @@ -361,9 +361,11 @@ void ISearchHandlerController.QueryConfirmed() BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(SearchHandler), null, BindingMode.OneTime, propertyChanged: OnCommandChanged); +#pragma warning disable CS0618 /// Bindable property for . public static readonly BindableProperty DisplayMemberNameProperty = BindableProperty.Create(nameof(DisplayMemberName), typeof(string), typeof(SearchHandler), null, BindingMode.OneTime); +#pragma warning restore CS0618 /// Bindable property for . public static readonly BindableProperty IsSearchEnabledProperty = @@ -498,6 +500,7 @@ public object CommandParameter } /// + [Obsolete("Use ItemTemplate instead.")] public string DisplayMemberName { get { return (string)GetValue(DisplayMemberNameProperty); } diff --git a/src/Controls/src/Core/Shell/Shell.cs b/src/Controls/src/Core/Shell/Shell.cs index eb46e39bd0ac..e53a8bca4bbe 100644 --- a/src/Controls/src/Core/Shell/Shell.cs +++ b/src/Controls/src/Core/Shell/Shell.cs @@ -340,24 +340,9 @@ internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableO DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) { - BindableProperty bp = null; - string textBinding; - string iconBinding; + BindableProperty bp = bo is IMenuItemController ? MenuItemTemplateProperty : ItemTemplateProperty; var bindableObjectWithTemplate = GetBindableObjectWithFlyoutItemTemplate(bo); - if (bo is IMenuItemController) - { - bp = MenuItemTemplateProperty; - textBinding = "Text"; - iconBinding = "Icon"; - } - else - { - bp = ItemTemplateProperty; - textBinding = "Title"; - iconBinding = "FlyoutIcon"; - } - if (bindableObjectWithTemplate.IsSet(bp)) { return (DataTemplate)bindableObjectWithTemplate.GetValue(bp); @@ -368,7 +353,7 @@ DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) return (DataTemplate)GetValue(bp); } - return BaseShellItem.CreateDefaultFlyoutItemCell(textBinding, iconBinding); + return BaseShellItem.CreateDefaultFlyoutItemCell(bo); } event EventHandler IShellController.StructureChanged diff --git a/src/Controls/src/Core/Shell/ShellContent.cs b/src/Controls/src/Core/Shell/ShellContent.cs index d30336791bd6..915c9a756484 100644 --- a/src/Controls/src/Core/Shell/ShellContent.cs +++ b/src/Controls/src/Core/Shell/ShellContent.cs @@ -212,9 +212,27 @@ public static implicit operator ShellContent(TemplatedPage page) shellContent.Route = Routing.GenerateImplicitRoute(pageRoute); shellContent.Content = page; - shellContent.SetBinding(TitleProperty, new Binding(nameof(Title), BindingMode.OneWay, source: page)); - shellContent.SetBinding(IconProperty, new Binding(nameof(Icon), BindingMode.OneWay, source: page)); - shellContent.SetBinding(FlyoutIconProperty, new Binding(nameof(FlyoutIcon), BindingMode.OneWay, source: page)); + shellContent.SetBinding( + TitleProperty, + TypedBinding.ForSingleNestingLevel( + nameof(TemplatedPage.Title), + static (TemplatedPage page) => page.Title, + mode: BindingMode.OneWay, + source: page)); + shellContent.SetBinding( + IconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(TemplatedPage.IconImageSource), + static (TemplatedPage page) => page.IconImageSource, + mode: BindingMode.OneWay, + source: page)); + shellContent.SetBinding( + FlyoutIconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(TemplatedPage.IconImageSource), + static (TemplatedPage page) => page.IconImageSource, + mode: BindingMode.OneWay, + source: page)); return shellContent; } diff --git a/src/Controls/src/Core/Shell/ShellItem.cs b/src/Controls/src/Core/Shell/ShellItem.cs index 40bd26c3a6c4..f108bbea0731 100644 --- a/src/Controls/src/Core/Shell/ShellItem.cs +++ b/src/Controls/src/Core/Shell/ShellItem.cs @@ -222,10 +222,34 @@ internal static ShellItem CreateFromShellSection(ShellSection shellSection) result.Route = Routing.GenerateImplicitRoute(shellSection.Route); result.Items.Add(shellSection); - result.SetBinding(TitleProperty, new Binding(nameof(Title), BindingMode.OneWay, source: shellSection)); - result.SetBinding(IconProperty, new Binding(nameof(Icon), BindingMode.OneWay, source: shellSection)); - result.SetBinding(FlyoutDisplayOptionsProperty, new Binding(nameof(FlyoutDisplayOptions), BindingMode.OneTime, source: shellSection)); - result.SetBinding(FlyoutIconProperty, new Binding(nameof(FlyoutIcon), BindingMode.OneWay, source: shellSection)); + result.SetBinding( + TitleProperty, + TypedBinding.ForSingleNestingLevel( + nameof(ShellSection.Title), + static (ShellSection section) => section.Title, + mode: BindingMode.OneWay, + source: shellSection)); + result.SetBinding( + IconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(ShellSection.Icon), + static (ShellSection section) => section.Icon, + mode: BindingMode.OneWay, + source: shellSection)); + result.SetBinding( + FlyoutDisplayOptionsProperty, + TypedBinding.ForSingleNestingLevel( + nameof(ShellSection.FlyoutDisplayOptions), + static (ShellSection section) => section.FlyoutDisplayOptions, + mode: BindingMode.OneTime, + source: shellSection)); + result.SetBinding( + FlyoutIconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(ShellSection.FlyoutIcon), + static (ShellSection section) => section.FlyoutIcon, + mode: BindingMode.OneWay, + source: shellSection)); return result; } diff --git a/src/Controls/src/Core/Shell/ShellSection.cs b/src/Controls/src/Core/Shell/ShellSection.cs index b88e977c22f4..6ec213a70989 100644 --- a/src/Controls/src/Core/Shell/ShellSection.cs +++ b/src/Controls/src/Core/Shell/ShellSection.cs @@ -291,9 +291,27 @@ internal static ShellSection CreateFromShellContent(ShellContent shellContent) shellSection.Items.Add(shellContent); - shellSection.SetBinding(TitleProperty, new Binding(nameof(Title), BindingMode.OneWay, source: shellContent)); - shellSection.SetBinding(IconProperty, new Binding(nameof(Icon), BindingMode.OneWay, source: shellContent)); - shellSection.SetBinding(FlyoutIconProperty, new Binding(nameof(FlyoutIcon), BindingMode.OneWay, source: shellContent)); + shellSection.SetBinding( + TitleProperty, + TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.Title), + static (BaseShellItem item) => item.Title, + mode: BindingMode.OneWay, + source: shellContent)); + shellSection.SetBinding( + IconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.Icon), + static (BaseShellItem item) => item.Icon, + mode: BindingMode.OneWay, + source: shellContent)); + shellSection.SetBinding( + FlyoutIconProperty, + TypedBinding.ForSingleNestingLevel( + nameof(BaseShellItem.FlyoutIcon), + static (BaseShellItem item) => item.FlyoutIcon, + mode: BindingMode.OneWay, + source: shellContent)); return shellSection; } diff --git a/src/Controls/src/Core/TemplatedItemsList.cs b/src/Controls/src/Core/TemplatedItemsList.cs index 18b2b0a4e99f..9865a223d7df 100644 --- a/src/Controls/src/Core/TemplatedItemsList.cs +++ b/src/Controls/src/Core/TemplatedItemsList.cs @@ -755,7 +755,12 @@ TemplatedItemsList InsertGrouped(object item, int index) // time for right now. groupProxy.HeaderContent = _itemsView.CreateDefault(ListProxy.ProxiedEnumerable); groupProxy.HeaderContent.BindingContext = groupProxy; - groupProxy.HeaderContent.SetBinding(TextCell.TextProperty, "Name"); + groupProxy.HeaderContent.SetBinding( + TextCell.TextProperty, + TypedBinding.ForSingleNestingLevel( + nameof(TemplatedItemsList.Name), + getter: static (TemplatedItemsList list) => list.Name, + setter: static (list, val) => list.Name = val)); } SetIndex(groupProxy.HeaderContent, index); diff --git a/src/Controls/src/Core/TrimmerConstants.cs b/src/Controls/src/Core/TrimmerConstants.cs index 4ca7b73244dd..6d39f3865aae 100644 --- a/src/Controls/src/Core/TrimmerConstants.cs +++ b/src/Controls/src/Core/TrimmerConstants.cs @@ -9,6 +9,8 @@ class TrimmerConstants internal const string XamlRuntimeParsingNotSupportedWarning = "Loading XAML at runtime might require types and members that cannot be statically analyzed. Make sure all of the required types and members are preserved."; + internal const string SearchHandlerDisplayMemberNameNotSupportedWarning = "DisplayMemberName is not supported. Consider implementing custom ItemTemplate instead. Alternatively, enable DisplayMemberName by setting the $(MauiShellSearchResultsRendererDisplayMemberNameSupported) MSBuild property to true. Note: DisplayMemberName is not trimming-safe and it might not work as expected in NativeAOT or fully trimmed apps."; + internal const string QueryPropertyAttributeWarning = "Using QueryPropertyAttribute is not trimming friendly and might not work correctly. Implement the IQueryAttributable interface instead."; internal const string QueryPropertyDocsUrl = "https://learn.microsoft.com/dotnet/maui/fundamentals/shell/navigation#process-navigation-data-using-a-single-method"; } \ No newline at end of file diff --git a/src/Core/src/ILLink.Substitutions.xml b/src/Core/src/ILLink.Substitutions.xml index 104a360bdaa7..fdc9ed74f964 100644 --- a/src/Core/src/ILLink.Substitutions.xml +++ b/src/Core/src/ILLink.Substitutions.xml @@ -5,6 +5,8 @@ + + diff --git a/src/Core/src/RuntimeFeature.cs b/src/Core/src/RuntimeFeature.cs index e548d939e074..f15bc643c77c 100644 --- a/src/Core/src/RuntimeFeature.cs +++ b/src/Core/src/RuntimeFeature.cs @@ -16,11 +16,12 @@ internal static class RuntimeFeature { private const bool IsXamlRuntimeParsingSupportedByDefault = true; private const bool IsIVisualAssemblyScanningEnabledByDefault = false; + private const bool IsShellSearchResultsRendererDisplayMemberNameSupportedByDefault = true; private const bool IsQueryPropertyAttributeSupportedByDefault = true; internal static bool IsXamlRuntimeParsingSupported - => AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported", out bool isEnabled) - ? isEnabled + => AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported", out bool isSupported) + ? isSupported : IsXamlRuntimeParsingSupportedByDefault; internal const string XamlRuntimeParsingNotSupportedErrorMessage = "XAML runtime parsing is not supported. " + @@ -33,6 +34,11 @@ internal static bool IsXamlRuntimeParsingSupported ? isEnabled : IsIVisualAssemblyScanningEnabledByDefault; + internal static bool IsShellSearchResultsRendererDisplayMemberNameSupported + => AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported", out bool isSupported) + ? isSupported + : IsShellSearchResultsRendererDisplayMemberNameSupportedByDefault; + internal static bool IsQueryPropertyAttributeSupported => AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported", out bool isSupported) ? isSupported diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs index 6b2b645b0d1a..ba70b17ad634 100644 --- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs +++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/Utilities/BuildWarningsUtilities.cs @@ -139,29 +139,6 @@ public static void AssertWarnings(this List actualWarnings, Lis }, } }, - new WarningsPerFile - { - File = "src/Controls/src/Core/BindingExpression.cs", - WarningsPerCode = new List - { - new WarningsPerCode - { - Code = "IL2070", - Messages = new List - { - "Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Reflection.TypeInfo.GetDeclaredMethod(String)'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Reflection.TypeInfo.GetDeclaredMethod(String)'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Reflection.TypeInfo.GetDeclaredProperty(String)'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicFields', 'DynamicallyAccessedMemberTypes.NonPublicFields' in call to 'System.Reflection.TypeInfo.GetDeclaredField(String)'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.Interfaces' in call to 'System.Reflection.TypeInfo.ImplementedInterfaces.get'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.SetupPart(TypeInfo,BindingExpression.BindingExpressionPart)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Reflection.TypeInfo.DeclaredProperties.get'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Reflection.TypeInfo.DeclaredProperties.get'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties', 'DynamicallyAccessedMemberTypes.NonPublicProperties' in call to 'System.Reflection.TypeInfo.DeclaredProperties.get'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - "Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.Interfaces' in call to 'System.Reflection.TypeInfo.ImplementedInterfaces.get'. The parameter '#0' of method 'Microsoft.Maui.Controls.BindingExpression.GetIndexer(TypeInfo,String,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.", - } - }, - } - }, }; #region Utility methods for generating the list of expected warnings