Curs3W4ll
Curs3W4ll

Reputation: 46

.net MAUI Community Toolkit popup different layout behavior than page

I'm dealing with popups from .net MAUI Community Toolkit. I'm currently displaying my custom controls in popups. However, I noticed a layout difference in my controls when they are displayed on a page or in a popup. When my controls are displayed in a popup, their element's width will automatically stretch out to fill all the available space (which I do not want, and they are not doing this when displayed on a page).

For example, here are the two renders I got when I displayed the same control on a page and in a popup (notice the Show a popup button width difference)

In page In popup
Control render in page Control render in popup

As I use the same control, I expect the render to be the same.

My question is why does my control render differently if it's displayed on a page or in a popup?, and how to avoid that Layout behavior difference?

Here is the example code:

Control definition

namespace Medali.Views.Components
{
    public partial class TestComponent : ContentView
    {
        public TestComponent()
        {
            InitializeComponent();
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:Class="Medali.Views.Components.TestComponent"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:Medali.Views.Components"
             >
    <VerticalStackLayout Spacing="30" WidthRequest="500" BackgroundColor="Yellow" >
        <Label Text="Test"
               FontFamily="Inter700"
               FontSize="32"
               HeightRequest="60"
               HorizontalOptions="Center"
               TextColor="{StaticResource Blue}"
               VerticalTextAlignment="Center"
               HorizontalTextAlignment="Center"
               />
        <controls:ButtonComponent Text="Test button - Fill"
                                  FillContainer="True"
                                 />
        <controls:ButtonComponent Text="Test button - No fill"
                                  FillContainer="True"
                                 />
    </VerticalStackLayout>
</ContentView>

Popup definition

using CommunityToolkit.Maui.Views;
using CommunityToolkit.Mvvm.Input;
using Medali.ViewModels.Components;

namespace Medali.Views.Components
{
    public partial class ComponentPopup: Popup
    {
        public ComponentPopup(int width, int height, Color backgroundColor)
        {
            InitializeComponent();
            Size = new Size(width, height);
            mainFrame.BackgroundColor = backgroundColor;
        }

        public T setComponent<T>() where T : ContentView, new()
        {
            mainFrame.Content = new T();
            return (T)mainFrame.Content;
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<mct:Popup x:Class="Medali.Views.Components.ComponentPopup"
           xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
           xmlns:controls="clr-namespace:Medali.Views.Components"
           xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
           x:Name="componentPopupComponent"
           CanBeDismissedByTappingOutsideOfPopup="False"
           Color="Transparent"
           >

    <Frame x:Name="mainFrame"
           CornerRadius="10"
           Padding="0"
            >
    </Frame>
</mct:Popup>

Page definition

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:DataType="viewmodel:ConnectionViewModel"
             x:Class="Medali.Views.Pages.ConnectionPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:Medali.Views.Components"
             xmlns:models="clr-namespace:Medali.Models.Pages"
             xmlns:viewmodel="clr-namespace:Medali.ViewModels.Pages"
             Title=""
             >

    <Grid BackgroundColor="{StaticResource White}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="885*"/>
            <ColumnDefinition Width="1035*"/>
        </Grid.ColumnDefinitions>
        <Image Grid.Column="0"
               Aspect="Fill"
               Source="waves.png"
               />
        <Image Grid.Column="0"
               Margin="200,0,0,0"
               HeightRequest="250"
               HorizontalOptions="Start"
               Source="medali.png"
               WidthRequest="250"
               />

        <controls:AuthFormComponent Grid.Column="1"
                                    HorizontalOptions="FillAndExpand"
                                    VerticalOptions="Center"
                                    />
    </Grid>
</ContentPage>

Button control definition:

using CommunityToolkit.Mvvm.Input;
using Medali.Services;
using Medali.ViewModels.Components;

namespace Medali.Views.Components
{
    public partial class ButtonComponent : ContentView
    {
        private readonly ButtonComponentViewModel vm;

        public ButtonComponent()
        {
            InitializeComponent();
            vm = new() { paddingStyle = NoI, fillStyle = NoFillC, MainColor = ColorHelper.GetColor("DarkBlue") };
            updateColors();
            Loaded += (s, e) =>
            {
                contentContainer.Style = vm.paddingStyle;
                btnContainer.Style = vm.fillStyle;
            };
        }

        public static BindableProperty TypeProperty = BindableProperty.Create(
            nameof(Type),
            typeof(string),
            typeof(ButtonComponent),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;

                switch (newValue as string)
                {
                    case "Secondary":
                        control.vm.MainColor = ColorHelper.GetColor("Blue");
                        break;
                    case "Confirm":
                        control.vm.MainColor = ColorHelper.GetColor("Green");
                        break;
                    case "Danger":
                        control.vm.MainColor = ColorHelper.GetColor("Red");
                        break;
                    case "Primary":
                    default:
                        control.vm.MainColor = ColorHelper.GetColor("DarkBlue");
                        break;
                }
                control.updateColors();
            }
        );
        public string Type
        {
            get => (string)GetValue(TypeProperty);
            set => SetValue(TypeProperty, value);
        }

        public static BindableProperty ImageLeftProperty = BindableProperty.Create(
            nameof(ImageLeft),
            typeof(string),
            typeof(ButtonComponent),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;
                control.IconLeft.Source = newValue as string;
                control.IconLeft.IsVisible = true;
                if (control.ImageRight is null)
                {
                    control.vm.paddingStyle = control.ILeft;
                } else
                {
                    control.vm.paddingStyle = control.ILeftRight;
                }
            }
        );
        public string ImageLeft
        {
            get => (string)GetValue(ImageLeftProperty);
            set => SetValue(ImageLeftProperty, value);
        }

        public static BindableProperty ImageRightProperty = BindableProperty.Create(
            nameof(ImageRight),
            typeof(string),
            typeof(ButtonComponent),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;
                control.IconRight.Source = newValue as string;
                control.IconRight.IsVisible = true;
                if (control.ImageLeft is null)
                {
                    control.vm.paddingStyle = control.IRight;
                } else
                {
                    control.vm.paddingStyle = control.ILeftRight;
                }
            }
        );
        public string ImageRight
        {
            get => (string)GetValue(ImageRightProperty);
            set => SetValue(ImageRightProperty, value);
        }

        public static BindableProperty FillContainerProperty = BindableProperty.Create(
            nameof(FillContainer),
            typeof(bool),
            typeof(ButtonComponent),
            defaultValue: false,
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;
                control.vm.fillStyle = ((bool)newValue ? control.FillC : control.NoFillC);
            }
        );
        public bool FillContainer
        {
            get => (bool)GetValue(FillContainerProperty);
            set => SetValue(FillContainerProperty, value);
        }

        public static BindableProperty TextProperty = BindableProperty.Create(
            nameof(Text),
            typeof(string),
            typeof(ButtonComponent),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;

                control.lbl.Text = newValue as string;
            }
        );
        public string Text
        {
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }

        public static BindableProperty BgColorProperty = BindableProperty.Create(
            nameof(BgColor),
            typeof(Color),
            typeof(ButtonComponent),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;
                if (newValue as Color is not null)
                {
                    control.vm.MainColor = (Color)newValue;
                }
                control.updateColors();
            }
        );
        public Color BgColor
        {
            get => (Color)GetValue(BgColorProperty);
            set => SetValue(BgColorProperty, value);
        }

