XAML 如何使视图模型绑定与自定义Shell App Title View一起工作- Xamarin Forms

cqoc49vn  于 2023-05-21  发布在  Shell
关注(0)|答案(1)|浏览(257)

背景

我正在尝试为我的Xamarin Forms Shell应用程序制作一个可自定义,可重用的标题视图模板。所谓“标题视图”,我指的是出现在页面顶部的标题栏。**我已经基本实现了它,但我正在努力使一些绑定工作。
这个想法很简单。我想最终得到这样的东西,一个非常标准的标题栏模板,我可以在逐页的基础上填充空白:

我希望能够在我的任何ContentPages中创建它。我已经做了调整,这样我只需要告诉按钮它们的图像源和命令是什么,其余的将为我格式化:

<ContentPage
    x:DataType="viewmodels:MyViewModel"
    ... >

    <Shell.TitleView>
        
        <tv:CustomAppShellTitleView>
                        
            <tv:CustomAppShellTitleView.LeftButton>
                <tv:TitleViewButtonInfo Command="{Binding SomeCommandHere}" Source="{StaticResource SomeIconHere}" />
            </tv:CustomAppShellTitleView.LeftButton>

            <tv:CustomAppShellTitleView.Title>SomeTitle</tv:CustomAppShellTitleView.Title>

            <tv:CustomAppShellTitleView.RightButton1>
                <tv:TitleViewButtonInfo Command="{Binding SomeCommandHere}" Source="{StaticResource SomeIconHere}" />
            </tv:CustomAppShellTitleView.RightButton1>

            <tv:CustomAppShellTitleView.RightButton2>
                <tv:TitleViewButtonInfo Command="{Binding SomeCommandHere}" Source="{StaticResource SomeIconHere}" />
            </tv:CustomAppShellTitleView.RightButton2>

            <tv:CustomAppShellTitleView.RightButton3>
                <tv:TitleViewButtonInfo Command="{Binding SomeCommandHere}" Source="{StaticResource SomeIconHere}" />
            </tv:CustomAppShellTitleView.RightButton3>
            
        </tv:CustomAppShellTitleView>
        
    </Shell.TitleView>
...

我的所有ContentPages都使用视图模型,我想使用它们的数据来配置标题栏行为(例如,按钮命令,可见性等)。

TitleViewButtonInfo

你会注意到我使用了一个叫做TitleViewButtonInfo的东西。我想既然我有4个可能的按钮,我可以通过将它们的Bindable Properties放在一个漂亮的小对象中来减少代码重复,就像这样:

using System.Windows.Input;
using Xamarin.Forms;

namespace MyNamespace.Views.TitleViews
{
    public class TitleViewButtonInfo : BindableObject
    {
        public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(TitleViewButtonInfo), default(ICommand));
        public ICommand Command
        {
            get => (ICommand)GetValue(CommandProperty);
            set => SetValue(CommandProperty, value);
        }

        public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ImageSource), typeof(TitleViewButtonInfo), default(ImageSource));
        public ImageSource Source
        {
            get => (ImageSource)GetValue(SourceProperty);
            set
            {
                SetValue(SourceProperty, value);
                OnPropertyChanged(nameof(IsVisible));
            }
        }

        public bool IsVisible => Source != null;
    }
}

CustomAppShellTitleView

最后,我得到了我的 actualCustomAppShellTitleView。为了简单起见,我将省略右侧的按钮:

XAML

