Coder Social home page Coder Social logo

communitytoolkit / maui.markup Goto Github PK

View Code? Open in Web Editor NEW
468.0 21.0 35.0 1.01 MB

The .NET MAUI Markup Community Toolkit is a community-created library that contains Fluent C# Extension Methods to easily create your User Interface in C#

Home Page: https://learn.microsoft.com/dotnet/communitytoolkit/maui/markup/markup

License: MIT License

C# 99.78% PowerShell 0.22%
dotnet communitytoolkit dotnet-maui csharp csharp-markup csharp-ui hacktoberfest maui dotnet-communitytoolkit

maui.markup's Introduction

.NET MAUI Community Toolkit bot .NET Foundation

Build Status NuGet

.NET MAUI Markup Community Toolkit

The .NET MAUI Markup Community Toolkit is a collection of Fluent C# Extension Methods that allows developers to continue architecting their apps using MVVM, Bindings, Resource Dictionaries, etc., without the need for XAML

All features are contributed by you, our amazing .NET community, and maintained by a core set of maintainers.

And – the best part – the features you add to the .NET MAUI Toolkit may one day be included into the official .NET MAUI library! We leverage the Community Toolkits to debut new features and work closely with the .NET MAUI engineering team to nominate features for promotion.

Getting Started

In order to use the .NET MAUI Community Toolkit you need to call the extension method in your MauiProgram.cs file as follows:

using CommunityToolkit.Maui.Markup;

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    // Initialise the toolkit
    builder.UseMauiApp<App>().UseMauiCommunityToolkitMarkup();
    // the rest of your logic...
}

Documentation

image

All of the documentation for CommunityToolkit.Maui.Markup can be found here on Microsoft Learn:

https://learn.microsoft.com/dotnet/communitytoolkit/maui/markup/markup

Examples

Here are some brief examples showing how common tasks can be achieved through the use of the Markup package.

Bindings

C# Markup allows us to define the binding fluently and therefore chain multiple methods together to reduce the verbosity of our code:

new Entry()
    .Bind(Entry.TextProperty, static (ViewModel vm) => vm.RegistrationCode);

For further details on the possible options for the Bind method refer to the BindableObject extensions documentation.

Sizing

Markup allows us to define the sizing fluently and therefore chain multiple methods together to reduce the verbosity of our code:

new Entry().Size(200, 40);

For further details on the possible options for the Size method refer to the VisualElement extensions documentation.

In-depth example

This example creates a Grid object, with child Label and Entry objects. The Label displays text, and the Entry data binds to the RegistrationCode property of the viewmodel. Each child view is set to appear in a specific row in the Grid, and the Entry spans all the columns in the Grid. In addition, the height of the Entry is set, along with its keyboard, colors, the font size of its text, and its Margin.

C# Markup extensions also allow developers to define names for Columns and Rows (e.g. Column.Input) using an enum.

C# Markup enables this to be defined using its fluent API:

using static CommunityToolkit.Maui.Markup.GridRowsColumns;

class SampleContentPage : ContentPage
{
    public SampleContentPage()
    {
        Content = new Grid
        {
            RowDefinitions = Rows.Define(
                (Row.TextEntry, 36)),

            ColumnDefinitions = Columns.Define(
                (Column.Description, Star),
                (Column.Input, Stars(2))),

            Children =
            {
                new Label()
                    .Text("Code:")
                    .Row(Row.TextEntry).Column(Column.Description),

                new Entry
                {
                    Keyboard = Keyboard.Numeric,
                    BackgroundColor = Colors.AliceBlue,
                }.Row(Row.TextEntry).Column(Column.Input)
                 .FontSize(15)
                 .Placeholder("Enter number")
                 .TextColor(Colors.Black)
                 .Height(44)
                 .Margin(5, 5)
                 .Bind(Entry.TextProperty, static (ViewModel vm) vm => vm.RegistrationCode)
            }
        };
    }

    enum Row { TextEntry }
    enum Column { Description, Input }
}

Submitting A New Feature

New features will follow this workflow, described in more detail in the steps below

New Feature Workflow

1. Discussion Started

Debate pertaining to new Maui Toolkit features takes place in the form of Discussions in this repo.

If you want to suggest a feature, discuss current design notes or proposals, etc., please open a new Discussion topic.

Discussions that are short and stay on topic are much more likely to be read. If you leave comment number fifty, chances are that only a few people will read it. To make discussions easier to navigate and benefit from, please observe a few rules of thumb:

  • Discussion should be relevant to the .NET MAUI Toolkit. If they are not, they will be summarily closed.
  • Choose a descriptive topic that clearly communicates the scope of discussion.
  • Stick to the topic of the discussion. If a comment is tangential, or goes into detail on a subtopic, start a new discussion and link back.
  • Is your comment useful for others to read, or can it be adequately expressed with an emoji reaction to an existing comment?

2. Proposal Submitted

Once you have a fully fleshed out proposal describing a new feature in syntactic and semantic detail, please open an issue for it, and it will be labeled as a Proposal. The comment thread on the issue can be used to hash out or briefly discuss details of the proposal, as well as pros and cons of adopting it into the .NET MAUI Toolkit. If an issue does not meet the bar of being a full proposal, we may move it to a discussion, so that it can be further matured. Specific open issues or more expansive discussion with a proposal will often warrant opening a side discussion rather than cluttering the comment section on the issue.

3. Proposal Championed

When a member of the .NET MAUI Toolkit core team finds that a proposal merits promotion into the Toolkit, they can Champion it, which means that they will bring it to the monthly .NET MAUI Toolkit Community Standup.

4. Proposal Approved

The .NET MAUI Toolkit core team will collectively vote to work on adopting and/or modifying the proposal, requiring a majority approval (i.e. greater than 50%) to be added to the Toolkit.

Once a Proposal has been championed and has received a majority approval from the .NET MAUI Toolkit core team, a Pull Request can be opened.

5. Pull Request Approved

After a Pull Request has been submitted, it will be reviewed and approved by the Proposal Champion.

Every new feature also requires an associated sample to be added to the .NET MAUI Toolkit Sample app.

6. Documentation Complete

Before a Pull Request can be merged into the .NET MAUI Toolkit, the Pull Request Author must also submit the documentation to our documentation repository.

7. Merged

Once a Pull Request has been reviewed + approved AND the documentation has been written, submitted and approved, the new feature will be merged adding it to the .NET MAUI Toolkit

Code of Conduct

As a part of the .NET Foundation, we have adopted the .NET Foundation Code of Conduct. Please familiarize yourself with that before participating with this repository. Thanks!

.NET Foundation

This project is supported by the .NET Foundation.

maui.markup's People

Contributors

bijington avatar brminnick avatar danielftz avatar dependabot[bot] avatar fredericoregateiro avatar github-actions[bot] avatar hobdev avatar jfversluis avatar joonghyuncho avatar mrlacey avatar rixcrafts avatar vladislavantonyuk avatar youssef1313 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

maui.markup's Issues

[Bug] Refresh view is off the screen

Description

Hi, I'm a newbie trying to learn Markup. I cloned the https://github.com/CommunityToolkit/Maui.Markup/tree/main/samples and ran it on iOS simulator on macOS M1, I noticed that the refresh view is off the screen. Also when I set the new count to "1" then the news list is also off the screen.

Stack Trace

N/A

Link to Reproduction Sample

https://github.com/CommunityToolkit/Maui.Markup/tree/main/samples

Steps to Reproduce

Please see above

Expected Behavior

  • Refresh view control is in the center
  • When news settings count = 1, the first news should be in the center
  • When news count = 2, the view should show 2 items

Actual Behavior

Untitled.mov

Basic Information

❯ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.400
 Commit:    7771abd614

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  12.5
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.400/

global.json file:
  Not found

Host:
  Version:      6.0.8
  Architecture: arm64
  Commit:       55fb7ef977

.NET SDKs installed:
  6.0.400 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Workaround

Reproduction imagery

Screen Shot 2022-09-03 at 10 49 42 PM

[Proposal] Add `RelativeBindingSource` Support to Typed Bindings Extensions

Feature name

Add RelativeBindingSource Support to Typed Bindings

Link to discussion

None

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

This Proposal improves support for RelativeBindingSource to our TypedBinding extensions.

This is not a breaking change and improves our API surface to better match .NET MAUI's TypedBinding API for its TypedBinding.Source property.

RelativeBindingSource is the recommended way for .NET MAUI developers to creating a binding referencing themselves or an ancestor without needing to pass in an instance of that object

  • Self Binding
    • RelativeBindingSource.Self
  • Binding to an ancestor in the UI hierarchy
    • new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(Page)))
  • Binding to an ancestor in the BindingContext hierarchy
    • new RelativeBindingSource(RelativeBindingSourceMode.FindAncestorBindingContext, typeof(MyViewModel))

Current Workaround

The current workaround to use RelativeBindingSource with our existing TypedBinding APIs is to first use .Assign() to assign the control to a variable, then pass that variable in as the source parameter.

.Assign(out CarouselView carouselView)
.Bind(CarouselView.CurrentItemChangedCommandParameterProperty, static (CarouselView view) => view.CurrentItem, source: carouselView)

Motivation

All of our TypedBinding APIs (example below) require the source parameter type to match the TBindingContext used in by the getter and setter parameter:

  • Expression<Func<TBindingContext, TSource>> getter
  • Action<TBindingContext, TSource>? setter = null
  • TBindingContext? source = default // Cannot be of type RelativeBindingSource
public static TBindable Bind<TBindable, TBindingContext, TSource>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Expression<Func<TBindingContext, TSource>> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode mode = BindingMode.Default,
		string? stringFormat = null,
		TBindingContext? source = default) where TBindable : BindableObject);

Detailed Design

The best way to support RelativeBindingSource, is to lower the Type of the API's source parameter from TBindingContext? to object?.

Using object? for the source parameter matches the .NET MAUI API for the TypedBinding.Source property.

Current Source Parameter

TBindingContext? source = default

Updated Source Parameter

object? source = null

Example Updated API

/// <summary>Bind to a specified property</summary>
public static TBindable Bind<TBindable, TBindingContext, TSource>(
  this TBindable bindable,
  BindableProperty targetProperty,
  Expression<Func<TBindingContext, TSource>> getter,
  Action<TBindingContext, TSource>? setter = null,
  BindingMode mode = BindingMode.Default,
  string? stringFormat = null,
  object? source = null) // Lowered from TBindingContext? source = default
where TBindable : BindableObject;

Usage Syntax

.Bind(CarouselView.CurrentItemChangedCommandParameterProperty, static (CarouselView view) => view.CurrentItem, source: RelativeBindingSource.Self)

Drawbacks

I don't see any drawbacks.

We will need to update our Unit Tests to validate RelativeBindingSource.

We will also need to update our Unit Tests with invalid source parameters to ensure .NET MAUI throws an exception if a user provides an invalid source parameter to our API now that it supports any object type.

Alternatives

An alternative to lowering our existing API surface from TBindingContext? source = default to object? source = null would be to create new APIs specific to RelativeBindingSource (example below).

I do not recommend this approach for two reasons:

  • It doubles our API surface for TypedBinding support, increasing our maintenance cost
    • Requires one TypedBinding API for TBindingContext? source = default and a second TypedBinding API for object? source = null
  • Lowering the parameter Type to object? better matches .NET MAUI's TypedBinding.Source API (public object? Source)

Example API using RelativeBindingSource

public static TBindable Bind<TBindable, TBindingContext, TSource>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Expression<Func<TBindingContext, TSource>> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode mode = BindingMode.Default,
		string? stringFormat = null,
		RelativeBindingSource? source = null) where TBindable : BindableObject);

Unresolved Questions

None

[BUG] Typed bindings is not working as expected.

Is there an existing issue for this?

  • I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

Typed binding breaks the working behavior of the controls, whereas classic binding works in the way it is designed.

Expected Behavior

Like how it works in classic binding, only the approach changes with improved type safety.

Steps To Reproduce

  1. Clone the repository
  2. Run the sample app
  3. It hosts two controls on the Main page of the app, both are pickers, one with typed binding and the other with classic binding
  4. Both are bound to the same Index property, which means a change in one picker should reflect in the other
  5. Now change the value in the type picker and no response in the classic picker (the issue)
  6. Then change the value in the classic picker, there is a change in the type picker (actual behavior)

Link to public reproduction project repository

https://github.com/egvijayanand/markup-issue-272

Environment

- .NET MAUI C# Markup CommunityToolkit: v4.0.0
- OS: Windows 11 v10.0.22631.2715
- .NET MAUI: v8.0.3

Anything else?

No response

[Proposal] Absolute layout LayoutBounds and LayoutFlag extension

Absolute layout LayoutBounds and LayoutFlag markup extension

  • Proposed
  • Prototype
  • Implementation
  • Unit Tests
  • Documentation
  • Sample

Link to Discussion

#22

Summary

I propose a ViewInAbsoluteLayoutExtensions static class, where LayoutBounds and LayoutFlag static methods (and their overloads) are implemented.

In most cases when using AbsoluteLayout, every child view will need to define two attached properties, LayoutBounds and LayoutFlag, where LayoutBounds is a Rectangle struct and LayoutFlag is an AbsoluteLayoutFlags Enum. Thus, the ViewInAbsoluteLayoutExtensions will offer the child View the ability to define these two attached properties declaratively.

Example:

BoxView box= new BoxView
{
   Color = Colors.Red,
}.LayoutBounds(0.5, 0.5, 100, 100)
.LayoutFlags(AbsoluteLayoutFlags.PositionProportional);

Motivation

Currently In MCT Markup, there are no declarative way to define LayoutBounds and LayoutFlag (like Row and Column for child Views in Grid)

Detailed Design

The ViewInAbsoluteLayoutEx tensions static class should at the very least include 4 extension methods to cover the 4 basic use cases,

  • setting AbsoluteLayoutFlags
