是否可以将 MenuItem.IsEnabled 属性 绑定到不同的上下文?

Is it possible to bind the MenuItem.IsEnabled property to a different Context?

我有一个 ListView 绑定到对象集合(在本例中称为 Users),模板包含一个ContextActions 菜单。需要启用或禁用其中一个菜单项,具体取决于与视图中的项目没有直接关系的条件(无论是否存在与某种外围设备的蓝牙连接)。我现在正在做的是在 TemplatedItems 属性 中迭代 Cells 并设置 IsEnabled[=每个 33=]。

这是 ListView 的 XAML,精简到对我的问题重要的部分:

<ListView ItemsSource="{Binding .}" ItemTapped="item_Tap">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Label}">
                <TextCell.ContextActions>
                    <MenuItem
                        Text="Copy to other device"
                        ClassId="copyMenuItem"
                        Clicked="copyMenuItem_Click" />
                </TextCell.ContextActions>
            </TextCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

下面是我现在设置 属性 值的方式:

foreach (Cell cell in usersListView.TemplatedItems)
{
    foreach (MenuItem item in cell.ContextActions)
    {
        if ("copyMenuItem" == item.ClassId)
        {
            item.IsEnabled = isBluetoothConnected;
        }
    }
}

可以,但我不喜欢。这显然不符合数据绑定视图的整体理念。我宁愿有一个布尔值,我可以绑定到 IsEnabled 属性,但从对象设计的角度来看,将其添加到用户 对象;它与 class 的含义(代表登录帐户)无关。我想过将 User 包装在某个本地 class 中,只是为了将这个布尔值 属性 粘贴到它上面,但这也感觉很奇怪,因为该值将始终是集合中的每个项目都相同。有没有其他方法可以绑定 MenuItem.IsEnabled 属性?

使用relative binding

  1. 准备好您的视图模型 class,继承 INotifyPropertyChanged 或您的 BaseViewModel
    public class YourViewModel : INotifyPropertyChanged
    {
        private string isBluetoothConnected;

        public string IsBluetoothConnected
        {
            get => isBluetoothConnected;
            set => SetProperty(ref isBluetoothConnected, value);
        }

        public ObservableCollection<User> Users { get; private set; }
    }
  1. 给ListView添加一个名称以供参考,并在MenuItem中应用相对绑定。
<ListView
    x:Name="UserListView"
    ItemsSource="{Binding Users}"
    ItemTapped="item_Tap">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Label}">
                <TextCell.ContextActions>
                    <MenuItem
                        IsEnabled="{Binding Path=BindingContext.IsBluetoothConnected, Source={x:Reference UserListView}}"
                        Text="Copy to other device"
                        ClassId="copyMenuItem"
                        Clicked="copyMenuItem_Click" />
                </TextCell.ContextActions>
            </TextCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

事实证明,BindableProperty 这种情况实际上是不可绑定的:https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/menuitem#enable-or-disable-a-menuitem-at-runtime

必须将 Command 属性 添加到 MenuItem 并分配 BindingContext,并设置其可执行性。这是我的代码的最新版本,它确实有效:

<MenuItem
    Text="Copy to other device"
    Clicked="copyMenuItem_Click"
    BindingContext="{x:Reference usersListView}"
    Command="{Binding BindingContext.CopyCommand}" />
public class UsersViewModel
{
    public Command CopyCommand { get; set; }
    public bool IsBluetoothConnected
    {
        get { return isBluetoothConnected; }
        set
        {
            isBluetoothConnected = value;
            if (CopyCommand.CanExecute(null) != value)
            {
                CopyCommand.ChangeCanExecute();
            }
        }
    }
    public ObservableCollection<User> Users { get; private set; }

    private bool isBluetoothConnected;

    public async System.Threading.Tasks.Task<int> Populate( )
    {
        CopyCommand = new Command(( ) => { return; }, ( ) => IsBluetoothConnected); // execute parameter is a no-op since I really just want the canExecute parameter
        IList<User> users = await App.DB.GetUsersAsync();
        Users = new ObservableCollection<User>(users.OrderBy(user => user.Username));
        return Users.Count;
    }
}

我对此还不是很满意;它用特定视图的关注点污染了视图模型。我将看看是否可以将 Command 与视图模型分开。但它确实实现了我的主要目标,将此 UI 实现带入数据绑定范例。