Xamarin Forms自定义Shell标题视图ViewModel绑定失效问题求助
我正在为Xamarin Forms Shell应用制作可定制、可复用的标题栏模板。目前大部分功能已实现,但部分绑定无法正常工作。
需求很明确:实现一个标准标题栏模板,可在任意页面按需配置,效果如下:
希望在ContentPage中能像这样快速配置,只需指定按钮的图标源和命令,其余样式自动适配:
<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> ...
所有ContentPage都使用ViewModel,希望通过ViewModel中的数据控制标题栏行为(比如按钮命令、可见性等)。
TitleViewButtonInfo实现
为了减少重复代码,我把4个按钮的可绑定属性封装到TitleViewButtonInfo类中,代码如下:
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实现
以下是标题栏模板的核心实现,为简化展示,省略了右侧按钮的代码:
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> <!-- 左侧按钮 --> <ColumnDefinition Width="Auto" /> <!-- 标题 --> <ColumnDefinition Width="*" /> <!-- 右侧按钮组 --> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- 左侧按钮 --> <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}" /> <!-- 标题文本 --> <Label Grid.Column="1" HorizontalOptions="StartAndExpand" LineBreakMode="TailTruncation" MaxLines="1" Text="{Binding Title, Source={x:Reference Name=customAppShellTitleView}}" VerticalOptions="CenterAndExpand" /> <!-- 右侧按钮组 --> <StackLayout Grid.Column="2" Orientation="Horizontal" Spacing="10"> <!-- 按钮1、2、3的代码在此处 --> </StackLayout> </Grid> </ContentView.Content> </ContentView>
后台代码
using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace MyNamespace.Views.TitleViews { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class CustomAppShellTitleView : ContentView { // 左侧按钮属性 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); } // 右侧按钮1、2、3的属性定义与左侧按钮一致,此处省略 #region 标题属性 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; } } }
核心问题
目前标题和按钮图标能正常显示,但按钮绑定到ViewModel中的ICommand后点击无响应——而相同的命令绑定到普通按钮时完全正常,说明不是ViewModel与XAML的匹配问题。
已尝试的方法
我在TitleViewButtonInfo.CommandProperty和ViewModel构造函数中设置命令的位置加了断点:由于视图初始化早于ViewModel的实例化和BindingContext设置,第一次触发时Command为null是正常的,但ViewModel设置命令后,CommandProperty的setter从未再次触发。即使ViewModel中的ICommand已经赋值,标题栏也无法感知到变化。我尝试手动触发Command的OnPropertyChanged,但没有效果。
请问如何让标题栏感知到ViewModel中Command的变化?我肯定在某个基础概念上理解有误,导致标题栏的属性只能被设置一次。
内容的提问来源于stack exchange,提问作者ktom