public static TView LayoutFlags<TView>(this TView view, AbsoluteLayoutFlags flag) where TView : View
{
    view.SetValue(AbsoluteLayout.LayoutFlagsProperty, flag);
    return view;
}
  • setting position only LayoutBounds value (Not sure if this implementation is correct)
public static TView LayoutBounds<TView>(this TView view, Point point) where TView : View
{
    view.SetValue(AbsoluteLayout.LayoutBoundsProperty, point);
    return view;
}
  • setting position and size LayoutBounds value
public static TView LayoutBounds<TView>(this TView view, Rectangle rectangle) where TView : View
{
    view.SetValue(AbsoluteLayout.LayoutBoundsProperty, rectangle);
    return view;
}

Additionally, LayoutBounds method should have overloads mostly for the purpose of ease of use. Allowing the user to input in x,y,width,height directly, instead of encapsulating these values in a Rectangle struct.

public static TView LayoutBounds<TView>(this TView view, int x, int y) where TView : View
public static TView LayoutBounds<TView,TCoord>(this TView view, TCoord x, TCoord y) where TView : View where TCoord : Enum
public static TView LayoutBounds<TView>(this TView view, int x, int y, int width , int height) where TView : View
public static TView LayoutBounds<TView, TCoord>(this TView view, TCoord x, TCoord y, TCoord width, TCoord height) where TView : View where TCoord : Enum
public static TView LayoutBounds<TView>(this TView view, Point point, Size size) where TView : View

There also needs to be a private ToDouble helper method in this class, as double is expected input type for Rectangle struct

static double ToDouble(this Enum enumValue) => Convert.ToDouble(enumValue, CultureInfo.InvariantCulture);

Drawbacks

None that i can think of.

Alternatives

None that i can think of.

Unresolved Questions

  • For child View objects that need to use AbsoluteValue.AutoSize, the implementation still needs more testing.
  • Sometimes if the user only wants to change the position or the size of the child View, should they be given the option to do that with one of the overloads?

Fluent API support


Issue moved from dotnet/maui#12380


From @idexus on Monday, January 2, 2023 8:09:40 PM

Discussed in dotnet/maui#12273

Description

I would like to have fluent API support in MAUI so first I created a library https://github.com/idexus/Sharp.UI then because some classes are sealed, I decided to create a fork https://github.com/idexus/maui to fully support this feature in the MAUI project. Most code is generated using source generators.

I have two questions

  • Is it possible to include fluent API support directly in the maui project?
  • If there is no way to add such support to the maui project for now, is there a way to "unseal" the maui classes?

I think for many it would make it much easier to create an interface without the need for XAML, without disabling the possibility of using it.

My goal was:

new ScrollView
{
    new VerticalStackLayout
    {
        new Grid
        {
            new Label("Hello")
                .Column(0)
                .FontSize(28),
            
            new Label("World", out var label)
                .Column(1),

            new Button("Click me")
                .Row(1)
                .ColumnSpan(2)
                .OnClicked(button => {
                    label.Text = "you";
                });
        }
        .ColumnDefinitions(e => e.Star(2).Star(1))
        .RowDefinitions(e => e.Star().Absolute(100)),
            
        new Path
        {
            new GeometryGroup
            {
                new PathGeometry
                {
                    new PathFigure(15, 50)
                    {
                        new LineSegment(800,150),
                        new LineSegment(500,50)
                    }
                },

                new EllipseGeometry()
                    .Center(new Point(50,70))
                    .RadiusX(10)
                    .RadiusY(50),
            }
        }
        .Stroke(Colors.Yellow)
        .Fill(Colors.Red),
    }
}

Unfortunately some classes are sealed, e.g.

PathGeometry, PathFigure, TapGestureRecognizer, PinchGestureRecognizer, PointerGestureRecognizer, SwipeGestureRecognizer, Ellipse, Polyline, Line, Path, Polygon, Rectangle, RoundRectangle, TableSection, ColumnDefinition, RowDefinition Style, Trigger,... etc.

If I want to create a library with these classes so that I could create the UI declaratively as I described, only in code using fluent interface, I can't do it without wrapping them.

Detailed description

Below is a description of how each problem was solved.

Properties

Fluent extension methods are generated for all properties and bindable properties (for all derived classes of BindableObject, and for Style, VisualState, VisualStateGroup, VisualStateGroupList classes)

Usage:

Label()
  .FontSize(28)
  .TextColor(Colors.White)

Generated methods for FontSize property:

public static T FontSize<T>(this T obj,
    double fontSize)
    where T : Microsoft.Maui.Controls.Label
{
    obj.FontSize = fontSize;
    return obj;
}

public static T FontSize<T>(this T obj,
    System.Func<ValueBuilder<double>, ValueBuilder<double>> buidValue)
    where T : Microsoft.Maui.Controls.Label
{
    var builder = buidValue(new ValueBuilder<double>());
    if (builder.ValueIsSet()) obj.FontSize = builder.GetValue();
    return obj;
}

public static T FontSize<T>(this T obj,
    System.Func<LazyValueBuilder<double>, LazyValueBuilder<double>> buidValue)
    where T : Microsoft.Maui.Controls.Label
{
    var builder = buidValue(new LazyValueBuilder<double>());
    if (builder.ValueIsSet()) obj.FontSize = builder.GetValue();
    return obj;
}

public static T FontSize<T>(this T obj,
    System.Func<BindingBuilder<double>, BindingBuilder<double>> buidBinding)
    where T : Microsoft.Maui.Controls.Label
{
    var builder = buidBinding(new BindingBuilder<double>(obj, Microsoft.Maui.Controls.Label.FontSizeProperty));
    builder.BindProperty();
    return obj;
}

EventHandler

Fluent extension methods are generated for each EventHandler adding an On prefix

usage example:

new VerticalStackLayout
{
    new Label("Hello"),
    new Button("Click me")
        OnClicked(button => {

        });
}

generated methods for Clicked event handler:

public static T OnClicked<T>(this T obj, System.EventHandler handler)
    where T : Microsoft.Maui.Controls.Button
{
    obj.Clicked += handler;
    return obj;
}

public static T OnClicked<T>(this T obj, System.Action<T> action)
    where T : Microsoft.Maui.Controls.Button
{
    obj.Clicked += (o, arg) => action(obj);
    return obj;
}

ContentProperty attributes

Implementation of IList (if not implemented yet) or IEnumerable interfaces to add Add() method support to class are generated for all classes with the ContentProperty attribute.

Example usage:

new Border
{
    new VerticalStackLayout
    {
        new Label("Hello").FontSize(28),
        new Grid
        {
            ...
        },
        new HorizontalStackLayout
        {
            new Label("foo"),
            new Button("OK").OnClicked(OnClickedButton)
        }

    }
}

Generated class implementation for single item containers:

public partial class Border : IEnumerable
{

    ...

    // ----- single item container -----

    public IEnumerator GetEnumerator() { yield return this.Content; }

    public void Add(Microsoft.Maui.Controls.View? content) => this.Content = content;

}

Generated class implementation for collection containers:

public partial class Shell : IList<Microsoft.Maui.Controls.ShellItem>
{

    ...

    // ----- collection container -----

    public int Count => this.Items.Count;
    public Microsoft.Maui.Controls.ShellItem this[int index] { get => this.Items[index]; set => this.Items[index] = value; }
    public bool IsReadOnly => false;
    public void Add(Microsoft.Maui.Controls.ShellItem item) => this.Items.Add(item);
    public void Clear() => this.Items.Clear();
    public bool Contains(Microsoft.Maui.Controls.ShellItem item) => this.Items.Contains(item);
    public void CopyTo(Microsoft.Maui.Controls.ShellItem[] array, int arrayIndex) => this.Items.CopyTo(array, arrayIndex);
    public IEnumerator<Microsoft.Maui.Controls.ShellItem> GetEnumerator() => this.Items.GetEnumerator();
    public int IndexOf(Microsoft.Maui.Controls.ShellItem item) => this.Items.IndexOf(item);
    public void Insert(int index, Microsoft.Maui.Controls.ShellItem item) => this.Items.Insert(index, item);
    public bool Remove(Microsoft.Maui.Controls.ShellItem item) => this.Items.Remove(item);
    public void RemoveAt(int index) => this.Items.RemoveAt(index);
    IEnumerator IEnumerable.GetEnumerator() => this.Items.GetEnumerator();

}

Constructors

Aditional constructors

Additional constructors have been added to simplify the interface creation process

example usage:

new VerticalStackLayout
{
    new Label("Hello"),
    new Button("Click me")
        OnClicked(button => {

        });
}

implementation

public partial class Label
{
    public Label(string text) : this()
    {
        this.Text = text;
    }
}

Additional out parameter

Additional constructors with the out parameter are generated for all public constructors.

usage:

new VerticalStackLayout
{
    new Label("Hello", out var label)
    new Button("Click me")
        OnClicked(button => {
            label.Text = "Clicked";
        });
}

genetated constructor:

public partial class Label : IEnumerable
{

    // ----- constructors -----

    public Label(out Label label) : this()
    {
        label = this;
    }

    public Label(string text, out Label label) : this(text)
    {
        label = this;
    }

    ...
}

In-line value builders

BindingBuilder

For in-line creation of bindings

usage:

new VerticalStackLayout
{
    new Slider(1, 100, 1, out var slider),
    new Label()
        .Text(e => e.Path("Value").Source(slider).StringFormat("Value {0:F1}"))
}

ValueBuilder

To set values depending on the app theme, platform or device idiom

new Label("Hello")
    .TextColor(e => e.OnDark(Colors.White).OnLight(Colors.Black))
    .FontSize(e => e.Default(28).OnPhone(18).OnTV(40))

LazyValueBuilder

To lazily set values depending on the app theme, platform or device idiom

new Label()
    .Text(e => e
        .Default(() => DefaultName())
        .OnAndroid(() => AndroidName())
    )

ColumnDefinitionBuilder and RowDefinitionBuilder

To define the number and sizes of rows and columns.

example:

new Grid
{
    ...
}
.RowDefinitions(e => e.Star(2).Star().Star(3))
.ColumnDefinitions(e => e.Absolute(100).Star());

In this example you define

  • 3 rows - Star(2), Star(), Star(3)
  • 2 columns - Absolute(100), Star()

Style

BindableProperty => Setter

Button.BackgroundColorProperty.Set(Colors.White),

Button.TextColorProperty.Set().OnLight(Colors.White).OnDark(AppColors.Primary),

implementation

public static class BindablePropertyExtension
{
    public static Setter Set(this BindableProperty property, object value) => new Setter { Property = property, Value = value };
    public static Setter Set(this BindableProperty property) => new Setter { Property = property };		
}

public static class SetterExtension
{
    public static Setter OnLight(this Setter setter, object value) { if (Application.Current?.RequestedTheme == AppTheme.Light) setter.Value = value; return setter; }
    public static Setter OnDark(this Setter setter, object value) { if (Application.Current?.RequestedTheme == AppTheme.Dark) setter.Value = value; return setter; }
    ...
}

IList Add methods

Additional Add() methods for the IList interface

public partial class Style
{
    public void Add(Setter setter) {...}
    public void Add(Trigger trigger) {...}
    public void Add(DataTrigger trigger) {...}
    public void Add(VisualStateGroupList groupList) {...}
    public void Add(VisualStateGroup group) {...}
    public void Add(VisualState visualState) {...}
}

Usage example

new Style(typeof(Button))
{
    Button.TextColorProperty.Set().OnLight(Colors.White).OnDark(AppColors.Primary),
    Button.BackgroundColorProperty.Set().OnLight(AppColors.Primary).OnDark(Colors.White),
    Button.FontSizeProperty.Set(14).OnDesktop(20),
    Button.CornerRadiusProperty.Set(8).OniOS(15),
    new VisualState(VisualState.VisualElement.Normal)
    {
        Button.TextColorProperty.Set().OnLight(Colors.White).OnDark(AppColors.Primary),
        Button.BackgroundColorProperty.Set().OnLight(AppColors.Primary).OnDark(Colors.White),
    },
    new VisualState(VisualState.VisualElement.Disabled)
    {
        Button.TextColorProperty.Set().OnLight(AppColors.Gray950).OnDark(AppColors.Gray200),
        Button.BackgroundColorProperty.Set().OnLight(AppColors.Gray200).OnDark(AppColors.Gray600),
    },
},

User defined classes

[FluentInterface] attribute

Fluent methods will be generated for user definied classes with the [FluentInterface] attribute

[BindableProperties] attribute

Bindable properties and fluent methods will be generated for user definied classes with interfaces with the [BindableProperties] attribute

Additional atributes:
  • [DefaultValue] to define default values
  • [PropertyCallbacks(propertyChanged, propertyChanging,validateValue, coerceValue, defaultValueCreator] to define callback names
Example
[BindableProperties]
public interface IAngleViewModelProperties
{
    [PropertyCallbacks(propertyChanged: "OnAngleChanged")]
    public double RawAngle { get; set; }

    [PropertyCallbacks(coerceValue: "CoerceAngle")]
    public double Angle { get; set; }

    [DefaultValue(360.0)]
    public double MaximumAngle { get; set; }
}


[FluentInterface]
public partial class AngleViewModel : BindableObject, IAngleViewModelProperties
{
    static void OnAngleChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var viewModel = bindable as AngleViewModel;
        viewModel.Angle = (double)newValue;
    }

    static object CoerceAngle(BindableObject bindable, object value)
    {
        var viewModel = bindable as AngleViewModel;
        double input = (double)value;

        if (input > viewModel.MaximumAngle)
            input = viewModel.MaximumAngle;

        return input;
    }
}

Custom user view example