<ContentView
    x:Class="MyNamespace.Views.TitleViews.CustomAppShellTitleView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MyNamespace.Views.TitleViews"
    x:Name="customAppShellTitleView"
    x:DataType="local:CustomAppShellTitleView">
    <ContentView.Resources>
        <ResourceDictionary>
            <x:Double x:Key="ButtonSize">32</x:Double>
        </ResourceDictionary>
    </ContentView.Resources>
    <ContentView.Content>
        <Grid
            Padding="0,0,10,0"
            RowSpacing="0"
            VerticalOptions="CenterAndExpand">

            <Grid.ColumnDefinitions>
                <!--  Left-side Button  -->
                <ColumnDefinition Width="Auto" />
                <!--  Title  -->
                <ColumnDefinition Width="*" />
                <!--  Right-side Buttons  -->
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <!--  Left-side Button  -->
            <ImageButton
                Grid.Column="0"
                Command="{Binding LeftButton.Command, Source={x:Reference Name=customAppShellTitleView}}"
                HeightRequest="{StaticResource ButtonSize}"
                HorizontalOptions="Start"
                IsVisible="{Binding LeftButton.IsVisible, Source={x:Reference Name=customAppShellTitleView}}"
                Source="{Binding LeftButton.Source, Source={x:Reference Name=customAppShellTitleView}}"
                WidthRequest="{StaticResource ButtonSize}" />

            <!--  Title  -->
            <Label
                Grid.Column="1"
                HorizontalOptions="StartAndExpand"
                LineBreakMode="TailTruncation"
                MaxLines="1"
                Text="{Binding Title, Source={x:Reference Name=customAppShellTitleView}}"
                VerticalOptions="CenterAndExpand" />

            <!--  Right-side Buttons  -->
            <StackLayout
                Grid.Column="2"
                Orientation="Horizontal"
                Spacing="10">
                <!--  Buttons 1, 2 and 3 here -->
            </StackLayout>
        </Grid>
    </ContentView.Content>
</ContentView>

代码隐藏

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace MyNamespace.Views.TitleViews
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class CustomAppShellTitleView : ContentView
    {
        // LeftButton
        public static readonly BindableProperty LeftButtonProperty = BindableProperty.Create(
            nameof(LeftButton),
            typeof(TitleViewButtonInfo),
            typeof(CustomAppShellTitleView),
            new TitleViewButtonInfo());

        public TitleViewButtonInfo LeftButton
        {
            get => (TitleViewButtonInfo)GetValue(LeftButtonProperty);
            set => SetValue(LeftButtonProperty, value);
        }

        // Same thing for RightButton1, 2 and 3

        #region Title Property
        public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(CustomAppShellTitleView), default(string));
        
        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set => SetValue(TitleProperty, value);
        }
        #endregion

        public CustomAppShellTitleView()
        {
            InitializeComponent();
            BindingContext = this;
        }
    }
}

问题

这种设置似乎在大部分情况下都有效。一切看起来都很好。标题和按钮图像源在运行时被正确设置,主要是因为它们是静态值,在我的页面的XAML中设置Shell.TitleView时就存在了。但是,绑定是问题所在。例如,按钮在被按下时什么也不做,尽管在我的视图模型中它们的Command属性绑定到一些ICommand。当我将此命令绑定到视图中的常规按钮时,它可以正常工作,因此这不是由于XAML和视图模型之间的某些不匹配。
很明显,这里有一些基本的东西我不明白。

What I've Tried

我在TitleViewButtonInfo.CommandProperty和视图模型的构造函数中设置了断点,命令被分配到这里。由于视图是在视图模型被设置为BindingContext(甚至是现有的)之前初始化的,所以这是有意义的--但是一旦视图模型实际设置了CommandProperty的setter,它就再也不会被命中了。显然,第一次命中时,值将为null,因为视图模型尚未初始化。因此,即使在我的视图模型中设置了ICommand *,title视图也听不到它。我尝试在已绑定的命令上触发OnPropertyChanged,但不起作用。
有没有什么方法可以让它在我的视图模型中设置命令时听到?必须有一些明显的,我做错了这样的标题视图的属性只能设置 * 一次 *,永远不会再次。

cnjp1d6j

cnjp1d6j1#

TitleViewButtonInfo的BindingContext不是页的ViewModel。所以你可以为它设置绑定源。下面是一个例子:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         ...
         x:Name="this">

<Shell.TitleView>
    <tv:CustomAppShellTitleView>
        <tv:CustomAppShellTitleView.LeftButton>
            <tv:TitleViewButtonInfo Command="{Binding BindingContext.LeftButtonCommand,Source={x:Reference this}}" ... />
        </tv:CustomAppShellTitleView.LeftButton>

希望能成功。

相关问题