        public static BindableProperty IsDisableProperty = BindableProperty.Create(
            nameof(IsDisable),
            typeof(bool),
            typeof(ButtonComponent),
            defaultValue: false,
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                var control = (ButtonComponent)bindable;

                control.vm.isDisable = (bool)newValue;
                control.updateColors();
            }
        );
        public bool IsDisable
        {
            get => (bool)GetValue(IsDisableProperty);
            set => SetValue(IsDisableProperty, value);
        }

        public static BindableProperty AsyncActionCommandProperty = BindableProperty.Create(
            nameof(AsyncActionCommand),
            typeof(AsyncRelayCommand),
            typeof(ButtonComponent),
            null
        );
        public AsyncRelayCommand AsyncActionCommand
        {
            get => (AsyncRelayCommand)GetValue(AsyncActionCommandProperty);
            set => SetValue(AsyncActionCommandProperty, value);
        }

        public static BindableProperty ActionCommandProperty = BindableProperty.Create(
            nameof(ActionCommand),
            typeof(RelayCommand),
            typeof(ButtonComponent),
            null
        );
        public RelayCommand ActionCommand
        {
            get => (RelayCommand)GetValue(ActionCommandProperty);
            set => SetValue(ActionCommandProperty, value);
        }

        void setNewTintColor(Color color)
        {
            lbl.TextColor = color;
            IconLeftColor.TintColor = color;
            IconRightColor.TintColor = color;
        }
        void setNewBackgroundColor(Color color)
        {
            btnContainer.BackgroundColor = color;
        }
        void updateColors()
        {
            setNewTintColor(vm.GetTintColor());
            setNewBackgroundColor(vm.GetBackgroundColor());
        }