[BindableProperties]
public interface ICardViewProperties
{
    string CardTitle { get; set; }
    string CardDescription { get; set; }
    Color CardColor { get; set; }
    Color BorderColor { get; set; }
    Style DescriptionStyle { get; set; }
    View ContentView { get; set; }
    string ButtonTitle { get; set; }
}

[FluentInterface]
[ContentProperty(nameof(ContentView))]
public partial class CardView : ContentView, ICardViewProperties
{
    public event EventHandler Clicked;

    public CardView()
    {
        this.BindingContext = this;
        Content = new Border
        {
            new Grid
            {
                new VerticalStackLayout
                {
                    new Label(out var label1)
                        .Text(e => e.Path(nameof(CardTitle)))
                        .FontSize(29)
                        .TextColor(Colors.White),

                    new Label()
                        .Text(e => e.Path(nameof(CardDescription)))
                        .Style(e => e.Path(nameof(DescriptionStyle))),
                },

                new ContentView()
                    .Row(1)
                    .Content(e => e.Path(nameof(ContentView)))
                    .HorizontalOptions(LayoutOptions.Center)
                    .VerticalOptions(LayoutOptions.Center)
                    .SizeRequest(120,120),

                new Button()
                    .Row(2)
                    .Text(e => e.Path(nameof(ButtonTitle)))
                    .BackgroundColor(Colors.LightGray)
                    .TextColor(Colors.Black)
                    .OnClicked((sender, e) => Clicked(sender,e))
            }
            .RowDefinitions(e => e.Star(1.0).Star(2.0).Star(0.7))
            .RowSpacing(10)
        }
        .StrokeShape(new RoundRectangle().CornerRadius(10))
        .Stroke(e => e.Path(nameof(BorderColor)))
        .BackgroundColor(e => e.Path(nameof(CardColor)))
        .SizeRequest(200, 300)
        .Margin(50)
        .Padding(20);
    }
}

public partial class CardViewPage : ContentPage
{
    public CardViewPage()
    {
        this.Content = new VerticalStackLayout
        {
            new Slider(1, 100, 1, out var slider),

            new HorizontalStackLayout
            {
                new CardView(out var cardNo1)
                {
                    new Image("dotnet_bot.png").Aspect(Aspect.AspectFit)
                }
                .CardTitle(e => e.Path("Value").Source(slider).StringFormat("Value {0:F1}"))
                .ButtonTitle("Play")
                .CardDescription("Do you like it")
                .CardColor(Colors.DarkSlateGrey)
                .BorderColor(Colors.DarkGrey)
                .OnClicked(e =>
                {
                    cardNo1.CardDescription = "Let's play :)";
                }),

                new CardView(out var cardView)
                {
                    new VerticalStackLayout
                    {
                        new Label("This is a simple card view example"),
                        new Label("Second label")
                            .TextColor(Colors.Red)
                            .FontSize(18)
                    }					
                }
                .CardTitle("Title 2")
                .ButtonTitle("Stop")
                .CardColor(Colors.DarkSlateGrey)
                .BorderColor(Colors.DarkGrey)
                .DescriptionStyle(new Style(typeof(Label))
                {
                    Label.TextColorProperty.Set(Colors.Blue),
                    Label.FontSizeProperty.Set(20)
                })
            }
            .HorizontalOptions(LayoutOptions.Center)
        }
        .VerticalOptions(LayoutOptions.Center)
        .Padding(100);
    }
}

[Bug] Cursor Position wrong in Entry field when using IValueConverter

I have a page created with a number of entry fields which are formatted as either dollars, or percentages. When editing the value, once the new string has been formatted and returned to the field, the caret switches to the front of the entry field, making it unusable. See repo/gif at end

Stack Trace

N/A

Link to Reproduction Sample

https://github.com/devonuto/MauiBugs/tree/master/FormattingBug

Steps to Reproduce

  1. Add Entry field to page
  2. Bind entry field to value and converter
  3. Alter the value in the entry field

Expected Behavior

The cursor/caret would stay at the end of the entry field and not move to the start

Actual Behavior

It moves to ahead of the dollar sign after each keypress. This didn't happen using the same converter and XAML in Xamarin forms.

Basic Information

  • Version with issue: RC2
  • Last known good version: Unknown
  • IDE:
  • Platform Target Frameworks:
    • Android: 12.1 (API 32)
  • Android Support Library Version:
  • Nuget Packages:
    <PackageReference Include="CommunityToolkit.Maui.Markup" Version="1.0.0-rc2" /
- Affected Devices:

Workaround

Tried to do this, but it wasn't reliable enough

private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
    var entry = sender as Entry;
    entry.CursorPosition = entry.Text.Length;
}

Reproduction imagery

qemu-system-x86_64_6iH86GbTfO

[Proposal] Remove `BindableObject` Constraint from `.Assign()` and `.Invoke()`

Remove Constraints from Assign() and Invoke()

Summary

This Proposal removes the BindableObject constraint (where T : BindableObject) from .Assign() and .Invoke() extension methods.

Before

public static TBindable Assign<TBindable, TVariable>(this TBindable bindable, out TVariable variable) 
  where TBindable : BindableObject, TVariable;

public static TBindable Invoke<TBindable>(this TBindable bindable, Action<TBindable> action) 
  where TBindable : BindableObject;

After

public static TAssignable Assign<TAssignable, TVariable>(this TAssignable assignable, out TVariable variable) 
  where TAssignable : TVariable;

public static TAssignable Invoke<TAssignable>(this TAssignable assignable, Action<TAssignable> action);
  // no constraint

Motivation

There is nothing in Assign() or Invoke() that requires generic, T, to be a BindableObject.

Removing the constraint (eg removing where T : Bindable), allows the .Assign()and .Invoke() extension methods to be used on any type.

This is especially useful for custom controls that may implement BindableObject.

Detailed Design

I recommend creating a new class, public static class Object Extensions.

Both .Assign() and .Invoke() are currently in BindableObjectExtensions.cs. Keeping them in BindableObjectExtensions.cs doesn't make much sense now that they are no longer constrained to BindableObject.

namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// Extension Methods for Unconstrained Objects
/// </summary>
public static class ObjectExtensions
{
	/// <summary>
	/// Assign <typeparam name="TAssignable"> to a variable
	/// </summary>
	/// <typeparam name="TAssignable"></typeparam>
	/// <typeparam name="TVariable"></typeparam>
	/// <param name="assignable"></param>
	/// <param name="variable"></param>
	/// <returns>TBindable</returns>
	public static TAssignable Assign<TAssignable, TVariable>(this TAssignable assignable, out TVariable variable)
		where TAssignable : TVariable
	{
		variable = assignable;
		return assignable;
	}

	/// <summary>
	/// Perform an action on <typeparam name="TAssignable">
	/// </summary>
	/// <typeparam name="TAssignable"></typeparam>
	/// <param name="assignable"></param>
	/// <param name="action"></param>
	/// <returns>TBindable</returns>
	public static TAssignable Invoke<TAssignable>(this TAssignable assignable, Action<TAssignable> action)
	{
		ArgumentNullException.ThrowIfNull(action);

		action.Invoke(assignable);
		return assignable;
	}
}

Usage Syntax

C# Usage

class SamplePage : ContentPage
{
  readonly Label _label;
  readonly string _title;
  
  public SamplePage()
  {
    Title = "Sample Page".Assign(out _title);
    Content = new Button().Invoke(button => button.Clicked += HandleButtonClicked);
  }
}

Drawbacks

None. This is not a breaking change.

Alternatives

We could create two new extension methods, but since it is not a breaking change to remove constraints, I recommend we move forward with keeping the existing extension methods and removing their constraints.

Unresolved Questions

None

Use with Other Control Vendors?

Can Maui.Markup be used with other control vendor's controls, i.e. Telerik, DevExpress, Syncfusion?

I started playing with Telerik and did not have a lot of luck, I hope I'm missing something simple.

var stackLayoutStopStartButtons = new StackLayout();
var dataGridHashedData = new RadDataGrid();
stackLayoutStopStartButtons.Add(dataGridHashedData);

The last line - Error CS1503 Argument 1: cannot convert from 'Telerik.UI.Xaml.Controls.Grid.RadDataGrid' to 'Microsoft.Maui.IView' V.RelayDatabase (net6.0-windows10.0.19041.0)

Thanks!

[♻Housekeeping] Improve our NuGet package quality

Today our NuGet package isn't great and after watching this amazing talk from Claire I saw that we can make our package even better!
Helping users to see our code from VS, and debug our code which will help us fix bugs (since the users can provide more complete information), we can prevent breaking changes, etc...

I'll open one PR per item, to make it easy to review it and see if our CI will be happy.

[ ] Add Source-Link
[ ] Turn-on package validation

[Proposal] Update `LayoutFlags()` Functionality

Feature name

Update LayoutFlags() Functionality

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

Current Functionality

The current functionality of LayoutFlags overwrites any existing AbsoluteLayoutFlags that are already defined by the BindableObject.

For example, the resulting AbosluteLayoutFlag property of the Label in the following sample will be only AbsoluteLayoutFlags.PositionProportional, because the current functionality overwrites any existing AbsoluteLayoutFlag.

new Label() // Final `AbsoluteLayoutFlags` value is `AbsoluteLayoutFlags.PositionProportional`
  .LayoutFlags(AbsoluteLayoutFlags.SizeProportional)
  .LayoutFlags(AbsoluteLayoutFlags.PositionProportional)  // Overwrites existing `AbsoluteLayoutFlags`

Updated Functionality

The updated functionality of LayoutFlags will append the incoming AbsoluteLayoutFlags to every existing AbsoluteLayoutFlags.

For example, the resulting AbosluteLayoutFlag property of the Label in the following sample will be AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.PositionProportional, because the incoming value combined with any existing AbsoluteLayoutFlag values using a bitwise OR operator.

new Label() // Final `AbsoluteLayoutFlags` value is `AbsoluteLayoutFlags.PositionProportional | AbsoluteLayoutFlags.SizeProportional `
  .LayoutFlags(AbsoluteLayoutFlags.SizeProportional)
  .LayoutFlags(AbsoluteLayoutFlags.PositionProportional)  // Combined with existing `AbsoluteLayoutFlags`

Motivation

Updating the functionality of .LayoutFlags() to append the AbsoluteLayoutFlags input allows the ability to chain multiple LayoutFlags() methods together without overriding previous AbsoluteLayoutFlags

Detailed Design

///


/// Set an that indicates whether the layout bounds position and size values for a child are proportional to the size of the .
/// Appends to existing
///

///
/// To clear existing , use
///
///
///
///
///
public static TBindable LayoutFlags(this TBindable bindable, AbsoluteLayoutFlags flag) where TBindable : BindableObject
{
var currentFlags = AbsoluteLayout.GetLayoutFlags(bindable);

AbsoluteLayout.SetLayoutFlags(bindable, currentFlags | flag);
return bindable;
}

Usage Syntax

new Image().Source("dotnet_bot.png").Opacity(0.25).IsOpaque(false).Aspect(Aspect.AspectFit)
  .LayoutFlags(AbsoluteLayoutFlags.SizeProportional)
  .LayoutFlags(AbsoluteLayoutFlags.PositionProportional)
  .LayoutBounds(0.5, 0.5, 0.5, 0.5);

Drawbacks

There is one drawback to updating the LayoutFlags functionality: no other fluent extension method in CommunityToolkit.Maui.Markup appends the input; every other fluent API overwrites the existing property.

That being said appending the value of AbsoluteLayoutFlags does feel natural and does feel appropriate. Whereas, for other fluent APIs, like .BackgroundColor(), appending the input to the existing settings is not intuitive and does not make much sense.

Alternatives

To add multiple AbsoluteLayoutFlags, a developer can use the existing method:

.LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional)

Unresolved Questions

None

[Proposal] Add Overloaded Constructor to `Style<T>` that Accepts `Microsoft.Maui.Controls.Style`

Feature name

Add Overloaded Constructor to Style<T> that Accepts Microsoft.Maui.Controls.Style

Link to discussion

dotnet/maui#19725 (comment)

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

This Proposal adds another constructor to Style<T> that accepts. Microsoft.Maui.Controls.Style.

Motivation

The community has noted that it would be beneficial to easily convert Microsoft.Maui.Controls.Style to CommunityToolkit.Maui.Markup.Style<T>.

Detailed Design

// Additional Implicit Operator 
public static implicit operator CommunityToolkit.Maui.Markup.Style<T>(Microsoft.Maui.Controls.Style style) => new Style<T>(style);

// Additional Overloaded Constructor
public Style(Style mauiStyle)
{
  if (!mauiStyle.TargetType.IsAssignableTo(typeof(T)))
  {
	throw new ArgumentException($"Invalid type. The Type used in {nameof(mauiStyle)}.{nameof(mauiStyle.TargetType)} ({mauiStyle.TargetType.FullName}) must be assignable to the Type used in {nameof(Style<T>)} ({typeof(T).FullName})", nameof(mauiStyle));  
  }

  MauiStyle = mauiStyle;
}

Usage Syntax

Style mauiButtonStyle = new Style(typeof(Button));
Style<Button> markupButtonStyle_Constructor = new Style<Button>(mauiButtonStyle);
Style<Button> markupButtonStyle_Implicit = temp;

Drawbacks

I cannot think of any drawbacks at this time.

Alternatives

The current alternative to converting a Microsoft.Maui.Controls.Style to Style<T> is complicated and requires a for loop to move all of the BindableProperties from Style to Style<T>.

Unresolved Questions

Is there a valid use case where the type for MauiStyle.TargetType wouldn't match the type used for Style?

If not, I think it's best to throw an exception when !mauiStyle.TargetType.IsAssignableTo(typeof(T)).

E.g. The following code throws an InvalidArgumentException:

Style mauiLabelStyle = new Style(typeof(Label));

// Throws InvalidArgumentException
Style<Button> = mauiLabelStyle;

