You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI绑定属性的StringFormat无法显示附加文本问题

.NET MAUI自定义控件中StringFormat无法添加百分号后缀的问题

在.NET MAUI的XAML自定义控件里,我给Entry绑定了带Bindable Property的属性,想给文本加%后缀,但设置的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);
}

完整自定义控件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">

    <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);
        }
    }
    
   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();
       });
   
    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 = 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));

问题原因及解决方法

问题核心是绑定属性的propertyChanged回调直接覆盖了Entry的Text值,导致XAML里设置的StringFormat完全被忽略——不管你在XAML里怎么写格式,回调里的直接赋值都会把它覆盖掉。

两种正确实现方式:

方式1:移除回调,让XAML绑定自动处理格式

修改PercentProperty的定义,去掉propertyChanged回调,同时在XAML里给Entry的Text绑定加上StringFormat:

后台代码修改:

public static readonly BindableProperty PercentProperty = BindableProperty.Create(nameof(Percent), typeof(int), typeof(StatsControl), defaultValue:0);

public int Percent
{
    get => (int)GetValue(PercentProperty);
    set => SetValue(PercentProperty, value);
}

XAML里的PercentEntry修改:

<Entry
    x:Name="PercentEntry"
    Margin="10,0,10,0"
    HorizontalOptions="Start"
    IsReadOnly="True"
    Text="{Binding Percent, Source={x:Reference this}, FallbackValue='0%', StringFormat='{0}%'}"
    VerticalOptions="Center" />

方式2:在回调里处理格式(保留现有逻辑)

如果一定要保留propertyChanged回调,直接在回调里拼接百分号即可(你当前已经这么做了),但要注意XAML里的绑定不需要再加StringFormat,避免重复:

XAML里的PercentEntry保持现有代码,后台的PercentProperty回调逻辑不变。这种方式适合需要更复杂格式处理的场景。

另外,你的其他绑定属性(Correct、Wrong、Title)也存在同样的问题——用回调直接赋值会绕过XAML的绑定系统,建议统一改成移除回调,让绑定自动更新UI,更符合MAUI的绑定规范。

内容的提问来源于stack exchange,提问作者jason.kaisersmith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 14:10:20