使用参数将依赖项注入到 ContentView(代码隐藏)中

OXO*_*OXO 5 maui contentview

在我的 .NET MAUI MauiProgram.cs 中,我注册了一个 ResourceManager 以及名为 MyContentView 的 ContentView 的代码隐藏(指的是 MyContentView.xaml.cs):

来自 MauiProgram.cs 的部分

...
var builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp<App>()
    .UseMauiCommunityToolkit()
    .UseLocalizationResourceManager(settings =>
    {
        settings.RestoreLatestCulture(true);
        settings.AddResource(AppResources.ResourceManager);
    })
    .ConfigureFonts(fonts =>
    {
        fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
    });

builder.Services.AddTransient<MyItemInputControlView>();

builder.Services.AddTransient<MyContentPage>();
builder.Services.AddTransient<MyContentPageViewModel>(); 
Run Code Online (Sandbox Code Playgroud)

MyItemInputControlView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentView 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:views="clr-namespace:MyApp.Pages.Views"
             xmlns:loc="clr-namespace:LocalizationResourceManager.Maui;assembly=LocalizationResourceManager.Maui"
             xmlns:viewModels="clr-namespace:MyApp.ViewModels"
             x:Class="MyApp.Pages.Views.MyItemInputControlView"
             x:Name="this">

        
    <StackLayout BindingContext="{x:Reference this}">
        <Grid Margin="20, 0, 20, 0">
            <Grid.Resources>
                <!--<Style TargetType="Entry">
                    <Setter Property="Padding" Value="2 1" />
                    <Setter Property="BorderBrush" Value="LightGray" />
                </Style>-->
            </Grid.Resources>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>


            <StackLayout Grid.Row="0" Grid.Column="0" VerticalOptions="Center">
                <Label Text="{Binding NameLabelString}" />
                ...
            </StackLayout>
        </Grid>
    </StackLayout>
</ContentView>
Run Code Online (Sandbox Code Playgroud)

MyItemInputControlView.xaml.cs(隐藏代码)

namespace MyApp.Pages.Views;

using CommunityToolkit.Maui.Behaviors;
using CommunityToolkit.Mvvm.Messaging;
using LocalizationResourceManager.Maui;
using MyApp.ViewModels;
using MyApp.ViewModels.Messages;

public partial class MyItemInputControlView : ContentView
{
    public static readonly BindableProperty NameLabelProperty = BindableProperty.Create(nameof(NameLabelString), typeof(string), typeof(MyItemInputControlView), string.Empty, BindingMode.TwoWay);
    public static readonly BindableProperty IsOptionalProperty = BindableProperty.Create(nameof(IsOptionalLabelString), typeof(string), typeof(MyItemInputControlView), string.Empty, BindingMode.TwoWay);
    ...
    
    private ILocalizationResourceManager localizationResourceManager;


    public string NameLabelString
    {
        get => (string)GetValue(NameLabelProperty);
        set => SetValue(NameLabelProperty, $"{localizationResourceManager[value]}:");
    }

    ...
    
    // !!!! THE CONSTRUCTOR WITH ONE PARAMETER IS NEVER REACHED (only default-constructor)!!!
    // Wanted to assign the ILocalizationResourceManager to the Code-Behind
    public MyItemInputControlView(ILocalizationResourceManager res)
    {
    
        //WeakReferenceMessenger.Default.Register<LocalizationResourceManagerProviderMessage>(this, HandleLocalizationResourceManagerProviderMessage);

        InitializeComponent();

        ...
    }
    
    private void HandleLocalizationResourceManagerProviderMessage(object recipient, LocalizationResourceManagerProviderMessage message)
    {
        // Assign the ILocalizationResourceManager instance from the message
        localizationResourceManager = message.Value;
    }
}
Run Code Online (Sandbox Code Playgroud)

内容页-XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewModels="clr-namespace:MyApp.ViewModels"
             xmlns:controls="clr-namespace:MyApp.Pages.Views"
             xmlns:loc="clr-namespace:LocalizationResourceManager.Maui;assembly=LocalizationResourceManager.Maui"
             x:DataType="viewModels:ManageItemViewModel"
             x:Class="MyApp.Pages.ManageItem"
             Title="{loc:Translate ManageItem_Heading}">


    <VerticalStackLayout>

        <Label 
            Text="{loc:Translate ManageItem_HeadingLabel}"
            FontAttributes="Bold"
            VerticalOptions="Center" 
            HorizontalOptions="Center"
            Padding="20" />

        <StackLayout>
            <!-- "ManageItem_MandatoryLabelString" is the String from my Resource-File and will be translated in the Property-Getter -->
            <controls:MyItemInputControlView NameLabelString="ManageItem_MyProductNameLabel"
                                             IsOptionalLabelString="ManageItem_MandatoryLabelString"
                                             PlaceholderString="ManageItem_MyProductNamePlaceholder"
                                             EntryInput="{Binding MyItem.Name}"
                                             InputValid="{Binding MyItemNameInputValid, Mode=TwoWay}" 
                                             x:Name="firstContentView"/>
       </StackLayout>