// Throws InvalidArgumentException
var buttonStyle = new Style<Button>(mauiLabelStyle);

[Proposal] Further enhance typed binding support by adding BindCommand

Feature name

Further enhance typed binding support by adding BindCommand

Link to discussion

n/a

Progress tracker

Summary

The work carried out in #155 didn't bring full typed binding support up to par with the existing binding support. We want to add in the BindCommand implementation to match the old binding functionality.

Motivation

Quicker bindings means quicker apps.

Detailed Design

public static TBindable BindCommand<TBindable, TBindingContext, TSource>(
    this TBindable bindable,
    Func<TBindingContext, TSource> getter,
    TSource? source = null,
    string? parameterPath = Binding.SelfPath,
    object? parameterSource = null) where TBindable : BindableObject
{
    ...
}

Usage Syntax

new Button().BindCommand(static (ViewModel vm) => vm.SubmitCommand);

Drawbacks

No response

Alternatives

No response

Unresolved Questions

No response

[Bug] Upgraded to latest version and .Net7 and generated classes for TextAlignmentGenerator do not compile

The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Editor.TextCenterHorizontal(Microsoft.Maui.Controls.Editor)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Editor.TextCenterHorizontal(Microsoft.Maui.Controls.Editor)' (net7.0-windows10.0.19041.0) [REDACTED]\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\EditorTextAlignmentExtensions.g.cs 122 Active

[BUG] Label' does not contain a definition for 'TextCenter'

Is there an existing issue for this?

  • I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

Build fails with 66 errors. Here's one of them:

Label' does not contain a definition for 'TextCenter' and no accessible extension method 'TextCenter' accepting a first argument of type 'Label' could be found maui

Solution is to downgrade the CommunityToolkit.Maui.Markup NuGet package to 3.2.0.

Expected Behavior

Sample application should start after building the solution project.

Steps To Reproduce

  1. Open Sample solution.
  2. Build.

Link to public reproduction project repository

Huh?

Environment

- .NET MAUI CommunityToolkit:
- OS:
- .NET MAUI:

Anything else?

No response

Compiler errors on attempting to build sample application in Windows

I get the following 4 errors and 3 warnings on attempting to build the sample application in Visual Studio 2022 (Windows):

