我可以使用 BindableProperty 在我的 Xamarin ContentView 自定义控件中禁用 Button 吗?

Can I disable a Button in my Xamarin ContentView custom control using a BindableProperty?

我希望能够在我的 Xamarin.Forms 应用中禁用自定义 header 中的一个 ContentView 按钮,这样我就可以在离开之前警告用户他们有未保存的数据包含它的 ContentPage。

我为 HeaderView 创建了一个 BindableProperty,我的包含页面绑定了它的视图模型。我受到这篇文章的启发:Add Custom Controls with Binding Properties to Your Xamarin.Forms App ContentView 的 XAML 在这里

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             x:Class="HeaderView"
             x:Name="HeaderRoot">


       <Grid RowSpacing="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition Height="5"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Padding="20,20,20,0" ColumnSpacing="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="1.3*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

        <StackLayout Grid.Column="0">
                <FlexLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
                   <ImageButton x:Name="BackButton" TranslationY="-20" TranslationX="-20" BackgroundColor="Black"  HeightRequest="80" WidthRequest="80"  Source="angleleft.png" Padding="0,0,0,0" Margin="0,0,0,0" Clicked="OnBackClicked" IsEnabled="{Binding Source={x:Reference HeaderRoot}, Path=BindingContext.IsEnabled}" />
                </FlexLayout>
        </StackLayout>

        <FlexLayout Grid.Column="1" JustifyContent="Center" AlignItems="Center">
               <FlexLayout.TranslationY>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="Android" Value="-15"/>
                    <On Platform="iOS" Value="0"/>
                </OnPlatform>
            </FlexLayout.TranslationY>
           <Label x:Name="TitleLabel" FontFamily="{StaticResource TitleFont}" TextColor="White" FontSize="50" />
        </FlexLayout>

           </Grid>
    </Grid>
</ContentView>

ContentView背后的相关代码在这里

    public partial class HeaderView : ContentView
    {
        public HeaderView()
        {
            InitializeComponent();

            if (DesignMode.IsDesignModeEnabled)
                return; // Avoid rendering exception in the editor.

            //Argument is null because the Header does not need navigation information.
            BindingContext = new HeaderViewModel(null);
        }
        public static BindableProperty CanNavigateProperty = 
                                 BindableProperty.Create(
            propertyName: "CanNavigate",
            returnType: typeof(bool),
            declaringType: typeof(HeaderView),
            defaultValue: true,
            defaultBindingMode: BindingMode.TwoWay,
            propertyChanged: HandleCanNavigatePropertyChanged);

        private static void HandleCanNavigatePropertyChanged(
            BindableObject bindable, 
            object oldValue, 
            object newValue)
        {
            HeaderView targetView = (HeaderView)bindable;

            if (targetView != null)
                targetView.BackButton.IsEnabled = (bool)newValue;
        }

//...

        public bool CanNavigate
        {
            get
            {
                return (bool)base.GetValue(CanNavigateProperty);
            }
            set
            {
                if(this.CanNavigate != value)
                {
                    base.SetValue(CanNavigateProperty, value);
                }
            }
        }


        protected void OnBackClicked(object sender, EventArgs e)
        {
            if (CanNavigate)
            {
                this.Navigation.PopAsync();
            }
        }

    }

编辑。 视图模型非常简单。

    public class HeaderViewModel : ViewModelBase
    {
        public HeaderViewModel(INavigationService navigationService)
            : base(navigationService)
        {
            UserDTO dto = (Prism.PrismApplicationBase.Current as App).CurrentUser;
            UserName = string.Format("{0} {1}", dto.FirstName, dto.LastName);
        }

        private string userName;
        public string UserName 
        { 
            get
            {
                return userName; 
            }
            set
            {
                    SetProperty<string>(ref userName, value);
            }
        }
    }

然后我在包含页面中有以下标记

<mdaViews:HeaderView CanNavigate="{Binding IsSaved}" Title="COLLECTIONS" Grid.Row="0" />

我已验证 属性、IsSaved 确实更改了它的值。绑定在标签文本中使用时有效。

当我更改 IsSaved 属性 的值时,标签将按预期从 "true" 更改为 "false"。然而,这个相同的绑定似乎并没有改变我的自定义 header 中的 CanNavigate 值。调试时,OnBackClicked 处理程序始终显示 CanNavigate == true 的值。从未输入 属性Changed 事件 HandleCanNavigatePropertyChanged。如果我应该明确地调用它,我不知道该怎么做。如果我遗漏了什么或使用了错误的方法,我想知道。

Edit SO 中的一些帖子似乎暗示将 BindingContext 设置为视图模型可能是问题的一部分。

下面是这样一个例子: 。我不确定我应该如何处理视图模型,因为它按预期工作。

我在@Krumelur 的评论 "The page using the ContentView sets its BindingContext which will then be used for all child elements, including the custom ContentView. If you set the context in the content view again, you will overwrite it." 中找到了答案,他正在回复此答案

当我在 ContentView 中设置 BindingContext 时,我覆盖了父页面中可用的所有内容。为了解决这个问题,我将所有 ViewModel 处理都移到了后面的代码中,并删除了我设置 BindingContext 的代码。