因此,我在 .net MAUI XAML 中创建了一个具有可绑定属性的自定义控件。
首先,我希望在特定视图上的文本后面显示
%
作为后缀,但 StringFormat 不显示它。
我的视图元素
<Entry
x:Name="PercentEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Percent, Mode=OneWay, StringFormat='{0} %'}"
VerticalOptions="Center" />
我的代码
public static readonly BindableProperty PercentProperty = BindableProperty.Create(nameof(Percent), typeof(string), typeof(StatsControl),
propertyChanged: (bindable, oldValue, newValue) => {
var control = (StatsControl)bindable;
control.PercentEntry.Text = newValue as string;
});
public string Percent
{
get => GetValue(PercentProperty) as string;
set => SetValue(PercentProperty, value);
}
实际值正确呈现并且工作正常。但在显示值之前或之后,我没有在 StringFormat 中放置其他文本!
编辑: 经过大量研究后,我仍然无法真正使用 StringFormat 来实现这一点,所以唯一的方法是在我需要的地方对百分比符号进行硬编码。
我的自定义控件完整
<ContentView
x:Class="MathsForFlorence.Controls.StatsControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MathsForFlorence.Controls">
<Border
BackgroundColor="AliceBlue"
HorizontalOptions="Center"
Stroke="Black"
StrokeShape="RoundRectangle 10"
StrokeThickness="0"
WidthRequest="360">
<VerticalStackLayout HorizontalOptions="Center">
<Label
x:Name="TitleLabel"
Margin="5,5,0,0"
FontAttributes="Bold"
FontSize="14"
Text="{Binding Title, FallbackValue='Title'}" />
<FlexLayout
AlignContent="Stretch"
AlignItems="Stretch"
Direction="Row"
HorizontalOptions="CenterAndExpand"
JustifyContent="SpaceEvenly"
Wrap="Wrap">
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Correct:"
VerticalOptions="Center" />
<Entry
x:Name="CorrectEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Correct, Source={RelativeSource AncestorType={x:Type local:StatsControl}}}"
VerticalOptions="Center" />
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Wrong:"
VerticalOptions="Center" />
<Entry
x:Name="WrongEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Wrong, FallbackValue='0'}"
VerticalOptions="Center" />
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Percentage correct:"
VerticalOptions="Center" />
<Entry
x:Name="PercentEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Percent, Source={x:Reference this}, FallbackValue='0%'}"
VerticalOptions="Center" />
</HorizontalStackLayout>
</FlexLayout>
</VerticalStackLayout>
</Border>
</ContentView>
控件代码
public partial class StatsControl : ContentView
{
public static readonly BindableProperty CorrectProperty = BindableProperty.Create(nameof(Correct), typeof(int), typeof(StatsControl),
propertyChanged: (bindable, oldValue, newValue) => {
var control = (StatsControl)bindable;
control.CorrectEntry.Text = newValue.ToString();
});
public int Correct
{
get => (int)GetValue(CorrectProperty);
set
{
SetValue(CorrectProperty, value);
//OnPropertyChanged(nameof(Correct));
}
}
public static readonly BindableProperty WrongProperty = BindableProperty.Create(nameof(Wrong), typeof(int), typeof(StatsControl),
propertyChanged: (bindable, oldValue, newValue) => {
var control = (StatsControl)bindable;
control.WrongEntry.Text = newValue.ToString();
//control.WrongEntry.Text = String.Format(newValue as string, "{0}");
});
public int Wrong
{
get => (int)GetValue(WrongProperty);
set => SetValue(WrongProperty, value);
}
public static readonly BindableProperty PercentProperty = BindableProperty.Create(nameof(Percent), typeof(int), typeof(StatsControl),
propertyChanged: (bindable, oldValue, newValue) => {
var control = (StatsControl)bindable;
//control.PercentEntry.Text = (newValue as string) + "%";
control.PercentEntry.Text = String.Format("{0}%", newValue);
});
public int Percent
{
get => (int)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(StatsControl),
propertyChanged: (bindable, oldValue, newValue) => {
var control = (StatsControl)bindable;
control.TitleLabel.Text = newValue as string;
});
public string Title
{
get => GetValue(TitleProperty) as string;
set
{
SetValue(TitleProperty, value);
}
}
public StatsControl()
{
InitializeComponent();
}
}
我如何使用控件
<controls:StatsControl
x:Name="AdditionStatsControl"
Title="Addition"
Margin="0,5,0,0"
Correct="{Binding AdditionCorrect}"
Percent="{Binding AdditionPercentage}"
Wrong="{Binding AdditionWrong}" />
如何在页面的 ViewModel 中设置值
#region Properties - Addition
[ObservableProperty]
int additionCorrect;
[ObservableProperty]
int additionWrong;
[ObservableProperty]
int additionPercentage;
#endregion
...
AdditionCorrect = await SumCorrect(MathOperator.Add);
AdditionWrong = await SumWrong(MathOperator.Add);
AdditionPercentage = Helpers.CalculatePercent(AdditionCorrect, AdditionWrong);
OnPropertyChanged(nameof(AdditionCorrect));
OnPropertyChanged(nameof(AdditionWrong));
OnPropertyChanged(nameof(AdditionPercentage));
问题是您通过在控件的代码隐藏中设置
Text
属性来覆盖绑定表达式:
control.PercentEntry.Text = newValue as string;
您可以使用绑定 或 在代码隐藏中设置
Text
属性,但不能同时执行这两种操作。
您想要做的事情可以通过比在自定义控件的代码隐藏中设置值更简单的方式来实现。您根本不需要属性更改处理程序。
您可以按如下方式简化控件的隐藏代码:
public partial class StatsControl : ContentView
{
public static readonly BindableProperty CorrectProperty = BindableProperty.Create(nameof(Correct), typeof(int), typeof(StatsControl));
public static readonly BindableProperty WrongProperty = BindableProperty.Create(nameof(Wrong), typeof(int), typeof(StatsControl));
public static readonly BindableProperty PercentProperty = BindableProperty.Create(nameof(Percent), typeof(int), typeof(StatsControl));
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(StatsControl));
public int Correct
{
get => (int)GetValue(CorrectProperty);
set => SetValue(CorrectProperty, value);
}
public int Wrong
{
get => (int)GetValue(WrongProperty);
set => SetValue(WrongProperty, value);
}
public int Percent
{
get => (int)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public StatsControl()
{
InitializeComponent();
}
}
然后将您的 XAML 更改为:
<ContentView
x:Class="MathsForFlorence.Controls.StatsControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MathsForFlorence.Controls"
x:Name="MyStatsControl">
<Border
BackgroundColor="AliceBlue"
HorizontalOptions="Center"
Stroke="Black"
StrokeShape="RoundRectangle 10"
StrokeThickness="0"
WidthRequest="360">
<VerticalStackLayout HorizontalOptions="Center">
<Label
x:Name="TitleLabel"
Margin="5,5,0,0"
FontAttributes="Bold"
FontSize="14"
Text="{Binding Title, Source={x:Reference MyStatsControl}}" />
<FlexLayout
AlignContent="Stretch"
AlignItems="Stretch"
Direction="Row"
HorizontalOptions="CenterAndExpand"
JustifyContent="SpaceEvenly"
Wrap="Wrap">
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Correct:"
VerticalOptions="Center" />
<Entry
x:Name="CorrectEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Correct, Source={x:Reference MyStatsControl}}"
VerticalOptions="Center" />
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Wrong:"
VerticalOptions="Center" />
<Entry
x:Name="WrongEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Wrong, Source={x:Reference MyStatsControl}}"
VerticalOptions="Center" />
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Center">
<Label
Margin="10,0,10,0"
HorizontalOptions="Start"
Text="Percentage correct:"
VerticalOptions="Center" />
<Entry
x:Name="PercentEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Percent, Source={x:Reference MyStatsControl}, StringFormat='{}{0}%'}"
VerticalOptions="Center" />
</HorizontalStackLayout>
</FlexLayout>
</VerticalStackLayout>
</Border>
</ContentView>
我在这里所做的是通过分配
x:Name="MyStatsControl"
为整个控件命名,这样,它可以用作使用 x:Reference
的 be 绑定表达式的源,例如:
<Entry
x:Name="PercentEntry"
Margin="10,0,10,0"
HorizontalOptions="Start"
IsReadOnly="True"
Text="{Binding Percent, Source={x:Reference MyStatsControl}, StringFormat='{}{0}%'}"
VerticalOptions="Center" />
现在,
Entry的
Text
属性不再在代码隐藏中被覆盖,但当绑定的Percent
属性更改时,它仍然应该接收更新,同时始终以这种格式显示它: 42%
。