Severity	Code	Description	Project	File	Line	Suppression State	Detail Description
Error	CS1061	'Label' does not contain a definition for 'TextCenterHorizontal' and no accessible extension method 'TextCenterHorizontal' accepting a first argument of type 'Label' could be found (are you missing a using directive or an assembly reference?)	CommunityToolkit.Maui.Markup.Sample (net6.0-android), CommunityToolkit.Maui.Markup.Sample (net6.0-ios), CommunityToolkit.Maui.Markup.Sample (net6.0-maccatalyst), CommunityToolkit.Maui.Markup.Sample (net6.0-windows10.0.19041.0)	D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\samples\CommunityToolkit.Maui.Markup.Sample\Pages\SettingsPage.cs	23	Active
Error	CS1061	'Label' does not contain a definition for 'TextCenter' and no accessible extension method 'TextCenter' accepting a first argument of type 'Label' could be found (are you missing a using directive or an assembly reference?)	CommunityToolkit.Maui.Markup.Sample (net6.0-android), CommunityToolkit.Maui.Markup.Sample (net6.0-ios), CommunityToolkit.Maui.Markup.Sample (net6.0-maccatalyst), CommunityToolkit.Maui.Markup.Sample (net6.0-windows10.0.19041.0)	D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\samples\CommunityToolkit.Maui.Markup.Sample\Pages\NewsDetailPage.cs	32	Active
Error	CS1061	'Entry' does not contain a definition for 'TextCenter' and no accessible extension method 'TextCenter' accepting a first argument of type 'Entry' could be found (are you missing a using directive or an assembly reference?)	CommunityToolkit.Maui.Markup.Sample (net6.0-android), CommunityToolkit.Maui.Markup.Sample (net6.0-ios), CommunityToolkit.Maui.Markup.Sample (net6.0-maccatalyst), CommunityToolkit.Maui.Markup.Sample (net6.0-windows10.0.19041.0)	D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\samples\CommunityToolkit.Maui.Markup.Sample\Pages\SettingsPage.cs	40	Active
Error	CS1061	'Label' does not contain a definition for 'TextCenter' and no accessible extension method 'TextCenter' accepting a first argument of type 'Label' could be found (are you missing a using directive or an assembly reference?)	CommunityToolkit.Maui.Markup.Sample (net6.0-android), CommunityToolkit.Maui.Markup.Sample (net6.0-ios), CommunityToolkit.Maui.Markup.Sample (net6.0-maccatalyst), CommunityToolkit.Maui.Markup.Sample (net6.0-windows10.0.19041.0)	D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\samples\CommunityToolkit.Maui.Markup.Sample\Pages\SettingsPage.cs	52	Active
Warning	CS8034	Unable to load Analyzer assembly D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll: Could not find file 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'.	CommunityToolkit.Maui.Markup		1	Active	System.IO.FileNotFoundException: Could not find file 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'.
File name: 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.StandardFileSystem.OpenFile(String filePath, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.CommonCompilerFileSystemExtensions.OpenFileWithNormalizedException(ICommonCompilerFileSystem fileSystem, String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
   at Microsoft.CodeAnalysis.ModuleMetadata.CreateFromFile(String path)
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetAnalyzerTypeNameMap(String fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.GetExtensionTypeNameMap()
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.AddExtensions(Builder builder, String language, Func`2 shouldInclude)
-----

Warning	CS8034	Unable to load Analyzer assembly D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll: Could not find file 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'.	CommunityToolkit.Maui.Markup		1	Active	System.IO.FileNotFoundException: Could not find file 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'.
File name: 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup.SourceGenerators\bin\Debug\netstandard2.0\CommunityToolkit.Maui.Markup.SourceGenerators.dll'
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.StandardFileSystem.OpenFile(String filePath, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.CommonCompilerFileSystemExtensions.OpenFileWithNormalizedException(ICommonCompilerFileSystem fileSystem, String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
   at Microsoft.CodeAnalysis.ModuleMetadata.CreateFromFile(String path)
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetAnalyzerTypeNameMap(String fullPath, Type attributeType, AttributeLanguagesFunc languagesFunc)
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.GetExtensionTypeNameMap()
   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.AddExtensions(Builder builder, String language, Func`2 shouldInclude)
-----

Warning	WMC1006	Cannot resolve Assembly or Windows Metadata file 'D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\src\CommunityToolkit.Maui.Markup\bin\Debug\net6.0\CommunityToolkit.Maui.Markup.dll'	CommunityToolkit.Maui.Markup.Sample	D:\Simon\OneDrive\Documents\Visual Studio Projects\Examples\CommunityToolkit.Maui.Markup\samples\CommunityToolkit.Maui.Markup.Sample\CommunityToolkit.Maui.Markup.Sample.csproj	1	

`TextAlignmentExtensionsGenerator` shouldn't have `ISymbol`s in the pipeline

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

var userGeneratedClassesProvider = context.SyntaxProvider.CreateSyntaxProvider(
static (syntaxNode, cancellationToken) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
static (context, cancellationToken) =>
{
var compilation = context.SemanticModel.Compilation;
var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(iTextAlignmentInterface);
if (iTextAlignmentInterfaceSymbol is null)
{
throw new Exception("There's no .NET MAUI referenced in the project.");
}
var classSymbol = (INamedTypeSymbol?)context.SemanticModel.GetDeclaredSymbol(context.Node);
// If the ClassDlecarationSyntax doesn't implements those interfaces we just return null
if (classSymbol?.AllInterfaces.Contains(iTextAlignmentInterfaceSymbol, SymbolEqualityComparer.Default) is not true)
{
return null;
}
return classSymbol;
});
// Get Microsoft.Maui.Controls Symbols that implements the desired interfaces
var mauiControlsAssemblySymbolProvider = context.CompilationProvider.Select(
static (compilation, token) =>
{
var iTextAlignmentInterfaceSymbol = compilation.GetTypeByMetadataName(iTextAlignmentInterface);
if (iTextAlignmentInterfaceSymbol is null)
{
throw new Exception("There's no .NET MAUI referenced in the project.");
}
var mauiAssembly = compilation.SourceModule.ReferencedAssemblySymbols.Single(q => q.Name == mauiControlsAssembly);
var symbols = GetMauiInterfaceImplementors(mauiAssembly, iTextAlignmentInterfaceSymbol).Where(static x => x is not null);
return symbols;
});

Expected Behavior

Better pipeline

[Proposal] Extension bindings for AppTheme and AppThemeColor

Extension bindings for AppTheme and AppThemeColor

Link to Discussion

AppThemeBinding in Markup #627

Summary

Create binding extensions for SetAppTheme and SetAppThemeColor

Motivation

Detailed Design

Usage Syntax

new Button().AppThemeBinding(Button.TextColorProperty, Colors.Green, Colors.Orange)

C# Usage

public static TBindable AppThemeBinding<TBindable, T>(this TBindable bindable, BindableProperty bindableProperty, T light, T dark) where TBindable : BindableObject
{
    bindable.SetAppTheme(bindableProperty, light, dark);

    return bindable;
}
public static TBindable AppThemeColorBinding<TBindable>(this TBindable bindable, BindableProperty bindableProperty, Color light, Color dark) where TBindable : BindableObject
{
    bindable.SetAppThemeColor(bindableProperty, light, dark);

    return bindable;
}

Drawbacks

Alternatives

Unresolved Questions

Support for AppThemeBindings

Discussed in #75

Originally posted by HobDev June 23, 2022
The docs have the SetAppThemeColor() and SetAppTheme(). This way I have to declare the colors on each UI element. There should be some better way to share style among the UI elements in C#. C# markup must support to declare the style once and use it throughout the app.

 static Color darkThemeTextColor = Color.FromArgb("#FFFFFF");
        static Color lightThemeTextColor = Color.FromArgb("#000000");

        static Style<Label> cSharpLabelStyle;
        public static Style<Label> CSharpLabelStyle => cSharpLabelStyle ?? (cSharpLabelStyle = new Style<Label>(
        (Label.TextColorProperty, new AppThemeBindingExtension { Dark = darkThemeTextColor, Light = lightThemeTextColor }),
        (Label.FontFamilyProperty, "OpenSansRegular"),
       (Label.FontSizeProperty, 14)
       ));

Finally use the style in UI :

new Label{Text = "Hello World" , HorizontalOptions=LayoutOptions.Center, VerticalOptions=LayoutOptions.Center}.Style(AppStyles.CSharpLabelStyle),

In the attached sample the color of the XAML Label changes with the change of mode. But the C# Label does not change color until the app restarts. Unable to check the change of device theme due to this bug.

MauiApp1.zip

[Proposal] Add methods for all VisualElement Properties

Add methods for all VisualElement Properties

  • Proposed
  • Prototype
  • Implementation: PR raised
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests: at least one for each new method following the conventions for other methods in the class
  • Sample: Some uses of new methods added to the Markup sample app
  • Documentation: PR raised in docs repo

Link to Discussion

#140

Summary

As part of ensuring that Maui.Markup can provide a fluent interface for all standard controls, it will be a good starting point for having fluent methods for all properties of VisualElement as this is the base class to a lot of common controls.

Motivation

Fill in some common gaps in the fluent methods for defining a control instance.
Avoid needing to use the clunky method of combining an object initializer and chained fluent methods.

Detailed Design

Fill in the gaps of extension methods for setting the remaining properties of VisualElement that do not have them.

The properties are:

  • AnchorX
  • AnchorY
  • Background
  • BackgroundColor
  • Clip
  • FlowDirection
  • Opacity
  • InputTransparent
  • IsEnabled
  • IsVisible
  • RotationX
  • RotationY
  • Rotation
  • ScaleX
  • ScaleY
  • TranslationX
  • TranslationY
  • ZIndex

These should all be simple extension methods that wrap setting the property and then returning the element.

The implementations will be of the style:

public static TVisualElement AnchorX<TVisualElement >(this TVisualElement element, double anchorX) where TVisualElement : VisualElement
{
	element.AnchorX = anchorX;
	return element;
}

In addition to simple methods for the above properties, I also think it would be useful to add the following helper methods:

A method Anchor() that takes two values that can be applied to AnchorX and AnchorY with a single call.
A method Scale() that takes two values that can be applied to ScaleX and ScaleY with a single call.

Usage Syntax

With this change, it will be possible to write

new Label().Text("hello").Opacity(0.5).BackgroundColor(Colors.Red);

rather than having to write

new Label{ Opacity = 0.5, BackgroundColor = Colors.Red }.Text("hello")

Drawbacks

It makes the assembly slightly larger by adding methods where some of these might be rarely used.
It feels an appropriate trade-off for completeness though.

Alternatives

No real alternatives other than not doing this.
It just makes it easier to provide a more consistent way of writing code when defining a UI in C#.

Unresolved Questions

n/a

[Bug] TextAlignmentExtensions does not work with inherited controls of Label, Picker, etc.

Description

TextAlignmentExtensions does not work with inherited controls of Label, Picker, etc. Previously this worked because the type parameter of the extension method was ITextAlignment, instead of a concrete class.

Steps to Reproduce

  1. In a new class inherit Label
  2. Try to use .TextCenter()
  3. Explicit casting required error

Expected Behavior

Actual Behavior

Basic Information

  • Version with issue: 1.1.1
  • Last known good version:1.0.1
  • IDE:
  • Platform Target Frameworks:
    • iOS:
    • Android:
    • UWP:
  • Android Support Library Version:
  • Nuget Packages:
  • Affected Devices:

Workaround

Reproduction imagery

[Bug] .Assign Extension does not work with inherited controls of Label and Entry

.Assign Extension does not work with inherited controls of Label and Entry.

Stack Trace

I get compiler error:

The type 'Microsoft.Maui.Controls.Label' cannot be used as type parameter 'TBindable' in the generic type or method 'BindableObjectExtensions.Assign<TBindable, TVariable>(TBindable, out TVariable)'. There is no implicit reference conversion from 'Microsoft.Maui.Controls.Label' to 'MyProject.MyLabel'.

Basic Information

  • Version with issue: Latest
  • Last known good version: 1.0.1
  • IDE: VS for mac
  • Platform Target Frameworks:
    • iOS:
    • Android:
  • Android Support Library Version:
  • Nuget Packages:
  • Affected Devices:

[Proposal] Behaviors extension to VisualElementExtensions

Behaviors extension to VisualElementExtensions

  • Proposed
  • Prototype
  • Implementation
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests
  • Sample
  • Documentation

Link to Discussion

#28

Summary

I would like to propose that we add the WithBehavior(this VisualElement visualElement, Behavior behavior) and WithBehaviors(this VisualElement visualElement, params Behavior[] behaviors) extensions that will add the supplied behavior to visualElement.Behaviors.

Motivation

Reduces the amount of code developers need to write 😄

Detailed Design

public static class VisualElementExtensions
{
    public static WithBehavior(this VisualElement visualElement, Behavior behavior)
    {
        visualElement.Behaviors.Add(behavior);
        return visualElement;
    }

    public static WithBehaviors(this VisualElement visualElement, params Behavior[] behaviors)
    {
        foreach (var behavior in behaviors)
        {
            WithBehavior(behavior);
        }
        return visualElement;
    }
}

Usage Syntax

Content = new Entry
{
    Placeholder = "Start typing until MaxLength is reached...",
    MaxLength = 100
}.WithBehavior(new MaxLengthReachedBehavior().Bind(
        MaxLengthReachedBehavior.CommandProperty,
        nameof(ViewModel.MaxLengthReachedCommand));

Drawbacks

n/a

Alternatives

n/a

Unresolved Questions

n/a

[BUG] TextAlignment Extension Throwing Compiler Error

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

if I extend the base Label class and implement the ILabel interface,

public class MyLabel : Label, ILabel
{

}

if I try to use the extended MyLabel class, text alignment extension will throw this exception

public MainPage()
{
	Content = new MyLabel
	{

	}.Center().TextCenter();
}
[CS0121](classifiedtextelementview-invokeaction:0): The call is ambiguous between the following methods or properties:
 'TextAlignmentExtensions_MyLabel.TextCenter<TAssignable>(TAssignable)' and 
'TextAlignmentExtensions_Label.TextCenter<TAssignable>(TAssignable)'

Expected Behavior

No compiler Error

Steps To Reproduce

See above

Link to public reproduction project repository

https://github.com/danielftz/BugExample.MCTMarkupTextAlignment

Environment

- .NET MAUI C# Markup CommunityToolkit:
- OS: MacOS
- .NET MAUI: Latest CUrrent

Anything else?

No response

[Proposal] FlexLayout Basis<T> extension method overload + Doc and samples

FlexLayout Basis extension method overload

Link to Discussion

#34

Summary

This proposal adds a method overload to the Basis<T> extension method, so that FlexBasis can be set without the need for instantiating a FlexBasis struct. Also samples and docs for the existing FlexLayout methods will be added.

Motivation

Allows for more options when declaring FlexBasis.

Detailed Design

public static TBindable Basis<TBindable>(this TBindable bindable, float length, bool isRelative = false) where TBindable : BindableObject
{
      return bindable.Basis(new FlexBasis(length, isRelative));
}

Usage Syntax

Content = new Label().Basis(25, true);

Drawbacks

None

Alternatives

None

Unresolved Questions

None

Add Dark mode support in the sample app

There are docs for dark mode support with XAML in maui. But for someone using C# and C# markup to write the UI the sample should have support for dark mode.

[Proposal] Add `.Text()` and `.TextColor()` Extension Methods

Add .Text() + .TextColor() Extension Methods

  • Proposed
  • Prototype
  • Implementation
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests
  • Sample
  • Documentation

Link to Discussion

#37

Summary

This Proposal adds extension methods for IText.Text and ITextStyle.TextColor.

Motivation

Currently, the only way to set Text and TextColor properties on an element that implements IText is to set the properties directly. Here is an example from CommunityToolkit.Maui.Markup.Sample.Pages.SettingsPage:

new Label { Text = "Top Stories To Fetch", TextColor = ColorConstants.PrimaryTextColor }
	.LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional)
	.LayoutBounds(0, 0, 1, 40)
	.TextCenterHorizontal()
	.TextBottom(),

This Proposal will allow devs to use fluent extension methods to set both Text and TextColor.

Detailed Design

	/// <summary>
	/// Sets <see cref="ITextStyle.TextColor"/> Property
	/// </summary>
	/// <typeparam name="TBindable"><see cref="BindableObject"/></typeparam>
	/// <param name="bindable">Element</param>
	/// <param name="textColor">Text <see cref="Color"/></param>
	/// <returns></returns>
	public static TBindable TextColor<TBindable>(this TBindable bindable, Color? textColor) where TBindable : BindableObject, ITextStyle
	{
		bindable.SetValue(TextElement.TextColorProperty, textColor);
		return bindable;
	}

	/// <summary>
	/// Sets <see cref="IText.Text"/> Property
	/// </summary>
	/// <typeparam name="TBindable"><see cref="BindableObject"/></typeparam>
	/// <param name="bindable">Element</param>
	/// <param name="text"></param>
	/// <returns></returns>
	public static TBindable Text<TBindable>(this TBindable bindable, string? text) where TBindable : BindableObject, IText
	{
		bindable.SetValue(ITextElement.TextProperty, text);
		return bindable;
	}

	/// <summary>
	/// Sets <see cref="IText.Text"/> Property
	/// </summary>
	/// <typeparam name="TBindable"><see cref="BindableObject"/></typeparam>
	/// <param name="bindable">Element</param>
	/// <param name="text"></param>
	/// <param name="textColor">Text <see cref="Color"/></param>
	public static TBindable Text<TBindable>(this TBindable bindable, string? text, Color? textColor) where TBindable : BindableObject, IText
	{
		return bindable.Text(text).TextColor(textColor);
	}

Usage Syntax

C# Usage

Content = new VerticalStackLayout
{
  Children = 
  {
    new Label()
      .Text("Hello World", Colors.Blue),

    new Label()
      .TextColor(Colors.Green)
      .Bind(Label.TextProperty, nameof(ViewModel.LabelText);
  }
}

Drawbacks

Currently, TextElement does not have a TextProperty static property, i.e. TextElelement.TextProperty does not yet exist.

We can workaround this by doing the following. This is a proven and common workaround in Xamarin.CommunityToolkit.Markup.

using ITextElement = Microsoft.Maui.Controls.Label; // ToDo Remove this once TextElement.TextProperty is added

public static class ElementExtensions
{
	public static TBindable Text<TBindable>(this TBindable bindable, string? text) where TBindable : BindableObject, IText
	{
		bindable.SetValue(ITextElement.TextProperty, text);
		return bindable;
	}
}

Alternatives

Currently, the only alternative is to initialize the properties like so:

new Label { Text = "Top Stories To Fetch", TextColor = ColorConstants.PrimaryTextColor }

Unresolved Questions

Should we support null inputs? The default value of Color and Text is null and I imagine the number of use cases where developer would use this .Text() to revert the values of Color and Text back to null may be minimal.

[Bug] Unable to make use of BindCommand method on MenuFlyoutItem object (Desktop menu)

Description

Getting this exception message when the application is launched with the BindCommand method that is added to the MenuFlyoutItem object

No command + command parameter properties are registered for BindableObject type Microsoft.Maui.Controls.MenuFlyoutItem
Register command + command parameter properties for this BindableObject type

After this exception, have tried to register the command using the DefaultBindableProperties.RegisterForCommand method.

But it resulted in a different exception with the below message (as MenuFlyoutItem inherits from MenuItem):

An item with the same key has already been added. Key: Microsoft.Maui.Controls.MenuItem

Stack Trace

Link to Reproduction Sample

https://github.com/egvijayanand/dotnet-maui-samples

Solution name: MenuApp (Made available under the src folder)

The page that is causing this exception is available in the MenuPageCS.cs

Steps to Reproduce

  1. Clone the repository
  2. Navigate to the src folder
  3. Open the MenuApp solution
  4. Set the instance of MenuPageCS as the Startup page in App.xaml.cs

Expected Behavior

The command is expected to bound to the MenuFlyoutItem object and invoke the associated action.

Actual Behavior

Ends in an exception.

Basic Information

  • Version with issue: 1.0.0
  • Last known good version: NA
  • IDE: VS2022 17.3.0 Preview 3.0
  • Platform Target Frameworks:
    • iOS: NA
    • Android: NA
    • UWP: net6.0-windows10.0.19041.0 - running on Windows 11
    • MacCatalyst - Haven't tried on it due to infra constraint.
  • Android Support Library Version:
  • NuGet Packages:
  • Affected Devices:

Workaround

The event handler with the Invoke API works for now. Only Command is having the issue.

Reproduction imagery

[Proposal] Add `IImage` Extension Methods

Add IImage Extension Methods

Link to Discussion

#42

Summary

This Proposal adds the following fluent extension methods for IImage:

public static TBindable Source<TBindable>(this TBindable bindable, ImageSource imageSource) where TBindable : BindableObject, IImageSourcePart;

public static TBindable Aspect<TBindable>(this TBindable bindable, Aspect aspect) where TBindable : BindableObject, IImage;

public static TBindable IsOpaque<TBindable>(this TBindable bindable, bool isOpaque) where TBindable : BindableObject, IImage;

Motivation

Currently, the only way to assign values on an Image is to set the property directly, like so:

var image = new Image { Source = "dotnetbot", IsOpaque = true, Aspect = Aspect.AspectFill };

This Proposal allows us to instead assign these values using the fluent C# Markup syntax.

Detailed Design

public static TBindable Source<TBindable>(this TBindable bindable, ImageSource imageSource) where TBindable : BindableObject, IImageSourcePart
{
	bindable.SetValue(ImageElement.SourceProperty, imageSource);
	return bindable;
}

public static TBindable Aspect<TBindable>(this TBindable bindable, Aspect aspect) where TBindable : BindableObject, IImage
{
	bindable.SetValue(ImageElement.AspectProperty, aspect);
	return bindable;
}

public static TBindable IsOpaque<TBindable>(this TBindable bindable, bool isOpaque) where TBindable : BindableObject, IImage
{
	bindable.SetValue(ImageElement.IsOpaqueProperty, isOpaque);
	return bindable;
}

Usage Syntax

C# Usage

Content = new Image().Source("dotnetbot").Aspect(Aspect.AspectFill).IsOpaque(true);

Drawbacks

No known drawbacks

Alternatives

The current alternative is to assign the properties upon initialization:

var image = new Image { Source = "dotnetbot", IsOpaque = true, Aspect = Aspect.AspectFill };

Unresolved Questions

No unresolved questions

[Proposal] Further enhance `Bind` typed binding support to provide `IValueConverter` support

Feature name

Further enhance Bind typed binding support to provide IValueConverter support

Link to discussion

I haven't created one

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation: MicrosoftDocs/CommunityToolkit#226

Summary

Currently the Bind extension method provides an override to pass in an IValueConverter implementation and this works great with all of the toolkits converters. The new typed bindings implementation does not provide this support. I propose that we fix that.

Motivation

Faster bindings!

Detailed Design

/// <summary>Bind to a specified property with inline conversion and conversion parameter</summary>
public static TBindable Bind<TBindable, TBindingContext, TSource, TParam, TDest>(
    this TBindable bindable,
    BindableProperty targetProperty,
    Func<TBindingContext, TSource> getter,
    (Func<TBindingContext, object?>, string)[] handlers,
    Action<TBindingContext, TSource>? setter = null,
    BindingMode mode = BindingMode.Default,
    IValueConverter converter = null,
    TParam? converterParameter = default,
    string? stringFormat = null,
    TBindingContext? source = default,
    TDest? targetNullValue = default,
    TDest? fallbackValue = default) where TBindable : BindableObject
{
    bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(bindingContext => (getter(bindingContext), true), setter, handlers.Select(x => x.ToTuple()).ToArray())
    {
        Mode = mode,
        Converter = converter,
        ConverterParameter = converterParameter,
        StringFormat = stringFormat,
        Source = source,
        TargetNullValue = targetNullValue,
        FallbackValue = fallbackValue
    });

    return bindable;
}

Usage Syntax

new Label()
    .Bind(
    Label.TextProperty,
    static (ViewModel vm => vm.MyFavoriteColor,
    converter: new ColorToByteAlphaConverter())

Drawbacks

No response

Alternatives

No response

Unresolved Questions

No response

[BUG] .Bind to nested/complex model property not working

Is there an existing issue for this?

  • I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

Binding to an nested model property (property of an object inside the model) does not work. The internal TypedBinding has always just one handler, which does not cover complex bindings:

public static TBindable Bind<TBindable, TBindingContext, TSource, TParam, TDest>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Expression<Func<TBindingContext, TSource>> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode mode = BindingMode.Default,
		IValueConverter? converter = null,
		TParam? converterParameter = default,
		string? stringFormat = null,
		TBindingContext? source = default,
		TDest? targetNullValue = default,
		TDest? fallbackValue = default) where TBindable : BindableObject
	{

		return Bind(
				bindable,
				targetProperty,
				getterFunc,
                                // only one handler added here
				new (Func<TBindingContext, object?>, string)[] { ((TBindingContext b) => b, GetMemberName(getter)) },
				setter,
				mode,
				converter,
				converterParameter,
				stringFormat,
				source,
				targetNullValue,
				fallbackValue);
	}

As you can find in the original Microsoft.Maui Unit Test public void ValueSetOnTwoWayWithComplexPathBinding(bool setContextFirst, bool isDefault) they created multiple handlers for each subproperty:

	var binding = new TypedBinding<ComplexMockViewModel, string>(
		cmvm => (cmvm.Model.Model.Text, true),
		(cmvm, s) => cmvm.Model.Model.Text = s, new[] {
			new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
			new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
			new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
		})
	{ Mode = bindingMode };

Therefore i've added some functionality which takes the original Expression and create the handlers for the TypedBinding.

I'm not familiar with posting possible solutions, or creating Expression magic. Maybe this code snippet will still help, or give a better idea.

public static TBindable Bind<TBindable, TBindingContext, TSource, TParam, TDest>(
	this TBindable bindable,
	BindableProperty targetProperty,
	Expression<Func<TBindingContext, TSource>> getter,
	Action<TBindingContext, TSource>? setter = null,
	BindingMode mode = BindingMode.Default,
	Func<TSource?, TParam?, TDest>? convert = null,
	Func<TDest?, TParam?, TSource>? convertBack = null,
	TParam? converterParameter = default,
	string? stringFormat = null,
	TBindingContext? source = default,
	TDest? targetNullValue = default,
	TDest? fallbackValue = default) where TBindable : BindableObject
{
	var getterFunc = ConvertExpressionToFunc(getter);

        // Create a default setter, if no explicit one is set
	if(setter == null)
	{
		setter = ExpressionHelpers.CreateDefaultSetterFromGetter(getter);
	}

	return Bind(
			bindable,
			targetProperty,
			getterFunc,
			ExpressionHelpers.GetPropertyAccessExpressions(getter),
			//new (Func<TBindingContext, object?>, string)[] { ((TBindingContext b) => b, GetMemberName(getter)) },
			setter,
			mode,
			convert,
			convertBack,
			converterParameter,
			stringFormat,
			source,
			targetNullValue,
			fallbackValue);
}


public static class ExpressionHelpers
{
	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TModel"></typeparam>
	/// <typeparam name="TProperty"></typeparam>
	/// <param name="expression"></param>
	/// <returns></returns>
	public static (Func<TModel, object?>, string)[] GetPropertyAccessExpressions<TModel, TProperty>(
		Expression<Func<TModel, TProperty>> expression)
	{
		var expressionParamater = expression.Parameters.FirstOrDefault();
		if (expressionParamater == null)
		{
			throw new Exception();
		}

		var propertyExpressions = new List<(Func<TModel, object?>, string)>();
		ExtractPropertyExpressions(expression.Body, expressionParamater, propertyExpressions);
		propertyExpressions.Reverse();
		return propertyExpressions.ToArray();
	}

	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TModel"></typeparam>
	/// <param name="expression"></param>
	/// <param name="parameterExpression"></param>
	/// <param name="propertyExpressions"></param>
	static void ExtractPropertyExpressions<TModel>(
		Expression expression,
		ParameterExpression parameterExpression,
		List<(Func<TModel, object?>, string)> propertyExpressions)
	{
		while (expression is MemberExpression memberExpression)
		{
			var memberName = memberExpression.Member.Name;

			if (memberExpression.Expression == null)
			{
				throw new Exception();
			}

			var lambdaExpression = Expression.Lambda<Func<TModel, object?>>(memberExpression.Expression, parameterExpression);

			propertyExpressions.Add((lambdaExpression.Compile(), memberName));

			expression = lambdaExpression.Body;
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="TModel"></typeparam>
	/// <typeparam name="TProperty"></typeparam>
	/// <param name="expression"></param>
	/// <returns></returns>
	public static Action<TModel, TProperty> CreateDefaultSetterFromGetter<TModel, TProperty>(
		Expression<Func<TModel, TProperty>> expression)
	{
		var modelParam = expression.Parameters[0];

		var valueParam = Expression.Parameter(typeof(TProperty), "val");

		var assignment = Expression.Assign(expression.Body, valueParam);

		var setterLambda = Expression.Lambda<Action<TModel, TProperty>>(
			assignment,
			modelParam,
			valueParam
		);

		return setterLambda.Compile();
	}
}

Expected Behavior

Bindings like

.Bind(Label.TextProperty, getter: static (TestModel myModel) => myModel.NestedObject.Test)

should work

Steps To Reproduce

  1. Create a new Maui project with CommunityToolkit.Markup and CommunityToolkit.Mvvm packages installed.
  2. Create a view with model:
public class TestView : ContentPage
{
	TestModel model;

	public TestView()
	{
		model = new TestModel();
		BindingContext = model;

		Content = new Grid
		{
			Children =
			{
				new VerticalStackLayout
				{
					new Label
					{

					}.Bind(Label.TextProperty, path: "NestedObject.Test"),

					new Label
					{

					}.Bind(Label.TextProperty, getter: static (TestModel myModel) => myModel.NestedObject.Test),

					new Entry
					{

					}.Bind(Entry.TextProperty, static (TestModel model) => model.NestedObject.Test, (TestModel model, string value) => model.NestedObject.Test = value),

				}
			}
		};
	}
}

public partial class TestModel : ObservableObject
{
	[ObservableProperty]
	NestedObject nestedObject = new();

	[ObservableProperty]
	NestedObject? nestedObject2;

	[ObservableProperty]
	string title = "My Title";
}


public partial class NestedObject : ObservableObject
{
	[ObservableProperty]
	string? test = "Initial";
}
  1. Start, and change the value of the entry. Only the first (default Microsoft) Binding should work.

Link to public reproduction project repository

https://github.com/Falco94/CommunityToolkit.IssueTest

Environment

- .NET MAUI C# Markup CommunityToolkit: 3.2
- OS: Windows 10 Build 10.0.19044
- .NET MAUI: 7

Anything else?

No response

[Proposal] Typed Bindings

Feature name

Typed Bindings

Link to discussion

#154 (reply in thread)

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation: MicrosoftDocs/CommunityToolkit#204

Summary

Typed Bindings are used by XAML Compiled Bindings to improve performance and ensure Type safety.

This Proposal extends the .Bind() extension method by providing the option of using TypedBinding which is the binding engine used by XAML Compiled Bindings to improve performance and ensure Type safety.

// One-way (aka read-only) Binding
new Label().Row(Row.Description).Bind(Label.TextProperty, (StoryModel m) => m.Description)

// Two-way Binding
new Entry().Bind(Entry.TextProperty, (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, (SettingsViewModel vm, int text) => vm.NumberOfTopStoriesToFetch = text)

Motivation

The current implementation of .Bind() uses the Binding class which requires reflection when a change to the binding is applied.

This updated implementation brings the option of using TypedBinding with the .Bind() extension method which does not require reflection providing a substantial performance improvement for bindings.

Bindings TypedBinding
Uses Reflection Yes No
Type Safe No Yes

Detailed Design

A POC of this can be found on the Compiled-Bindings branch:

using Microsoft.Maui.Controls.Internals;

namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// TypedBinding Extension Methods for Bindable Objects
/// </summary>
public static class TypedBindingExtensions
{
	/// <summary>Bind to a specified property</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		string? stringFormat = null,
		TBindingContext? source = default) where TBindable : BindableObject
	{
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			StringFormat = stringFormat,
			Source = source,
		});

		return bindable;
	}

	/// <summary>Bind to a specified property with inline conversion</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource, TDest>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		Func<TSource?, TDest>? convert = null,
		Func<TDest?, TSource>? convertBack = null,
		string? stringFormat = null,
		TBindingContext? source = default,
		TDest? targetNullValue = default,
		TDest? fallbackValue = default) where TBindable : BindableObject
	{
		var converter = new FuncConverter<TSource, TDest, object>(convert, convertBack);
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			Converter = converter,
			StringFormat = stringFormat,
			Source = source,
			TargetNullValue = targetNullValue,
			FallbackValue = fallbackValue
		});

		return bindable;
	}

	/// <summary>Bind to a specified property with inline conversion and conversion parameter</summary>
	public static TBindable Bind<TBindable, TBindingContext, TSource, TParam, TDest>(
		this TBindable bindable,
		BindableProperty targetProperty,
		Func<TBindingContext, TSource> getter,
		Action<TBindingContext, TSource>? setter = null,
		BindingMode? mode = null,
		Func<TSource?, TParam?, TDest>? convert = null,
		Func<TDest?, TParam?, TSource>? convertBack = null,
		TParam? converterParameter = default,
		string? stringFormat = null,
		TBindingContext? source = default,
		TDest? targetNullValue = default,
		TDest? fallbackValue = default) where TBindable : BindableObject
	{
		var converter = new FuncConverter<TSource, TDest, TParam>(convert, convertBack);
		bindable.SetBinding(targetProperty, new TypedBinding<TBindingContext, TSource>(result => (getter(result), true), setter, null)
		{
			Mode = (setter, mode) switch
			{
				(_, not null) => mode.Value, // Always use the provided mode when given
				(null, null) => BindingMode.OneWay, // When setter is null, binding is read-only; use BindingMode.OneWay to improve performance
				_ => BindingMode.Default // Default to BindingMode.Default
			},
			Converter = converter,
			ConverterParameter = converterParameter,
			StringFormat = stringFormat,
			Source = source,
			TargetNullValue = targetNullValue,
			FallbackValue = fallbackValue
		});

		return bindable;
	}
}

