Запретить DataTemplate перерабатывать привязки в .net MAUI

У меня есть модель 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)

  1. Нажмите кнопку с командой AddFolder и введите имя, например. Test1 во всплывающем окне.
  2. Удерживая только что созданную TestCard, отобразится всплывающее окно с Test1. Нажмите кнопку удаления.
  3. Повторить 1)
  4. Повторите 2), но имя, отображаемое во всплывающем окне, и переданная ссылка относятся к Test1 TestItem.

Как мы можем воспроизвести эту проблему? Не могли бы вы опубликовать этапы воспроизведения этой проблемы и дополнительные фрагменты кода об этой проблеме (например, TestCard.cs , BrowsingItemDataTemplateSelector.cs и MainViewModel.cs)?

Jessie Zhang -MSFT 28.05.2024 05:10

Хорошо, я сделаю это, когда у меня будет возможность

Jan Jonáš 28.05.2024 07:04

И не могли бы вы опубликовать код IBaseItem.cs и GoldMenuAddButton?

Jessie Zhang -MSFT 29.05.2024 09:01

добавлю примерно через 7 часов

Jan Jonáš 29.05.2024 12:13

Какие коды у GoldMenuButton, FolderCard и FolderItem? Если вам это удобно, не могли бы вы поделиться Минимальным воспроизводимым примером здесь, чтобы мы могли вам лучше помочь?

Jessie Zhang -MSFT 30.05.2024 11:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
121
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема, с которой вы столкнулись с переработанными ссылками в 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.

Jan Jonáš 28.05.2024 11:08

Кроме того, без оскорблений, но кажется, что этот ответ создан ИИ..

Jan Jonáš 29.05.2024 17:39

Вы пробовали?

Abhishek khatri 30.05.2024 06:32
Ответ принят как подходящий

Проблема связана с тем, как поведение обрабатывает привязку; похоже, что та же проблема не возникает при использовании обычного GestureRecouncer. Судя по всему, создатель этой функции сожалеет об этом по сей день.

Другие вопросы по теме