这个问题涉及到两个MAUI控件(
我在我的MAUI应用程序中使用双向数据绑定:数据的更改可以直接来自用户,也可以来自后台轮询任务,检查规范数据是否在其他地方发生了更改。我面临的问题是,尽管控件确实引发了显示它们“注意到”属性更改的事件,但视图模型的更改并没有“视觉上”传播到
构建环境:Visual Studio 2022 17.2.0 preview 2.1
应用程序环境:Android,任选模拟器“Pixel 5-API 30”或真实的Pixel 6
示例代码如下,但根本问题是:这是我代码中的某个bug吗(我需要以某种方式“告诉”控件更新自己吗?),还是可能是MAUI中的一个bug(在这种情况下,我应该报告它)?
这两个示例都有一个非常简单的情况:一个控件与视图模型双向绑定,并且一个按钮会更新视图模型属性(以模拟“在实际应用程序中已修改数据”的情况)。 在这两种情况下,控件在视觉上保持不变。
请注意,我在两种情况下都指定了
Switch
和ListView
),我在同一个问题中询问它们,因为我认为问题的根本原因可能是两个控件都存在的。当然,它们也可能是不同的问题,只是有一些共同的症状(CollectionView
也存在类似的问题,但还有其他复杂因素使其更难以证明)。我在我的MAUI应用程序中使用双向数据绑定:数据的更改可以直接来自用户,也可以来自后台轮询任务,检查规范数据是否在其他地方发生了更改。我面临的问题是,尽管控件确实引发了显示它们“注意到”属性更改的事件,但视图模型的更改并没有“视觉上”传播到
Switch.IsToggled
和ListView.SelectedItem
属性。其他控件(例如Label
和Checkbox
)则会进行视觉更新,表明视图模型通知正常工作,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);
}
}
}
点击“切换”按钮后,模拟器截图,更新视图模型:
备注:
- 复选框(绑定到同一个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);
}
}
}
点击“切换”按钮后,模拟器截图更新了视图模型:
注:
- 列表视图下方的标签(绑定到相同的VM属性)已更新
- 按钮下方的标签指示
listView.SelectedItem
具有新值 - 下方的标签指示已触发
ListView.ItemSelected
事件 ListView
本身似乎没有选定项
有趣的是,列表视图实际上确实改变了外观:在单击按钮之前,第一个项目被视觉上选择(橙色)。手动从列表中选择项目会更新所有属性,但我们不会看到选定的项目呈橙色。