Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Trimming] Use typed bindings internally #20567

Merged
merged 12 commits into from Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions 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.
Expand Up @@ -210,6 +210,7 @@
<PropertyGroup>
<MauiXamlRuntimeParsingSupport Condition="'$(MauiXamlRuntimeParsingSupport)' == '' and '$(PublishAot)' == 'true'">false</MauiXamlRuntimeParsingSupport>
<MauiEnableIVisualAssemblyScanning Condition="'$(MauiEnableIVisualAssemblyScanning)' == ''">false</MauiEnableIVisualAssemblyScanning>
<MauiShellSearchResultsRendererDisplayMemberNameSupported Condition="'$(MauiShellSearchResultsRendererDisplayMemberNameSupported)' == '' and '$(PublishAot)' == 'true'">false</MauiShellSearchResultsRendererDisplayMemberNameSupported>
<MauiQueryPropertyAttributeSupport Condition="'$(MauiQueryPropertyAttributeSupport)' == ''">false</MauiQueryPropertyAttributeSupport>
</PropertyGroup>
<ItemGroup>
Expand All @@ -221,6 +222,10 @@
Condition="'$(MauiEnableIVisualAssemblyScanning)' != ''"
Value="$(MauiEnableIVisualAssemblyScanning)"
Trim="true" />
<RuntimeHostConfigurationOption Include="Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported"
Condition="'$(MauiShellSearchResultsRendererDisplayMemberNameSupported)' != ''"
Value="$(MauiShellSearchResultsRendererDisplayMemberNameSupported)"
Trim="true" />
<RuntimeHostConfigurationOption Include="Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported"
Condition="'$(MauiQueryPropertyAttributeSupport)' != ''"
Value="$(MauiQueryPropertyAttributeSupport)"
Expand Down
Expand Up @@ -5,6 +5,7 @@
using Android.Views;
using Android.Widget;
using Java.Lang;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using AView = Android.Views.View;

Expand Down Expand Up @@ -66,7 +67,24 @@ DataTemplate 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<ShellSearchViewAdapter>()?.LogError(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning);
throw new InvalidOperationException(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning);
}
#pragma warning restore CS0618
}

label.HorizontalTextAlignment = TextAlignment.Center;
label.VerticalTextAlignment = TextAlignment.Center;

Expand Down
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Specialized;
using Foundation;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Extensions.Logging;
using ObjCRuntime;
using UIKit;

Expand Down Expand Up @@ -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)
jonathanpeppers marked this conversation as resolved.
Show resolved Hide resolved
{
Application.Current?.FindMauiContext()?.CreateLogger<ShellSearchResultsRenderer>()?.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;

Expand Down Expand Up @@ -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;

Expand Down
22 changes: 12 additions & 10 deletions src/Controls/src/Core/ContentConverter.cs
Expand Up @@ -66,29 +66,31 @@ 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<TSource, TProperty>(
BindableObject content,
BindableProperty property,
Func<TSource, TProperty> getter)
{
if (content.IsSet(property) || content.GetIsBound(property))
{
// Don't override the property if user has already set it
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)
Expand Down
11 changes: 9 additions & 2 deletions src/Controls/src/Core/ContentPresenter.cs
Expand Up @@ -3,6 +3,7 @@
using System.ComponentModel;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls
{
Expand All @@ -16,8 +17,14 @@ public class ContentPresenter : Compatibility.Layout, IContentView
/// <include file="../../docs/Microsoft.Maui.Controls/ContentPresenter.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
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));
}

/// <include file="../../docs/Microsoft.Maui.Controls/ContentPresenter.xml" path="//Member[@MemberName='Content']/Docs/*" />
Expand Down
23 changes: 12 additions & 11 deletions src/Controls/src/Core/Items/CarouselView.cs
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls
{
Expand Down Expand Up @@ -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));
}

/// <include file="../../../docs/Microsoft.Maui.Controls/CarouselView.xml" path="//Member[@MemberName='IsScrolling']/Docs/*" />
Expand Down
8 changes: 7 additions & 1 deletion src/Controls/src/Core/ListView/ListView.cs
Expand Up @@ -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;
}

Expand Down
74 changes: 48 additions & 26 deletions src/Controls/src/Core/RadioButton/RadioButton.cs
Expand Up @@ -444,37 +444,48 @@ 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()
{
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<TProperty>(
BindableProperty property,
Func<RadioButton, TProperty> 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
{
Expand Down Expand Up @@ -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);
Expand Down