</ContentPage>
Run Code Online (Sandbox Code Playgroud)

然后,在我的一个内容页面中,我到达一个带有两个参数的构造函数:

//!!! IN MY REGISTERED CONTENT PAGE THE CONSTRUCTOR INCLUDING ILocalizationResourceManager PARAMETER IS REACHED !!!
public partial class MyContentPage : ContentPage
{
    public MyContentPage(MyContentPageViewModel viewModel, ILocalizationResourceManager localizationResourceManager)
    {
        InitializeComponent();

        BindingContext = viewModel;
    }   
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,在我的 ContentView 的代码隐藏中,它始终只是默认构造函数。为什么会这样?我在这里做错了什么,ContentPage 正确地有 2 个构造函数参数,而我的 ContentView 却没有?

Liq*_*SFT 1

I made a small demo trying to use binding for BindableProperty, which works fine.

For MyItemInputControlView.xaml, almost the same:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         ...
  
   x:Class="MauiLocalizationResourceManagerSample.MyItemInputControlView">

    <StackLayout BindingContext="{x:Reference this}" HeightRequest="100">
        <StackLayout Grid.Row="0" Grid.Column="0" VerticalOptions="Center">
            <Label x:Name="mylabel" Text="{Binding NameLabelString}" />
        </StackLayout>
    </StackLayout>

</ContentView>
Run Code Online (Sandbox Code Playgroud)

For MyItemInputControlView.cs, create a BindableProperty. Pay attention to the naming convention of it. For more info. you could refer to Create a bindable property

Also you can see I define a OnStringChanged method which will be invoked if NameLabelString changed. And when changed, we will set new value for label text. For more info, you could refer to Detect property changes

public partial class MyItemInputControlView : ContentView
{
    public static readonly BindableProperty NameLabelStringProperty = BindableProperty.Create(nameof(NameLabelString), typeof(string),
        typeof(MyItemInputControlView), string.Empty, BindingMode.TwoWay,propertyChanged:OnStringChanged);

    private static void OnStringChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var a = bindable as MyItemInputControlView;
        a.mylabel.Text = newValue.ToString();
    }

    public string NameLabelString
    {
        get => (string)GetValue(MyItemInputControlView.NameLabelStringProperty);
        set => SetValue(MyItemInputControlView.NameLabelStringProperty, value);
    }

    public MyItemInputControlView()
    {
        InitializeComponent();
    }
}
Run Code Online (Sandbox Code Playgroud)

Then the ContentPage which consume the ContentView:

    <VerticalStackLayout>
        <controls:MyItemInputControlView NameLabelString="{Binding CounterClicked.Localized}"/>

    </VerticalStackLayout>
Run Code Online (Sandbox Code Playgroud)

For convenient I just set the BindingContext of ContentPage to MainPageViewModel:

public MainPage(MainPageViewModel mainPageViewModel)
{
    InitializeComponent();
    this.BindingContext = mainPageViewModel;
}
Run Code Online (Sandbox Code Playgroud)

And for MainPageViewModel:

public class MainPageViewModel
{
    ILocalizationResourceManager _localizationResourceManager;

    public LocalizedString CounterClicked { get; set; }

    public MainPageViewModel(ILocalizationResourceManager localizationResourceManager)
    {
        _localizationResourceManager = localizationResourceManager;
        CounterClicked = new(() => _localizationResourceManager["HelloWorld"]);
    }

}
Run Code Online (Sandbox Code Playgroud)

Don't forget to register MainPageViewModel in MauiProgram as we use DI for it:

builder.Services.AddTransient<MainPageViewModel>();
Run Code Online (Sandbox Code Playgroud)

And the NameLabelString could be localized and pass it to the ContentView. Much appreciated if you could have a try.

Update

Also if you don't want to use bindings, simply set

<VerticalStackLayout>
    <controls:MyItemInputControlView NameLabelString="{loc:Translate HelloWorld}"/>
</VerticalStackLayout>
Run Code Online (Sandbox Code Playgroud)

This tutorial made by Gerald Versluis inspire me. You could refer to Translate Your .NET MAUI App with LocalizationResourceManager.Maui