是否可以将 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 属性?
- 准备好您的视图模型 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; }
}
- 给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 实现带入数据绑定范例。
我有一个 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 属性?
- 准备好您的视图模型 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; }
}
- 给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 实现带入数据绑定范例。