我正在处理来自 .net MAUI Community Toolkit 的弹出窗口。 我目前正在弹出窗口中显示我的自定义控件。 但是,我注意到当控件显示在页面或弹出窗口中时,它们的布局存在差异。 当我的控件显示在弹出窗口中时,它们的元素的宽度将自动拉伸以填充所有可用空间(我不希望这样做,并且在页面上显示时它们不会这样做)。
例如,以下是我在页面和弹出窗口中显示相同控件时得到的两个渲染(注意
Show a popup
按钮宽度差异)
当我使用相同的控件时,我希望渲染结果是相同的。
我的问题是,为什么我的控件在页面或弹出窗口中显示时呈现不同?以及如何避免布局行为差异?
这是示例代码:
控制定义
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>
弹出定义
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>
页面定义
<?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>
按钮控件定义:
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>
这是框架中的问题。 Frame是Xamarin.Forms中的一个控件,建议在MAUI中将Frame更改为Border。你可以查看这个答案关于Frame和Border之间的区别。 这里还有示例(.Net MAUI Xaml - 调整大小时调整文本和框架)以使用边框解决问题。