Reputation: 1502546
This question is about two MAUI controls (Switch
and ListView
) - I'm asking about them both in the same question as I'm expecting the root cause of the problem to be the same for both controls. It's entirely possible that they're different problems that just share some common symptoms though. (CollectionView
has similar issues, but other confounding factors that make it trickier to demonstrate.)
I'm using 2-way data binding in my MAUI app: changes to the data can either come directly from the user, or from a background polling task that checks whether the canonical data has been changed elsewhere. The problem I'm facing is that changes to the view model are not visually propagated to the Switch.IsToggled
and ListView.SelectedItem
properties, even though the controls do raise events showing that they've "noticed" the property changes. Other controls (e.g. Label
and Checkbox
) are visually updated, indicating that the view model notification is working fine and the UI itself is generally healthy.
Build environment: Visual Studio 2022 17.2.0 preview 2.1
App environment: Android, either emulator "Pixel 5 - API 30" or a real Pixel 6
The sample code is all below, but the fundamental question is whether this a bug somewhere in my code (do I need to "tell" the controls to update themselves for some reason?) or possibly a bug in MAUI (in which case I should presumably report it)?
The sample code below can be added directly a "File new project" MAUI app (with a name of "MauiPlayground" to use the same namespaces), or it's all available from my demo code repo. Each example is independent of the other - you can try just one. (Then update App.cs
to set MainPage
to the right example.)
Both examples have a very simple situation: a control with two-way binding to a view-model, and a button that updates the view-model property (to simulate "the data has been modified elsewhere" in the real app). In both cases, the control remains unchanged visually.
Note that I've specified {Binding ..., Mode=TwoWay}
in both cases, even though that's the default for those properties, just to be super-clear that that isn't the problem.
The ViewModelBase
code is shared by both examples, and is simply a convenient way of raising INotifyPropertyChanged.PropertyChanged
without any extra dependencies:
ViewModelBase.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MauiPlayground;
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool SetProperty<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return true;
}
}
SwitchDemo.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiPlayground.SwitchDemo">
<StackLayout>
<Label Text="Switch binding demo" />
<HorizontalStackLayout>
<Switch x:Name="switchControl"
IsToggled="{Binding Toggled, Mode=TwoWay}"
Toggled="Toggled" />
<CheckBox IsChecked="{Binding Toggled, Mode=TwoWay}" />
<Label Text="{Binding Toggled}" />
</HorizontalStackLayout>
<Button Text="Toggle" Clicked="Toggle" />
<Label x:Name="manualLabel1" Text="Value set in button click handler" />
<Label x:Name="manualLabel2" Text="Value set in toggled handler" />
</StackLayout>
</ContentPage>
SwitchDemo.cs
namespace MauiPlayground;
public partial class SwitchDemo : ContentPage
{
public SwitchDemo()
{
InitializeComponent();
BindingContext = new ViewModel();
}
private void Toggle(object sender, EventArgs e)
{
var vm = (ViewModel)BindingContext;
vm.Toggled = !vm.Toggled;
manualLabel1.Text = $"Set in click handler: {switchControl.IsToggled}";
}
private void Toggled(object sender, ToggledEventArgs e) =>
manualLabel2.Text = $"Set in toggled handler: {switchControl.IsToggled}";
private class ViewModel : ViewModelBase
{
private bool toggled;
public bool Toggled
{
get => toggled;
set => SetProperty(ref toggled, value);
}
}
}
Screenshot of the emulator after clicking on the "Toggle" button, which updates the view-model:
Notes:
switch.IsToggled
is trueSwitch.Toggled
event has been raisedSwitch
itself has not changed visible stateClicking on the Switch
control directly does visually toggle it.
ListViewDemo.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiPlayground.ListViewDemo">
<StackLayout>
<Label Text="ListView binding demo" />
<ListView x:Name="listView" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
VerticalOptions="Start"
ItemSelected="ItemSelected"/>
<Label Text="{Binding SelectedItem}" />
<Button Text="Toggle" Clicked="Toggle" />
<Label x:Name="manualLabel1" Text="Text set in button click handler" />
<Label x:Name="manualLabel2" Text="Text set in item selected handler" />
</StackLayout>
</ContentPage>
ListViewDemo.cs
namespace MauiPlayground;
public partial class ListViewDemo : ContentPage
{
public ListViewDemo()
{
InitializeComponent();
BindingContext = new ViewModel();
}
private void Toggle(object sender, EventArgs e)
{
var vm = (ViewModel)BindingContext;
vm.SelectedItem = vm.SelectedItem == "First" ? "Second" : "First";
manualLabel1.Text = $"Set in click handler: {listView.SelectedItem}";
}
private void ItemSelected(object sender, EventArgs e) =>
manualLabel2.Text = $"Set in item selected handler: {listView.SelectedItem}";
private class ViewModel : ViewModelBase
{
public List<string> Items { get; } = new List<string> { "First", "Second" };
private string selectedItem = "First";
public string SelectedItem
{
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
}
}
Screenshot of the emulator after clicking on the "Toggle" button, which updates the view-model:
Notes:
listView.SelectedItem
has the new valueListView.ItemSelected
event has been raisedListView
itself appears to have no selected itemInterestingly, the list view does actually change appearance: before clicking on the button, the first item is visually selected (in orange). Selecting an item from the list manually updates all the properties, but we don't see the selected item in orange.
Upvotes: 29
Views: 8206
Reputation: 813
The issue with Switch.IsToggled
is known and has been fixed but won't be available until the next RC is published (6.0.300-rc.1).
I haven't found any reported issues regarding the ListView
problem but I was able to reproduce it. It seems to be caused by the default ViewCell
created for each item. It can be fixed by specifying a custom ListView.ItemTemplate
like this:
<ListView x:Name="listView" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
VerticalOptions="Start"
ItemSelected="ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Upvotes: 17
Reputation: 2188
These both may be bugs with the currently released version of MAUI.
This bug was recently posted and there is already a fix for the Switch to address this issue.
Upvotes: 5