我有一个模型
TestItem : IBaseItem
,自定义控件 TestCard
其中 x:DataType = "TestItem"
。
我还有一个
MainViewModel
,里面有一个ObservableCollection<IBaseItem> BrowsingItems
在主页中,有一个带有 DataTemplates 的资源字典,其中之一包含来自 Community Tookit 的自定义 TestCard 及其 TouchBehaviour。来自 BrowsingItems 的数据通过 DataTemplateSelector 绑定到 CollectionView,该 DataTemplateSelector 根据 IBaseItem 是否为 TestItem 进行比较。
<DataTemplate x:Key="TestTemplate"
>
<controls:TestCard >
<controls:TestCard.Behaviors>
<toolkit:TouchBehavior x:DataType="vm:TestItem"
LongPressCommand="{Binding Source={x:Reference mainPage}, Path=BindingContext.ShowTestContextPopupCommand}"
LongPressCommandParameter="{Binding Mode=TwoWay}"
LongPressDuration="750" />
</controls:TestCard.Behaviors>
</controls:TestCard>
</DataTemplate>
<DataTemplate x:Key="FolderTemplate">
<controls:FolderCard />
</DataTemplate>
<controls:BrowsingItemDataTemplateSelector x:Key="BrowsingTemplateSelector"
TemplateTest="{StaticResource TestTemplate}"
TemplateFolder="{StaticResource FolderTemplate}" />
<CollectionView Grid.Row="1"
x:Name="browsingView"
ItemsSource="{Binding BrowsingItems}"
ItemTemplate="{StaticResource BrowsingTemplateSelector}">
</CollectionView>
.
.
.
.
<Grid Grid.Column="0"
RowDefinitions="*,*"
RowSpacing="5">
<controls:GoldMenuAddButton Image="folder_gold.svg"
Clicked="{Binding AddFolderCommand}"
Grid.Row="0" />
<controls:GoldMenuAddButton Image="test_gold.svg"
Clicked="{Binding AddTestCommand}"
Grid.Row="1" />
</Grid>
在测试卡上按住鼠标时执行第一个命令。它显示一个带有项目
Name
的弹出窗口,以及一组可供选择的命令(枚举),然后我计划在 MainViewModel 中执行这些命令。
第二个命令用于根据另一个弹出窗口中选择的名称添加新的测试项目。该命令用于与页面上另一个按钮的绑定。
public partial class MainViewModel : ObservableObject
{
public MainViewModel()
{
BrowsingItems = [];
}
public ObservableCollection<IBaseItem> BrowsingItems { get; private set; }
void Sort()
{
var temp = BrowsingItems.OrderByDescending(x => x is FolderItem).ToList();
BrowsingItems.Clear();
foreach (var e in temp) BrowsingItems.Add(e);
}
[RelayCommand]
async void AddTest()
{
var popup = new CreateBrowsingItemPopup();
var name = await Application.Current.MainPage.ShowPopupAsync(popup);
if (name != null)
{
BrowsingItems.Add(new TestItem() { Name = (string)name, QuestionsAmount = 0, QuestionsAnswered = 0, Random = false });
Sort();
}
}
int folderCount = 0;
int testCount = 0;
[RelayCommand]
async void AddFolder()
{
var popup = new CreateBrowsingItemPopup();
var name = await Application.Current.MainPage.ShowPopupAsync(popup);
if (name != null)
{
BrowsingItems.Add(new FolderItem() { Name = (string)name });
Sort();
}
}
[RelayCommand]
async void ShowTestContextPopup(TestItem testItem)
{
var popup = new TestCardContextPopup(testItem.Name);
var result = await Application.Current.MainPage.ShowPopupAsync(popup);
BrowsableCommandType? testCommand = (BrowsableCommandType?) result;
if (testCommand == null) return;
switch (testCommand)
{
case BrowsableCommandType.DELETE:
BrowsingItems.Remove(testItem);
break;
}
}
}
删除后,TestCard 被移除,CollectionView 刷新。 如果我使用按钮添加另一个 TestItem,则会添加并显示适当的名称。
但是,如果我按住它,弹出窗口中显示的名称是上一个 TestItem 的名称。
它还将相同的先前引用传递给 ShowTestContextPopup 命令。
关于如何阻止它回收旧引用或如何更新数据模板上的绑定有什么想法吗?
编辑1:
这里是TestItem.cs
public class TestItem : IBaseItem
{
public int ID { get; init; }
public string Name { get; init; }
public int QuestionsAmount { get; init; }
public int QuestionsAnswered { get; init; }
public bool Random { get; init; }
public double PercentageAnswered => (QuestionsAmount==0) ? 0 : (double)QuestionsAnswered / (double)QuestionsAmount;
}
浏览项目模板选择器
public class BrowsingItemDataTemplateSelector : DataTemplateSelector
{
public DataTemplate? TemplateTest { get; set; }
public DataTemplate? TemplateFolder { get; set; }
protected override DataTemplate? OnSelectTemplate(object item, BindableObject container)
{
return ((IBaseItem)item is TestItem) ? TemplateTest : TemplateFolder;
}
}
测试卡.xaml
<Border xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:TryMe.Resources.Models"
x:Class="TryMe.Resources.Controls.TestCard"
x:DataType="vm:TestItem"
Padding="5"
StrokeThickness="3"
BackgroundColor="{StaticResource AppSecondary}"
HeightRequest="80"
Margin="12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="15"></RoundRectangle>
</Border.StrokeShape>
<Grid RowDefinitions="*,*">
<Image Source="test_black.svg" HeightRequest="35" WidthRequest="35"
Aspect="AspectFit"
HorizontalOptions="Start"
VerticalOptions="Center"></Image>
<Label Text="{Binding Name}" x:Name="nameLbl"
HorizontalOptions="Center"
VerticalOptions="Center"
FontSize="Medium"></Label>
<Label Text="{Binding QuestionsAmount}"
Grid.Row="1"
HorizontalOptions="Start"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontSize="Medium"
HeightRequest="35"
WidthRequest="35">
</Label>
<ProgressBar Margin="50,0,10,0" Grid.Row="1" Progress="{Binding PercentageAnswered}" HeightRequest="25" ProgressColor="Black" BackgroundColor="{StaticResource AppSecondary}"></ProgressBar>
</Grid>
</Border>
TestCard.xaml.cs
只是带有 InitializeComponent() 的构造函数。
ShowTestContextPopup.xaml:
<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:controls="clr-namespace:TryMe.Resources.Controls"
x:Class="TryMe.Resources.Controls.Popups.TestCardContextPopup" Color="Transparent">
<Border Stroke="{StaticResource AppGold}"
StrokeThickness="5"
WidthRequest="250"
HeightRequest="150"
StrokeShape="RoundRectangle 15"
BackgroundColor="{StaticResource AppPrimary}"
Padding="5">
<Grid BackgroundColor="{StaticResource AppPrimary}"
VerticalOptions="Center"
HorizontalOptions="Center"
RowDefinitions="45,50"
ColumnDefinitions="50,50,50,50"
ColumnSpacing="5"
RowSpacing="5"
>
<Label
x:Name="nameLbl"
TextColor="{StaticResource AppGold}"
Grid.Row="0"
Grid.ColumnSpan="6"
FontSize="26"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"></Label>
<controls:GoldMenuButton Image="edit_gold.svg"
ImagePadding="8"
Grid.Row="1"
Grid.Column="0"
x:Name="editBttn"
/>
<controls:GoldMenuButton Image="info_gold.svg"
Grid.Row="1"
Grid.Column="1"
x:Name="infoBttn"
/>
<controls:GoldMenuButton Image="trash_gold.svg"
Grid.Row="1"
Grid.Column="2"
x:Name="deleteBttn" />
<controls:GoldMenuButton Image="reset_gold.svg"
Grid.Row="1"
Grid.Column="3"
x:Name="resetBttn" />
</Grid>
</Border>
</toolkit:Popup>
public enum BrowsableCommandType { EDIT, INFO, DELETE, RESET };
public partial class TestCardContextPopup : Popup
{
public TestCardContextPopup(string name)
{
InitializeComponent();
nameLbl.Text = name;
editBttn.Clicked = new Command (() => { ReturnTestCommand(BrowsableCommandType.EDIT); });
infoBttn.Clicked = new Command(() => { ReturnTestCommand(BrowsableCommandType.INFO); });
deleteBttn.Clicked = new Command(() => { ReturnTestCommand(BrowsableCommandType.DELETE); });
resetBttn.Clicked = new Command(() => { ReturnTestCommand(BrowsableCommandType.RESET); });
}
public void ReturnTestCommand(BrowsableCommandType command)
{
Close(command);
}
}
重现步骤: v1)
您在 CollectionView 中回收引用时遇到的问题可能是由于 MAUI 框架出于性能原因重用元素的方式造成的。当您删除一项并添加新项时,由于视图回收,旧视图元素可能仍保留以前的数据上下文。
要解决此问题,请确保数据绑定正确刷新,并且视图元素在数据上下文更改时适当更新其绑定。以下是一些有助于解决此问题的建议:
使用可观察属性: 确保 TestItem 中的属性实现 INotifyPropertyChanged 以通知视图更改。
public class TestItem : IBaseItem, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id;
public int ID
{
get => id;
init
{
id = value;
OnPropertyChanged();
}
}
private string name;
public string Name
{
get => name;
init
{
name = value;
OnPropertyChanged();
}
}
private int questionsAmount;
public int QuestionsAmount
{
get => questionsAmount;
init
{
questionsAmount = value;
OnPropertyChanged();
}
}
private int questionsAnswered;
public int QuestionsAnswered
{
get => questionsAnswered;
init
{
questionsAnswered = value;
OnPropertyChanged();
}
}
private bool random;
public bool Random
{
get => random;
init
{
random = value;
OnPropertyChanged();
}
}
public double PercentageAnswered => (QuestionsAmount == 0) ? 0 : (double)QuestionsAnswered / QuestionsAmount;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
确保绑定上下文设置正确: 在 XAML 中设置数据上下文时,请确保正确更新 BindingContext,尤其是在重用视图时。
显式更新绑定: 您可能需要强制视图刷新其绑定上下文。一种方法是在重新分配视图之前将其 BindingContext 设置为 null。
避免数据上下文缓存: 确保 DataTemplate 不缓存旧的数据上下文。您可以通过确保每次重用该项目时正确设置 BindingContext 来实现此目的。 像这样:
async void ShowTestContextPopup(TestItem testItem)
{
var popup = new TestCardContextPopup(testItem.Name);
popup.BindingContext = null; // Clear the previous context
popup.BindingContext = testItem; // Set the new context
var result = await Application.Current.MainPage.ShowPopupAsync(popup);
BrowsableCommandType? testCommand = (BrowsableCommandType?)result;
if (testCommand == null) return;
switch (testCommand)
{
case BrowsableCommandType.DELETE:
BrowsingItems.Remove(testItem);
break;
}
}
谢谢