Usage Syntax

// One-way (aka read-only) Binding
new Label().Row(Row.Description).Bind(Label.TextProperty, (StoryModel m) => m.Description)

// Two-way Binding
new Entry().Bind(Entry.TextProperty, (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, (SettingsViewModel vm, int text) => vm.NumberOfTopStoriesToFetch = text)

Drawbacks

This is an overload to the existing .Bind() method, increasing the number of overloaded methods for .Bind() to 16.

This implementation also ignores TypedBinding's string[] handler constructor parameter. This parameter isn't documented and I'm unsure how it is being used and what use-cases it covers. However, I'm confident we can add support for this parameter in a future update without breaking changes.

Alternatives

TypedBinding can be used currently without C# Markup Extensions

// Two-way Binding
var entry = new Entry();
entry.SetBinding(Entry.TextProperty, new TypedBinding<SettingsViewModel, int>(vm => (vm.NumberOfTopStoriesToFetch, true), (vm, number) => vm.NumberOfTopStoriesToFetch = number, null));

Content = entry;

Unresolved Questions

Should we use a different name for this extension method, like .TypedBind()?

[Proposal] Add support for fluently applying SemanticProperties

Add support for fluently applying SemanticProperties

  • Proposed
  • Prototype
  • Implementation
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests
  • Sample
  • Documentation

Link to Discussion

Summary

To make it easier for us all to build accessible applications when using the markup toolkit.

Motivation

To make it easier for us all to build accessible applications when using the markup toolkit.

Detailed Design

Propose that we add 2 sets of extension method classes:

SemanticExtensions

public static class SemanticExtensions
{
    public BindableObject SemanticDescription(this BindableObject bindableObject, string description)
    {
        SemanticProperties.SetDescription(bindableObject, description);
        return bindableObject;
    }

    public BindableObject SemanticHeadingLevel(this BindableObject bindableObject, SemanticHeadingLevel headingLevel)
    {
        SemanticProperties.SetHeadingLevel(bindableObject, headingLevel);
        return bindableObject;
    }

    public BindableObject SemanticHint(this BindableObject bindableObject, string hint)
    {
        SemanticProperties.SetHint(bindableObject, hint);
        return bindableObject;
    }
}

AutomationExtensions

I propose that we do not add all AutomationProperties as some have been superseded by the SemanticProperties above as detailed at: https://docs.microsoft.com/dotnet/maui/fundamentals/accessibility#automation-properties

AutomationProperties.Name -> SemanticProperties.Description

AutomationProperties.HelpText -> SemanticProperties.Hint

AutomationProperties.LabeledBy -> SemanticProperties.Description with a controls value
e.g.

Content = 
    new VerticalStackLayout
    {
        Children = 
        {
            new Label().Text("Enter name:").Assign(out var label),

            new Entry().SemanticDescription(label.Text)
        }
    };
public static class AutomationExtensions
{
    public BindableObject AutomationExcludedWithChildren(this BindableObject bindableObject, bool? isExcludedWithChildren)
    {
        AutomationProperties.SetExcludedWithChildren(bindableObject, isExcludedWithChildren);
        return bindableObject;
    }

    public BindableObject AutomationIsInAccessibleTree(this BindableObject bindableObject, bool? isInAccessibleTree)
    {
        AutomationProperties.SetIsInAccessibleTree(bindableObject, isInAccessibleTree);
        return bindableObject;
    }
}

Usage Syntax

C# Usage

Content = new Image().SemanticHint("Like this post.");

Drawbacks

Alternatives

Unresolved Questions

Do we need to include the other AutomationProperties that have been superseded by SemanticProperties?

[BUG] Build fails when including class library project including CommunityToolkit.Maui.Markup.

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Build fails when referencing a class library project that includes the CommunityToolkit.Maui.Markup NuGet.

Expected Behavior

Successful build with referencing a class library project that includes the CommunityToolkit.Maui.Markup NuGet.

Steps To Reproduce

  1. Open and run the project solution from the repository.
  2. When building the project referencing the class library project (where the Markup nuget is installed) observe the output log:

Screenshot 2022-11-30 at 16 31 11

Link to public reproduction project repository

https://bitbucket.org/maximfrisk/markup/src/master/

Environment

- .NET MAUI C# Markup CommunityToolkit: 2.0.0
- OS: MacOS Ventura 13.0.1 (22A400)
- .NET MAUI: 7.0.49
- Visual Studio 17.5 Preview (17.5 build 437)

Anything else?

No response

[Proposal] Add ItemsView Extensions

Add ItemsView Extensions

Link to Discussion

#50

Summary

This PR adds fluent extension methods for ItemsView.

These extensions will work for both CarouselView and CollectionView, both of which inherit from ItemsView.

Motivation

To improve the developer workflow around initializing CollectionView and CarouselView

Detailed Design

using System.Collections;
using System.Windows.Input;
using Microsoft.Maui;
using Microsoft.Maui.Controls;

namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// Fluent extension methods for <see cref="ItemsView"/>
/// </summary>
public static class ItemsViewExtensions
{
	/// <summary>
	/// Assigns the <see cref="ItemsView.EmptyView"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="view"></param>
	/// <returns>ItemsView with Empty View</returns>
	public static TItemsView EmptyView<TItemsView>(this TItemsView itemsView, object view) where TItemsView : ItemsView
	{
		itemsView.EmptyView = view;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.EmptyViewTemplate"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="view"></param>
	/// <returns>ItemsView with Empty View Template</returns>
	public static TItemsView EmptyViewTemplate<TItemsView>(this TItemsView itemsView, DataTemplate view) where TItemsView : ItemsView
	{
		itemsView.EmptyViewTemplate = view;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.ItemsSource"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="itemsSource"></param>
	/// <returns>ItemsView with ItemSource</returns>
	public static TItemsView ItemsSource<TItemsView>(this TItemsView itemsView, IEnumerable itemsSource) where TItemsView : ItemsView
	{
		itemsView.ItemsSource = itemsSource;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.HorizontalScrollBarVisibility"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="visibility"></param>
	/// <returns>ItemsView with updated Horiztonal Scroll Bar Visibility</returns>
	public static TItemsView HorizontalScrollBarVisibility<TItemsView>(this TItemsView itemsView, ScrollBarVisibility visibility) where TItemsView : ItemsView
	{
		itemsView.HorizontalScrollBarVisibility = visibility;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.VerticalScrollBarVisibility"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="visibility"></param>
	/// <returns>ItemsView with updated Vertical Scroll Bar Visibility</returns>
	public static TItemsView VerticalScrollBarVisibility<TItemsView>(this TItemsView itemsView, ScrollBarVisibility visibility) where TItemsView : ItemsView
	{
		itemsView.VerticalScrollBarVisibility = visibility;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.VerticalScrollBarVisibility"/> and <see cref="ItemsView.HorizontalScrollBarVisibility"/> properties
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="visibility"></param>
	/// <returns>ItemsView with updated Horiztonal + Vertical Scroll Bar Visibility</returns>
	public static TItemsView ScrollBarVisibility<TItemsView>(this TItemsView itemsView, ScrollBarVisibility visibility) where TItemsView : ItemsView
	{
		return itemsView.HorizontalScrollBarVisibility(visibility).VerticalScrollBarVisibility(visibility);
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.RemainingItemsThreshold"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="threshold"></param>
	/// <returns>ItemsView with updated Remaining Items Threshold</returns>
	public static TItemsView RemainingItemsThreshold<TItemsView>(this TItemsView itemsView, int threshold) where TItemsView : ItemsView
	{
		itemsView.RemainingItemsThreshold = threshold;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.RemainingItemsThresholdReachedCommand"/>  ans <see cref="ItemsView.RemainingItemsThresholdReachedCommandParameter"/>properties
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="command"></param>
	/// <param name="parameter"></param>
	/// <returns>ItemsView with updated Remaining Items Threshold Reached Command + CommandParameter</returns>
	public static TItemsView RemainingItemsThresholdReachedCommand<TItemsView>(this TItemsView itemsView, ICommand command, object? parameter) where TItemsView : ItemsView
	{
		return itemsView.RemainingItemsThresholdReachedCommand(command).RemainingItemsThresholdReachedCommandParameter(parameter);
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.RemainingItemsThresholdReachedCommand"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="command"></param>
	/// <returns>ItemsView with updated Remaining Items Threshold Reached Command</returns>
	public static TItemsView RemainingItemsThresholdReachedCommand<TItemsView>(this TItemsView itemsView, ICommand command) where TItemsView : ItemsView
	{
		itemsView.RemainingItemsThresholdReachedCommand = command;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.RemainingItemsThresholdReachedCommandParameter"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="parameter"></param>
	/// <returns>ItemsView with updated Remaining Items Threshold Reached Command Parameter</returns>
	public static TItemsView RemainingItemsThresholdReachedCommandParameter<TItemsView>(this TItemsView itemsView, object? parameter) where TItemsView : ItemsView
	{
		itemsView.RemainingItemsThresholdReachedCommandParameter = parameter;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.ItemTemplate"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="template"></param>
	/// <returns>ItemsView with updated Item Template</returns>
	public static TItemsView ItemTemplate<TItemsView>(this TItemsView itemsView, DataTemplate template) where TItemsView : ItemsView
	{
		itemsView.ItemTemplate = template;
		return itemsView;
	}

	/// <summary>
	/// Assigns the <see cref="ItemsView.ItemsUpdatingScrollMode"/> property
	/// </summary>
	/// <typeparam name="TItemsView"></typeparam>
	/// <param name="itemsView"></param>
	/// <param name="mode"></param>
	/// <returns>ItemsView with updated ItemsUpdatingScrollMode</returns>
	public static TItemsView ItemsUpdatingScrollMode<TItemsView>(this TItemsView itemsView, ItemsUpdatingScrollMode mode) where TItemsView : ItemsView
	{
		itemsView.ItemsUpdatingScrollMode = mode;
		return itemsView;
	}
}

Usage Syntax

C# Usage

Content = new CollectionView()
                  .ItemSource(new [] { "Hello", "World" })
                  .ItemTemplate(new DataTemplate(() => new Label().Bind(Label.TextProperty, "."));

Drawbacks

No known drawbacks

Alternatives

Currently, the properties need to be set using initializers, leading to a combination of properties + extension methods:

Content = new CollectionView
{
  ItemTemplate = new DataTemplate(() => new Label().Bind(Label.TextProperty, "."))
}.Bind(CollectionView.ItemSourceProperty, nameof(ViewModel.ItemSource));

Unresolved Questions

No known questions

[Proposal] Add AbsoluteLayout Flags Extension Methods

Feature name

Update AbsoluteLayout Flags Extensions

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation: MicrosoftDocs/CommunityToolkit#271

Summary

This Proposal adds two new APIs to AbsoluteLayoutExtensions and updates the functionality of the existing .LayoutFlags() API to add existing AbsoluteLayoutFlags instead of overwriting existing AbsoluteLayoutFlags.

public static TBindable ClearLayoutFlags<TBindable>(this TBindable bindable) where TBindable : BindableObject;

public static TBindable LayoutFlags<TBindable>(this TBindable bindable, params AbsoluteLayoutFlags[] flags) where TBindable : BindableObject;

Motivation

1. .ClearLayoutFlags()

public static TBindable ClearLayoutFlags<TBindable>(this TBindable bindable) where TBindable : BindableObject;

Adding .ClearLayoutFlags() allows developers to easily remove any existing AbsoluteLayoutFlags using fluent APIs.

2. .LayoutFlags()

public static TBindable LayoutFlags<TBindable>(this TBindable bindable, params AbsoluteLayoutFlags[] flags) where TBindable : BindableObject;

Allowing this new .LayoutFlags() API helps developers (especially new developers) who may be unfamiliar with using Bitwise OR operator. This new API allows developers to pass in multiple AbsoluteLayoutFlags by listing them sequentially which provides a more natural feel and may be what some developers expect, like so:

new Label()
  .LayoutFlags(AbsoluteLayoutFlags.XProportional, AbsoluteLayoutFlags.WidthProportional)

Detailed Design

/// <summary>
/// Removes all <see cref="AbsoluteLayoutFlags"/>, reverting to the default configuraion of <see cref="AbsoluteLayoutFlags.None"/>.
/// </summary>
/// <typeparam name="TBindable"></typeparam>
/// <param name="bindable"></param>
/// <returns></returns>
public static TBindable ClearLayoutFlags<TBindable>(this TBindable bindable) where TBindable : BindableObject
{
  AbsoluteLayout.SetLayoutFlags(bindable, AbsoluteLayoutFlags.None);
  return bindable;
}

/// <summary>
/// Set an <see cref="AbsoluteLayoutFlags"/> that indicates whether the layout bounds position and size values for a child are proportional to the size of the <see cref="AbsoluteLayout"/>.
/// </summary>
/// <typeparam name="TBindable"></typeparam>
/// <param name="bindable"></param>
/// <param name="flags"></param>
/// <returns></returns>
public static TBindable LayoutFlags<TBindable>(this TBindable bindable, params AbsoluteLayoutFlags[] flags) where TBindable : BindableObject
{
  var newFlags = AbsoluteLayoutFlags.None;
  
  foreach(var flag in flags)
  {
	  newFlags |= flag;
  }
  
  AbsoluteLayout.SetLayoutFlags(bindable, newFlags);

  return bindable;
}

Usage Syntax

using Microsoft.Maui.Layouts;

namespace CommunityToolkit.Maui.Markup.Sample.Pages;

sealed class SettingsPage : BaseContentPage<SettingsViewModel>
{
	public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewModel, "Settings")
	{
		Content = new AbsoluteLayout
		{
			Children =
			{
				new Image().Source("dotnet_bot.png").Opacity(0.25).IsOpaque(false).Aspect(Aspect.AspectFit)
					.ClearLayoutFlags()
					.LayoutFlags(AbsoluteLayoutFlags.SizeProportional | AbsoluteLayoutFlags.PositionProportional)
					.LayoutBounds(0.5, 0.5, 0.5, 0.5)
					.AutomationIsInAccessibleTree(false),

				new Label()
					.Text("Top Stories To Fetch")
					.AppThemeBinding(Label.TextColorProperty,AppStyles.BlackColor, AppStyles.PrimaryTextColorDark)
					.LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional)
					.LayoutBounds(0, 0, 1, 40)
					.TextCenterHorizontal()
					.TextBottom()
					.Assign(out Label topNewsStoriesToFetchLabel),

				new Entry { Keyboard = Keyboard.Numeric }
					.BackgroundColor(Colors.White)
					.Placeholder($"Provide a value between {SettingsService.MinimumStoriesToFetch} and {SettingsService.MaximumStoriesToFetch}", Colors.Grey)
					.LayoutFlags(AbsoluteLayoutFlags.XProportional, AbsoluteLayoutFlags.WidthProportional)
					.LayoutBounds(0.5, 45, 0.8, 40)
					.Behaviors(new NumericValidationBehavior
					{
						Flags = ValidationFlags.ValidateOnValueChanged,
						MinimumValue = SettingsService.MinimumStoriesToFetch,
						MaximumValue = SettingsService.MaximumStoriesToFetch,
						ValidStyle = AppStyles.ValidEntryNumericValidationBehaviorStyle,
						InvalidStyle = AppStyles.InvalidEntryNumericValidationBehaviorStyle,
					})
					.TextCenter()
					.SemanticDescription(topNewsStoriesToFetchLabel.Text)
					.Bind(Entry.TextProperty, static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, static (SettingsViewModel vm, int text) => vm.NumberOfTopStoriesToFetch = text),

				new Label()
					.Bind(
						Label.TextProperty,
						binding1: new Binding { Source = SettingsService.MinimumStoriesToFetch },
						binding2: new Binding { Source = SettingsService.MaximumStoriesToFetch },
						convert: ((int minimum, int maximum) values) => string.Format(CultureInfo.CurrentUICulture, $"The number must be between {values.minimum} and {values.maximum}."),
						mode: BindingMode.OneTime)
					.LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional)
					.LayoutBounds(0, 90, 1, 40)
					.TextCenter()
					.AppThemeColorBinding(Label.TextColorProperty,AppStyles.BlackColor, AppStyles.PrimaryTextColorDark)
					.Font(size: 12, italic: true)
					.SemanticHint($"The minimum and maximum possible values for the {topNewsStoriesToFetchLabel.Text} field above.")
			}
		};
	}
}

Drawbacks

None

Alternatives

To clear existing AbsoluteLayoutFlags, a developer can use the existing method:

.LayoutFlags(AbsoluteLayoutFlags.None)

To add multiple AbsoluteLayoutFlags, a developer can use the existing method:

.LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional)

[BUG] Shell navigation not working in markup

Is there an existing issue for this?

  • I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

I register routes for more than two level, when navigating back from the third level it always jumps to root page.
this is for "normal" navigation and modal pages

Expected Behavior

Following the docs on https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-7.0

when calling
var route = $".."; return Shell.Current.GoToAsync(route);
from the ModalPage or the 2ndLevelPage in both cases it should pop the page of the stack and show NewsDetailPage

Steps To Reproduce

to reproduce:

  • pull https://github.com/hulluP/MauiShellNavigation
  • run in iOs or Android
  • click on any of the article entries on the NewsPage
  • click on the "Launch 2nd Level" button
  • click on close me -> this should not jump back to Newspage it should jump to Detail page
  • click on any of the article entries on the NewsPage
  • click on the "Launch Modal" button
  • click on close me -> this should not jump back to Newspage it should jump to Detail page

Link to public reproduction project repository

https://github.com/hulluP/MauiShellNavigation

Environment

- .NET MAUI C# Markup CommunityToolkit: 3.1.0
- 
- OS: 
   - iOS 14.2
   - Android 21
- .NET MAUI: 5.0.0

Anything else?

No response

[BUG] Source generator error - The call is ambiguous between the following methods or properties - CommunityToolkit.Maui.Markup.TextAlignmentExtensions

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

This is a peculiar issue as it is getting simulated only when CommunityToolkit.Maui.Markup is referenced in a class library or dependency for a NuGet package and that is in turn gets referenced in a .NET MAUI App.

Such a scenario results in the below error:

The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_XYZ.TextCenterHorizontal(Microsoft.Maui.Controls.XYZ)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_XYZ.TextCenterHorizontal(Microsoft.Maui.Controls.XYZ)'

Where XYZ is the type for which the TextCenterHorizontal() extension method is defined.

Detailed error:

Build started...
1>------ Build started: Project: MauiApp6, Configuration: Debug Any CPU ------
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\SearchBarTextAlignmentExtensions.g.cs(122,153,122,173): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_SearchBar.TextCenterHorizontal(Microsoft.Maui.Controls.SearchBar)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_SearchBar.TextCenterHorizontal(Microsoft.Maui.Controls.SearchBar)'
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\EntryCellTextAlignmentExtensions.g.cs(122,153,122,173): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_EntryCell.TextCenterHorizontal(Microsoft.Maui.Controls.EntryCell)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_EntryCell.TextCenterHorizontal(Microsoft.Maui.Controls.EntryCell)'
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\EditorTextAlignmentExtensions.g.cs(122,147,122,167): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Editor.TextCenterHorizontal(Microsoft.Maui.Controls.Editor)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Editor.TextCenterHorizontal(Microsoft.Maui.Controls.Editor)'
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\PickerTextAlignmentExtensions.g.cs(122,147,122,167): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Picker.TextCenterHorizontal(Microsoft.Maui.Controls.Picker)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Picker.TextCenterHorizontal(Microsoft.Maui.Controls.Picker)'
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\EntryTextAlignmentExtensions.g.cs(122,145,122,165): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Entry.TextCenterHorizontal(Microsoft.Maui.Controls.Entry)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Entry.TextCenterHorizontal(Microsoft.Maui.Controls.Entry)'
1>E:\MauiApp6\MauiApp6\CommunityToolkit.Maui.Markup.SourceGenerators\CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentExtensionsGenerator\LabelTextAlignmentExtensions.g.cs(122,145,122,165): error CS0121: The call is ambiguous between the following methods or properties: 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Label.TextCenterHorizontal(Microsoft.Maui.Controls.Label)' and 'CommunityToolkit.Maui.Markup.TextAlignmentExtensions_Label.TextCenterHorizontal(Microsoft.Maui.Controls.Label)'
1>Done building project "MauiApp6.csproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========
========== Elapsed 00:00.874 ==========

Expected Behavior

Should compile without any errors.

Steps To Reproduce

  1. Clone the project in the repository mentioned
  2. Try to build the project
  3. Will end up in the source generator error as detailed in the current behavior

In simple terms, whenever this Markup library is an external dependency, it results in this source generator error.

Link to public reproduction project repository

https://github.com/egvijayanand/markup-issue-158

Environment

- .NET MAUI C# Markup CommunityToolkit:2.0.0
- OS: Windows 11 (10.0.22621.819)
- .NET MAUI: .NET 7 (7.0.49)

Anything else?

This issue is encountered only when CommunityToolkit.Maui.Markup is an external dependency and not getting simulated when directly referenced in the App project.

[Proposal] Add TypedBindings to GesturesExtensions

Feature name

Add TypedBindings to GesturesExtensions

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation: MicrosoftDocs/CommunityToolkit#272

Summary

This proposal augments GesturesExtensions by adding the following APIs that support Typed Bindings

  • .BindClickGesture()
  • .BindSwipeGesture()
  • .BindTapGesture()

Motivation

This is a continuation of #155 adding the ability to use Typed Bindings in CommunityToolkit.Maui.Markup

Detailed Design

public static TGestureElement BindClickGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Expression<Func<TCommandBindingContext, ICommand>> getter,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

public static TGestureElement BindClickGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Func<TCommandBindingContext, ICommand>> getter,
  (Func<TCommandBindingContext, object?>, string)[] handlers,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

public static TGestureElement BindSwipeGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Expression<Func<TCommandBindingContext, ICommand>> getter,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

public static TGestureElement BindSwipeGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Func<TCommandBindingContext, ICommand>> getter,
  (Func<TCommandBindingContext, object?>, string)[] handlers,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

public static TGestureElement BindTapGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Expression<Func<TCommandBindingContext, ICommand>> getter,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

public static TGestureElement BindTapGesture<TGestureElement, TCommandBindingContext, TParameterBindingContext,
  TParameterSource>(
  this TGestureElement gestureElement,
  Func<TCommandBindingContext, ICommand>> getter,
  (Func<TCommandBindingContext, object?>, string)[] handlers,
  Action<TCommandBindingContext, ICommand>? setter = null,
  TCommandBindingContext? source = default,
  BindingMode commandBindingMode = BindingMode.Default,
  Expression<Func<TParameterBindingContext, TParameterSource>>? parameterGetter = null,
  Action<TParameterBindingContext, TParameterSource>? parameterSetter = null,
  BindingMode parameterBindingMode = BindingMode.Default,
  TParameterBindingContext? parameterSource = default,
  int? numberOfClicksRequired = null) where TGestureElement : BindableObject, IGestureRecognizers;

Usage Syntax

new Label().BindTapGesture(static (ViewModel vm) => vm.TapGestureCommand, mode: BindingMode.OneWay)

Drawbacks

None

Alternatives

The following APIs currently exist without support for Typed Bindings (require reflection):

  • BindClickGesture
  • BindSwipeGesture
  • BindTapGesture

Unresolved Questions

None

[Bug] Pasting into text entry does not work on iOS

Description

Hi, I'm a Maui / markup newbie and stumbled into this problem. In the settings, copy and pasting into Text entry field doesn't work.

Stack Trace

None. UI bug

Link to Reproduction Sample

https://github.com/CommunityToolkit/Maui.Markup/tree/main/samples

Steps to Reproduce

  1. Run the sample app
  2. Go to settings
  3. Copy paste a number into the text entry

Expected Behavior

Pasted value should show-up

Actual Behavior

Paste does not work. But works in other app like Messages. So it looks like copy > saves to device clipboard but paste doesn't work in the Maui app. But paste works in other iOS apps.

Basic Information

❯ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.400
 Commit:    7771abd614

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  12.5
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.400/

global.json file:
  Not found

Host:
  Version:      6.0.8
  Architecture: arm64
  Commit:       55fb7ef977

.NET SDKs installed:
  6.0.400 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Workaround

None that I know of

Reproduction imagery

Screenshot.000396.mp4

[Proposal] Add `IPlaceholder` Extension Methods

IPlaceholder Extension Methods

  • Proposed
  • Prototype
  • Implementation: Not Started
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests
  • Sample
  • Documentation

Link to Discussion

#41

Summary

This Proposal adds the following extension methods to support IPlaceholder controls:

public static TBindable PlaceholderColor<TBindable>(this TBindable bindable, Color? textColor) where TBindable : BindableObject, IPlaceholder
public static TBindable Placeholder<TBindable>(this TBindable bindable, string? text) where TBindable : BindableObject, IPlaceholder
public static TBindable Placeholder<TBindable>(this TBindable bindable, string? text, Color? textColor) where TBindable : BindableObject, IPlaceholder

Motivation

We recently approved the implementation of .Text() in #38 for IText and ITextStyle controls.

This proposal is the next logical step to also implement the extension methods .Placeholder() and .PlaceholderColor().

Detailed Design

public static TBindable PlaceholderColor<TBindable>(this TBindable bindable, Color? textColor) where TBindable : BindableObject, IPlaceholder
{
  bindable.SetValue(PlaceholderElement.PlaceholderColorProperty, textColor);
  return bindable;
}

public static TBindable Placeholder<TBindable>(this TBindable bindable, string? text) where TBindable : BindableObject, IPlaceholder
{
  bindable.SetValue(PlaceholderElement.PlaceholderProperty, text);
  return bindable;
}

public static TBindable Placeholder<TBindable>(this TBindable bindable, string? text, Color? textColor) where TBindable : BindableObject, IPlaceholder
{
  return bindable.Placeholder(text).PlaceholderColor(textColor);
}

Usage Syntax

C# Usage

Content = new VerticalStackLayout
{
  Children = 
  {
    new Entry().Placeholder("Phone Number", Colors.Grey),
    new Editor().Placeholder("Address, City, Zip", Colors.Grey)
  }
};

Drawbacks

Alternatives

The current alternative is to use property initialization:

Content = new VerticalStackLayout
{
  Children = 
  {
    new Entry { Placeholder = "Phone Number",  PlaceholderColor = Colors.Grey }
    new Editor { Placeholder = "Address, City, Zip",  PlaceholderColor = Colors.Grey }
  }
};

Unresolved Questions

No known questions

[Bug] `.Text()` Throws `InvalidCastException`

Description

In CommunityToolit.Maui.Markup v1.0.0-pre9, using .Text() on any IText other than Label

Stack Trace

   at Microsoft.Maui.Controls.Label.OnTextPropertyChanged(BindableObject bindable, Object oldvalue, Object newvalue)
   at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, Boolean silent)
   at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, Boolean fromStyle, Boolean checkAccess)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value)
   at CommunityToolkit.Maui.Markup.ElementExtensions.Text[Button](Button bindable, String text)
   at HelloMauiMarkup.MainPage..ctor(IDeviceInfo deviceInfo, MainViewModel mainViewModel) in /Users/bramin/GitHub/HelloMauiMarkup/src/Pages/MainPage.cs:line 13
   at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.DoInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2[[System.Type, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Func`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetOrAdd(Type key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type serviceType)
   at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[IApplication](IServiceProvider provider)
   at Microsoft.Maui.MauiUIApplicationDelegate.FinishedLaunching(UIApplication application, NSDictionary launchOptions)
   at UIKit.UIApplication.Main(String[] args, Type principalClass, Type delegateClass)
   at HelloMauiMarkup.Program.Main(String[] args) in /Users/bramin/GitHub/HelloMauiMarkup/src/Platforms/iOS/Program.cs:line 7

Reproduction Sample

Content = new Button().Text("Tap Here");

Expected Behavior

Button.Text should be set to "Tap Here"

Actual Behavior

InvalidCastException thrown.

Workaround

The only workaround is to set the Text property without using the extension methods

Content = new Button { Text = "Tap Here" };

[Bug] BindableLayout is not working - Making use of the ItemTemplate markup method

Description

BindableLayout is not working - Making use of the ItemTemplate markup method.

Stack Trace

Link to Reproduction Sample

https://github.com/egvijayanand/markup-issue-89

Steps to Reproduce

  1. Clone the repository
  2. Navigate to the src folder
  3. Open the LayoutApp solution
  4. Set Windows as the platform target, then build and run the project
  5. For testing, have provided both XAML and C# versions, switch to the appropriate start page in App.xaml.cs

Expected Behavior

A repeater kind of view that generates the view from the defined template for each of the items in the list.

Actual Behavior

No output. The screen is blank.

Basic Information

  • Version with issue: 1.0.1
  • Last known good version: NA
  • IDE: VS2022 17.3.0 Preview 4.0
  • Platform Target Frameworks:
    • iOS:
    • Android:
    • UWP/WinUI: net6.0-windows10.0.19041.0 - running on Windows 11
  • Android Support Library Version:
  • NuGet Packages:
  • Affected Devices:

Workaround

XAML version works as expected.

Reproduction imagery

[Bug] TextCenterVertical() not center vertically within Grid

Description

if i use TextCenterVertical(), Label is not center vertically (windows, android, i dont have ios so can't try it), but if i use standard code, the apps runs perfect.

Stack Trace

Link to Reproduction Sample

Steps to Reproduce

`public partial class TestPage : BasePage
{
private enum Baris
{
Date,
Line,
Date2,
Line2,
};

private enum Kolom
{
	Kol1,
	Kol2
}

public TestPage(TestViewModel vm) : base(vm, vm.Title)
{
    Label lblDate1 = new Label().TextCenterVertical().Text("Date"),
        lblLine1 = new Label().TextCenterVertical().Text("Line");

    Label lblDate2 = new Label()
    {
        VerticalTextAlignment = TextAlignment.Center,
    }.Text("Date 2"),
        lblLine2 = new Label()
        {
            VerticalTextAlignment = TextAlignment.Center,
        }.Text("Line 2");

    DatePicker dtpDate1 = new(),
        dtpDate2 = new();

    Entry txtLine1 = new()
    {
        Keyboard = Keyboard.Numeric,
        MaxLength = 11
    },
    txtLine2 = new()
    {
        Keyboard = Keyboard.Numeric,
        MaxLength = 11
    };

    Grid grid = new Grid
    {
        Padding = new(20),
        ColumnSpacing = 20,
        RowSpacing = 20,

        RowDefinitions = Rows.Define(Star, Star, Star, Star),
        ColumnDefinitions = Columns.Define(Stars(.30), Stars(.70)),
    };

    grid.Add(lblDate1, (int)Kolom.Kol1, (int)Baris.Date);
    grid.Add(dtpDate1, (int)Kolom.Kol2, (int)Baris.Date);

    grid.Add(lblLine1, (int)Kolom.Kol1, (int)Baris.Line);
    grid.Add(txtLine1, (int)Kolom.Kol2, (int)Baris.Line);

    grid.Add(lblDate2, (int)Kolom.Kol1, (int)Baris.Date2);
    grid.Add(dtpDate2, (int)Kolom.Kol2, (int)Baris.Date2);

    grid.Add(lblLine2, (int)Kolom.Kol1, (int)Baris.Line2);
    grid.Add(txtLine2, (int)Kolom.Kol2, (int)Baris.Line2);

    ScrollView scrollView = new()
    {
        Content = grid
    };

    Content = scrollView;
}

}`

Expected Behavior

test1

u can see the image, the 2 label above is not center vertically, but 2 label below is center vertically with the Entry Component

Actual Behavior

Basic Information

  • Version with issue:
  • Last known good version:
  • IDE:
  • Platform Target Frameworks:
    • iOS:
    • Android:
    • UWP:
  • Android Support Library Version:
  • Nuget Packages:
  • Affected Devices:

Workaround

Reproduction imagery

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.