У меня есть модель TestItem : IBaseItem, индивидуальное управление TestCard которой x:DataType = "TestItem".
Еще у меня есть MainViewModel, у которого есть ObservableCollection<IBaseItem> BrowsingItems
В MainPage есть словарь ресурсов с DataTemplates, один из которых содержит пользовательский TestCard и его TouchBehaviour из набора инструментов сообщества. Данные из BrowsingItems привязаны к 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>
Первая команда выполняется при нажатии мыши на TestCard. Он отображает всплывающее окно с Name элемента и набор команд на выбор (перечисление), которые я затем планирую выполнить обратно в MainViewModel.
Вторая команда предназначена для добавления новых элементов TestItem на основе имени, выбранного в другом всплывающем окне. Команда используется в привязке к другой кнопке на странице.
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.
Есть идеи, как остановить переработку старых ссылок или как обновить привязки в DataTemplate?
РЕДАКТИРОВАТЬ 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;
}
Селектор BrowsingItemTemplateSelector
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;
}
}
TestCard.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);
}
}
ИБасеитем:
public interface IBaseItem
{
public int ID { get;}
}
ЗолотоМенюДобавитьКнопку:
<Border xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
x:Class = "TryMe.Resources.Controls.GoldMenuAddButton"
Stroke = "{StaticResource AppGold}"
BackgroundColor = "{StaticResource AppDarkPrimary}"
Padding = "5"
StrokeThickness = "3">
<Border.StrokeShape>
<RoundRectangle CornerRadius = "15"></RoundRectangle>
</Border.StrokeShape>
<Border.GestureRecognizers>
<TapGestureRecognizer x:Name = "tapRecognizer"
NumberOfTapsRequired = "1" />
</Border.GestureRecognizers>
<Grid ColumnDefinitions = "*,*" ColumnSpacing = "25" Padding = "1" >
<Image x:Name = "image" WidthRequest = "45"
Aspect = "AspectFit"
HorizontalOptions = "Center"
VerticalOptions = "Center"
Grid.Column = "0" >
</Image>
<Image
Aspect = "AspectFit"
Source = "add_gold.svg"
HorizontalOptions = "Center"
VerticalOptions = "Center"
Grid.Column = "1">
</Image>
</Grid>
</Border>
Код позади:
public partial class GoldMenuAddButton : Border
{
public GoldMenuAddButton()
{
InitializeComponent();
}
public BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(ImageSource), typeof(GoldMenuAddButton)
, propertyChanged: (bindable, oldValue, newValue) =>
{
var control = bindable as GoldMenuAddButton;
control.image.Source = newValue as ImageSource;
});
public static readonly BindableProperty ClickedProperty = BindableProperty.Create(nameof(Clicked), typeof(ICommand), typeof(GoldMenuAddButton)
, propertyChanged: (bindable, oldValue, newValue) =>
{
var control = bindable as GoldMenuAddButton;
control.tapRecognizer.Command = newValue as ICommand; ;
});
public ICommand Clicked
{
get => GetValue(ClickedProperty) as ICommand;
set => SetValue(ClickedProperty, value);
}
public ImageSource Image
{
get => GetValue(ImageProperty) as ImageSource;
set => SetValue(ImageProperty, value);
}
}
ДЕЙСТВИЯ ПО ВОСПРОИЗВЕДЕНИЮ: в1)
Хорошо, я сделаю это, когда у меня будет возможность
И не могли бы вы опубликовать код IBaseItem.cs и GoldMenuAddButton?
добавлю примерно через 7 часов
Какие коды у GoldMenuButton, FolderCard и FolderItem? Если вам это удобно, не могли бы вы поделиться Минимальным воспроизводимым примером здесь, чтобы мы могли вам лучше помочь?





Проблема, с которой вы столкнулись с переработанными ссылками в 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;
}
}
Спасибо
Я не думаю, что создание наблюдаемых свойств поможет, равно как и сброс BindingContext всплывающего окна. Проблема в том, что ссылка TestItem передается команде ShowTestContextPopup. Мне нужен способ обновить привязки в DataTemplate.
Кроме того, без оскорблений, но кажется, что этот ответ создан ИИ..
Вы пробовали?
Проблема связана с тем, как поведение обрабатывает привязку; похоже, что та же проблема не возникает при использовании обычного GestureRecouncer. Судя по всему, создатель этой функции сожалеет об этом по сей день.
Как мы можем воспроизвести эту проблему? Не могли бы вы опубликовать этапы воспроизведения этой проблемы и дополнительные фрагменты кода об этой проблеме (например,
TestCard.cs,BrowsingItemDataTemplateSelector.csиMainViewModel.cs)?