        void OnPointerEntered(object sender, PointerEventArgs e)
        {
            vm.isHover = true;
            updateColors();
        }

        void OnPointerExited(object sender, PointerEventArgs e)
        {
            vm.isHover = false;
            updateColors();
        }

        void OnTapGestureRecognizerTapped(object sender, TappedEventArgs args)
        {
            if (!IsDisable)
            {
                ActionCommand?.Execute(null);
                AsyncActionCommand?.Execute(null);
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:DataType="viewmodel:ConnectionViewModel"
             x:Class="Medali.Views.Components.ButtonComponent"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:viewmodel="clr-namespace:Medali.ViewModels.Pages"
             >
    <ContentView.Resources>
        <Style x:Key="NoImage"
               x:Name="NoI"
               TargetType="HorizontalStackLayout"
               >
            <Setter Property="Padding" Value="32,0" />
        </Style>
        <Style x:Key="ImageLeft"
               x:Name="ILeft"
               TargetType="HorizontalStackLayout"
               >
            <Setter Property="Padding" Value="16,0,32,0" />
        </Style>
        <Style x:Key="ImageRight"
               x:Name="IRight"
               TargetType="HorizontalStackLayout"
               >
            <Setter Property="Padding" Value="32,0,16,0" />
        </Style>
        <Style x:Key="ImageLeftRight"
               x:Name="ILeftRight"
               TargetType="HorizontalStackLayout"
               >
            <Setter Property="Padding" Value="16,0" />
        </Style>

        <Style x:Key="FillContainer"
               x:Name="FillC"
               TargetType="Frame">
            <Setter Property="HorizontalOptions" Value="Fill" />
        </Style>
        <Style x:Key="NoFillContainer"
               x:Name="NoFillC"
               TargetType="Frame">
            <Setter Property="HorizontalOptions" Value="Center" />
        </Style>
    </ContentView.Resources>

    <Frame x:Name="btnContainer"
        CornerRadius="10"
           IsClippedToBounds="True"
           Padding="0"
           BorderColor="{StaticResource Black}"
           VerticalOptions="Center"
           HeightRequest="60"
            >
        <Frame
            HorizontalOptions="Fill"
            VerticalOptions="Fill"
            Padding="0"
            BorderColor="Transparent"
            BackgroundColor="Transparent"
            >
            <HorizontalStackLayout
                x:Name="contentContainer"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                Spacing="16">
                <Image x:Name="IconLeft"
                       HeightRequest="55"
                       IsVisible="False"
                       WidthRequest="55"
                       >
                    <Image.Behaviors>
                        <toolkit:IconTintColorBehavior x:Name="IconLeftColor" TintColor="White"/>
                    </Image.Behaviors>
                </Image>

                <Label x:Name="lbl"
                       FontFamily="Inter700"
                       FontSize="24"
                       HorizontalTextAlignment="Center"
                       VerticalTextAlignment="Center"
                       TextColor="White"
                       />

                <Image x:Name="IconRight"
                       HeightRequest="55"
                       IsVisible="False"
                       WidthRequest="55"
                       >
                    <Image.Behaviors>
                        <toolkit:IconTintColorBehavior x:Name="IconRightColor" TintColor="White"/>
                    </Image.Behaviors>
                </Image>
            </HorizontalStackLayout>
        </Frame>
        <Frame.GestureRecognizers>
            <PointerGestureRecognizer PointerEntered="OnPointerEntered" PointerExited="OnPointerExited" />
            <TapGestureRecognizer Tapped="OnTapGestureRecognizerTapped"/>
        </Frame.GestureRecognizers>
    </Frame>
</ContentView>

Upvotes: 1

Views: 869

Answers (1)

Guangyu Bai - MSFT
Guangyu Bai - MSFT

Reputation: 4576

This is the issue in the Frame. The Frame is a control that is in Xamarin.Forms, and It is recommended to change the Frame to the Border in MAUI. You can check this answer about the difference between Frame and Border. Here also has the sample(.Net MAUI Xaml - adjust text and frame when resizing) to fix the issue by using the border.

Upvotes: 0

Related Questions