为什么MAUI中的Switch和ListView控件不支持双向绑定更新?

29
这个问题涉及到两个MAUI控件(SwitchListView),我在同一个问题中询问它们,因为我认为问题的根本原因可能是两个控件都存在的。当然,它们也可能是不同的问题,只是有一些共同的症状(CollectionView也存在类似的问题,但还有其他复杂因素使其更难以证明)。
我在我的MAUI应用程序中使用双向数据绑定:数据的更改可以直接来自用户,也可以来自后台轮询任务,检查规范数据是否在其他地方发生了更改。我面临的问题是,尽管控件确实引发了显示它们“注意到”属性更改的事件,但视图模型的更改并没有“视觉上”传播到Switch.IsToggledListView.SelectedItem属性。其他控件(例如LabelCheckbox)则会进行视觉更新,表明视图模型通知正常工作,UI本身通常健康。
构建环境:Visual Studio 2022 17.2.0 preview 2.1
应用程序环境:Android,任选模拟器“Pixel 5-API 30”或真实的Pixel 6
示例代码如下,但根本问题是:这是我代码中的某个bug吗(我需要以某种方式“告诉”控件更新自己吗?),还是可能是MAUI中的一个bug(在这种情况下,我应该报告它)?

示例代码

以下示例代码可以直接添加到“新建项目文件”MAUI应用程序中(名称为“MauiPlayground”,以使用相同的命名空间),或者您可以从我的演示代码库下载。每个示例都是独立的,您可以选择尝试一个。 (然后更新App.cs以将MainPage设置为正确的示例。)
这两个示例都有一个非常简单的情况:一个控件与视图模型双向绑定,并且一个按钮会更新视图模型属性(以模拟“在实际应用程序中已修改数据”的情况)。 在这两种情况下,控件在视觉上保持不变。
请注意,我在两种情况下都指定了{Binding…,Mode = TwoWay},即使对于那些属性来说,这是默认值,只是为了非常清楚地表明这不是问题。 ViewModelBase代码由两个示例共享,它只是一种方便的方式,可以触发INotifyPropertyChanged.PropertyChanged而不需要任何额外的依赖项: 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);
        }
    }
}

点击“切换”按钮后,模拟器截图,更新视图模型:

Switch问题演示

备注:

  • 复选框(绑定到同一个VM属性)已更新
  • 复选框旁边的标签(绑定到同一个VM属性)已更新
  • 按钮下面的标签指示switch.IsToggled为true
  • 下面的标签指示已触发Switch.Toggled事件
  • Switch本身的可见状态未改变

直接点击Switch控件会在视觉上进行切换。

ListView示例代码

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);
        }
    }
}

点击“切换”按钮后,模拟器截图更新了视图模型:

ListView问题演示

注:

  • 列表视图下方的标签(绑定到相同的VM属性)已更新
  • 按钮下方的标签指示listView.SelectedItem具有新值
  • 下方的标签指示已触发ListView.ItemSelected事件
  • ListView本身似乎没有选定项

有趣的是,列表视图实际上确实改变了外观:在单击按钮之前,第一个项目被视觉上选择(橙色)。手动从列表中选择项目会更新所有属性,但我们不会看到选定的项目呈橙色。


6
我不知道Jon Skeet会问问题!!我的假设是这只是预发布的MAUI中的一个bug。另外,据我所知,指定“TwoWay”不是必需的,因为用户交互控件会默认使用它。 - Jason
1
@Jason:是的,我意识到TwoWay是默认设置,但我想如果我明确指定它,那么就可以避免任何人猜测我需要这样做 :) 我会在问题中澄清这一点。 - Jon Skeet
在Xamarin中,我遇到了类似的问题。一些属性在SDK中没有绑定,我不得不阅读组件的源代码,然后在自定义渲染器中修改此组件。 - Dima
11
当传说询问问题时,错误将自行解决。 - Anand
问题大师班 - ChiefTwoPencils
2个回答

17
Switch.IsToggled存在问题已知,并且已被修复,但在下一个RC发布之前(6.0.300-rc.1)不会可用。 我没有发现关于ListView问题的任何报告,但我成功地复现了它。它似乎是由为每个项目创建的默认ViewCell引起的。可以通过指定自定义ListView.ItemTemplate来解决它,如下所示:
<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>

5

2
非常感谢 - 在发布之前我确实查找了现有的错误(并发现了一个关于CollectionView的错误),但错过了这两个。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接