I have a ListView bound to a collection of objects (called User, in this case), and the template includes a ContextActions menu. One of the menu items needs to be enabled or disabled depending on a condition having nothing directly to do with the items in the view (whether or not there's a Bluetooth connection to a certain kind of peripheral). What I'm doing right now is iterating the Cell s in the TemplatedItems property and setting IsEnabled on each.
Here's the current XAML for the ListView, stripped down to the parts that matter for my question:
<ListView x:Name="usersListView" 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>
Here's how I'm setting the property values now:
foreach (Cell cell in usersListView.TemplatedItems)
{
foreach (MenuItem item in cell.ContextActions)
{
if ("copyMenuItem" == item.ClassId)
{
item.IsEnabled = isBluetoothConnected;
}
}
}
That works, but I don't like it. It's obviously out of line with the whole idea of data-bound views. I'd much rather have a boolean value that I can bind to the IsEnabled property, but it doesn't make sense from an object design point of view to add that to the User object; it has nothing to do with what that class is about (representing a login account). I thought of wrapping User in some local class that exists just to tape this boolean property onto it, but that feels strange also since each time the value is set it will be the same for every item in the collection.
I read about relative binding and tried placing the collection of User as a property of a view model class called UsersViewModel and also added a boolean property called IsBluetoothConnected to the view model, and set that class as the BindingContext of the ListView usersListView.
public class UsersViewModel
{
public bool IsBluetoothConnected = false;
public ObservableCollection<User> Users { get; private set; }
public async System.Threading.Tasks.Task<int> Populate( )
{
IList<User> users = await App.DB.GetUsersAsync();
Users = new ObservableCollection<User>(users.OrderBy(user => user.Username));
return Users.Count;
}
}
Updated XAML:
<ListView x:Name="usersListView" ItemsSource="{Binding Users}" ItemTapped="item_Tap">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Label}">
<TextCell.ContextActions>
<MenuItem
Text="Copy to other device"
Clicked="copyMenuItem_Click"
IsEnabled="{various binding syntax attempted here, see below}" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That's all well and good as far as displaying the users, that still works great, but I'm still not able to get the IsEnabled property to bind. These are the variations of MenuItem.IsEnabled binding syntax I have tried so far, all with the same result: Builds and runs without error, but even though UsersViewModel.IsBluetoothConnected is defined as false for testing, the MenuItem is enabled.
{Binding Path=BindingContext.IsBluetoothConnected, Source={x:Reference usersListView}}
{Binding BindingContext.IsBluetoothConnected, Source={x:Reference usersListView}}
{Binding Path=IsBluetoothConnected, Source={x:Reference usersListView}}
{Binding IsBluetoothConnected, Source={x:Reference usersListView}}
{Binding Path=BindingContext.IsBluetoothConnected, Source={RelativeSource AncestorType={x:Type ListView}}}
{Binding BindingContext.IsBluetoothConnected, Source={RelativeSource AncestorType={x:Type ListView}}}
{Binding Path=BindingContext.IsBluetoothConnected, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}
{Binding BindingContext.IsBluetoothConnected, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}
{Binding Path=IsBluetoothConnected, Source={RelativeSource AncestorType={x:Type ListView}}}
{Binding IsBluetoothConnected, Source={RelativeSource AncestorType={x:Type ListView}}}
{Binding Path=IsBluetoothConnected, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}
{Binding IsBluetoothConnected, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}
{Binding Path=BindingContext.IsBluetoothConnected, Source={RelativeSource Mode=FindAncestorBindingContext, AncestorType={x:Type ListView}}}
{Binding BindingContext.IsBluetoothConnected, Source={RelativeSource Mode=FindAncestorBindingContext, AncestorType={x:Type ListView}}}
{Binding Path=IsBluetoothConnected, Source={RelativeSource Mode=FindAncestorBindingContext, AncestorType={x:Type ListView}}}
{Binding IsBluetoothConnected, Source={RelativeSource Mode=FindAncestorBindingContext, AncestorType={x:Type ListView}}}
Clearly there is something I don't yet understand. What do I need to do or change to make this boolean property binding work?