XAML .Net Maui - ContentView with a ViewModel

jpfvwuh4  于 2023-08-01  发布在  .NET
关注(0)|答案(2)|浏览(107)

因此,我试图在一个视图模型的ContentView中进行数据绑定。我认为这应该很容易,因为MVVM应该是MAUI的东西,但也许我错过了一些东西。当前的解决方案基于Databinding issue
所以我有一个简单的ContentView,像这样:

<?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:viewModel="clr-namespace:MyProject.ViewModels"
             x:Name="view"
             x:Class="MyProject.Components.ContentViewComponent">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="10"/>
        </Grid.ColumnDefinitions>

        <Label Grid.Row="1" Grid.Column="1" 
               //This NEVER picks up the value of Title >> Why??
               Text="{Binding VM.Title, Source={x:Reference view}}"
               FontSize="24"
               FontAttributes="Bold"/>

    </Grid>

</ContentView>

字符串
我的简单ContentView的Code-Behind:

using MyProject.ViewModels;

namespace MyProject.Components;

public partial class ContentViewComponent: ContentView
{
    internal MyViewModel VM { get; set; }
    
    public static readonly BindableProperty TProperty = BindableProperty.Create(nameof(T), typeof(string), typeof(MetricImperialDropdownConverter), string.Empty, propertyChanged : TitleChanged);
    private static void TitleChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        //This fires and sets Title to T
        ((ContentViewComponent)bindable).VM.Title = (string)newvalue;
    }

    //I want to be able to set this when reusing the component 
    public string T
    {
        get => (string)GetValue(TProperty);
        set => SetValue(TProperty, value);
    }

    public MetricImperialDropdownConverter()
    {
        VM = new MyViewModel();
        InitializeComponent();
    } 
}


然后我有一个这样的ViewModel:

using System.ComponentModel;

namespace MyProject.ViewModels
{
    public class MetricImperialDropdownConverterViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnProperyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private string _title = string.Empty;
        public string Title
        {
            get { return _title; }
            set { _title = value; OnProperyChanged(nameof(Title)); }
        }

}


然后使用this并传入一个值:

<?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:components="clr-namespace:MyProject.Components"
             x:Class="MyProject.Pages.SomePage"
             x:Name="this">
    <VerticalStackLayout BindingContext="{x:Reference this}">
        //This works and sets T correctly
        <components:ContentViewCompontent T="Here is my Title"/>
    </VerticalStackLayout>
</ContentPage>


组件的T设置正确。在设置T时,我的ViewModel中的Title属性VM是通过PropertyChanged事件实现的。但是UI永远不会使用Title的值进行更新。我认为这是因为UI不响应发生在其自身上下文之外的事件。但在这种情况下我该怎么办呢??如何使UI正确更新??

liwlm1x9

liwlm1x91#

从你的代码中我可以看出,你是从视图而不是从ViewModel设置Label的,坦率地说,你不应该让ContentView与ViewModel通信。它应该是可恢复的。View与ViewModel和控件通信。
我认为你混合了ContentPage(Views)和ContentViews(controls)。因此,完整的ContentView应该看起来像这样:

public partial class ContentViewComponent : ContentView
{
    public static readonly BindableProperty TProperty = BindableProperty.Create(nameof(T), typeof(string), typeof(ContentViewComponent), string.Empty, BindingMode.OneWay, propertyChanged: TitleChanged);

    public ContentViewComponent()
    {
        InitializeComponent();
    }

    private static void TitleChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        ((ContentViewComponent)bindable).MyLabel.Text = (string)newvalue;
    }

    public string T
    {
        get => (string)GetValue(TProperty);
        set => SetValue(TProperty, value);
    }
}

字符串
和xaml:

<ContentView
x:Class="MauiTest.Controls.ContentViewComponent"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<VerticalStackLayout>
    <Label
        x:Name="MyLabel"
        Text=""
        TextColor="Black" />
</VerticalStackLayout>

kmynzznz

kmynzznz2#

根据你的代码,我实现了这个功能,可以参考下面的代码:

ContentViewComponent.xaml.cs

添加可绑定属性YourName。你可以把它换成你的。

public partial class ContentViewComponent : ContentView 
{
    public String YourName
    {
        get
        {
            String value = (String)GetValue(YourNameProperty);
            return value;
        }
        set
        {
            SetValue(YourNameProperty, value);
        }
    }

    public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
    , typeof(String)
    , typeof(ChildView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);

    static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
    {
        Console.WriteLine("-----------------> " + newValue);
    }

    public ContentViewComponent()
      {
            InitializeComponent();
      }
}

字符串

ContentViewComponent.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"
             x:Class="MauiContentViewApp.ContentViewComponent"
             x:Name="view"
             >
    <VerticalStackLayout>
        <Label   Text="{Binding Source={x:Reference view}, Path=YourName}"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentView>

MetricImperialDropdownConverterViewModel.cs

在这个视图模型中,我添加了一个Button命令来更新Title的值

public class MetricImperialDropdownConverterViewModel: INotifyPropertyChanged 
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnProperyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private string _title = string.Empty;
        public string Title
        {
            get { return _title; }
            set { _title = value; OnProperyChanged(nameof(Title)); }
        }

        public MetricImperialDropdownConverterViewModel() 
        {
            Title = "initial title";
        }

        public ICommand ChangeNameCommand => new Command(changeMethod);

        private void changeMethod()
        {
            Title = "update data here";
        }
    }


使用示例:

<?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"
             x:Class="MauiContentViewApp.NewPage2"
             xmlns:mauiapp="clr-namespace:MauiContentViewApp"
             Title="NewPage2">

    <ContentPage.BindingContext>
        <mauiapp:MetricImperialDropdownConverterViewModel></mauiapp:MetricImperialDropdownConverterViewModel>
    </ContentPage.BindingContext>
    
    <VerticalStackLayout>
        <mauiapp:ContentViewComponent YourName="{Binding Title}" ></mauiapp:ContentViewComponent>

        <Button  Text="change value" Command="{Binding ChangeNameCommand}"></Button>
    </VerticalStackLayout>
</ContentPage>

相关问题