UWP: VirtualizingStackPanel с DependencyProperty. Новое связывание перед обработкой предыдущего вызывает проблемы - PullRequest
0 голосов
/ 19 апреля 2020

У меня есть ListView, который связан с экземплярами ItemsSource of Album. Поскольку может быть много (> 2000) экземпляров Album, я использую (горизонтально прокручивая) VirtualizingStackPanel в качестве ItemPanel:

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <VirtualizingStackPanel Orientation="Horizontal" />
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

В качестве ItemTemplate я использую пользовательский UserControl, который привязан к альбомам в ItemsSource:

<DataTemplate x:Name="albumItem" x:DataType="local2:Album">
    <local3:AlbumControl x:Name="albumControl" Album="{x:Bind}" />
</DataTemplate>

Альбом DependencyProperty на AlbumControl выполняет некоторые действия при возникновении новой привязки:

/// <summary>
/// Gets or sets the Album assigned to the control.
/// </summary>
public Album Album
{
    get { return (Album)GetValue(AlbumProperty); }
    set { SetValue(AlbumProperty, value); }
}

/// <summary>
/// Identifies the Album dependency property.
/// </summary>
public static readonly DependencyProperty AlbumProperty = DependencyProperty.Register(nameof(Album), typeof(Album), typeof(AlbumControl), new PropertyMetadata(null, HandleAlbumChange));

private static async void HandleAlbumChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is AlbumControl control)
    {
        if (e.NewValue is Album album)
        {
            control.DoStuff(album);
            await control.DoStuffAsync(album);
            control.DoMoreStuff(album);
        }
    }
}

Поскольку ItemsPanel виртуализируется, во время прокрутки возможно, что новый альбом связывается с AlbumControl после DoStuff () и до завершения DoStuffAsyn c (). Это вызывает проблемы.

Я предполагаю, что мне нужно:

  • какая-то блокировка (DoStuff () содержит блокировку, но вы не можете блокировать асинхронные вызовы c) или
  • что-то вроде отмены обработки старого альбома и просто обработки нового альбома, или
  • может быть, он не работает при ожидаемом вызове, или
  • может быть, мои настройки полностью неверны.

Основной вопрос: как обрабатывать события привязки, которые происходят быстрее, чем обработка привязки?

==============================================

В ответ на комментарий я добавлю еще несколько деталей того, что я наблюдал.

control.DoStuff (album) очистил свойство ObservableCollection элемента AlbumControl. AlbumControl показывает эту коллекцию в ListView.

элемент управления await. DoStuffAsyn c (альбом) использовался для установки источника элемента управления изображением в AlbumControl. Альбом содержит информацию для этого источника в виде байта []. Мне нужно преобразовать это в RandomAccessStream, чтобы иметь возможность вызвать await bmp.SetSourceAsync(stream), а затем я могу использовать bmp (BitmapImage) в качестве источника. Байт [] не поддерживается файлом на диске, поэтому я не могу использовать Uri для установки источника элемента управления изображением.

После этого ожидаемого вызова я снова заполнил коллекцию ObservableCollection (control.DoMoreStuff ()). Если бы я сделал это таким образом, коллекция последнего AlbumControl содержала списочные элементы из нескольких альбомов. Указывает, что во время ожидаемого звонка был установлен новый альбом. Когда поток возвращается после окончания асинхронного c первого альбома, он повторно заполняет коллекцию, затем возвращается поток для второго альбома и также заполняет коллекцию. В результате списки элементов для обоих альбомов в коллекции. Я «решил» это, переместив Clear () после вызова метода asyn c, прямо перед этапом повторного заполнения. Но мне все еще было интересно, сделал ли я что-то сомнительное.

Я не смог найти способ предварительно вычислить BitmapImage, чтобы мне не понадобился вызов asyn c. Я продолжал получать сообщения «Marshalled for a Другой поток», означающие, что мне нужно было создать RandomAccessStream в потоке пользовательского интерфейса.

================================================= =============

Демонстрационный код доступен на https://antamista.visualstudio.com/_git/TestAlbumControl

Ответы [ 2 ]

1 голос
/ 25 апреля 2020

Я думаю, что мы можем начать изменять это двумя способами:

  1. Пусть классы Album и Song наследуют интерфейс INotifyPropertyChanged, чтобы своевременно реагировать на изменения данных.
public class Album : List<Song>,INotifyPropertyChanged
{
    // ...

    private string _title;
    /// <summary>
    /// The title of Album
    /// </summary>
    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName]string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class Song:INotifyPropertyChanged
{
    //...

    private string _title;
    /// <summary>
    /// The song title
    /// </summary>
    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName]string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Блокировка нескольких назначений внутри AlbumControl по идентификатору.
private bool _isLoading = false;

private static async void HandleAlbumChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is AlbumControl control)
    {
        if (control._isLoading)
            return;
        if (e.NewValue is Album album)
        {
            control._isLoading = true;
            // handle code
            control._isLoading = false;
        }
        else
        {
            control.Songs.Clear();
            control.albumSleeve.Source = null;
        }
    }
}

DataTemplate

<DataTemplate x:DataType="local:Song">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{x:Bind Track}" FontSize="24" Margin="8,0,16,0" VerticalAlignment="Center"/>
        <StackPanel>
            <TextBlock Text="{x:Bind Title,Mode=OneWay}" FontWeight="SemiBold" TextWrapping="Wrap"/>
            <TextBlock Text="{x:Bind Album}" FontSize="12" TextWrapping="Wrap"/>
        </StackPanel>
    </StackPanel>
</DataTemplate>

Спасибо.

0 голосов
/ 25 апреля 2020

Кажется, проблема в том, что я использую альбом, помещенный в e.NewValue из DependencyPropertyChangedEventArgs e:

if (e.NewValue is Album album)
   {
       control.DoStuff(album);
       await control.DoStuffAsync(album);
       control.DoMoreStuff(album);
   }

Если я использую control.Album, проблемы больше не отображаются. По-видимому, при использовании DependencyPropertyChangedEventArgs синхронизация отключена. Изменение кода на:

control.DoStuff(control.Album);
await control.DoStuffAsync(control.Album);
control.DoMoreStuff(control.Album);

решает проблемы синхронизации для комбинации «Альбом / Песни».

Если для демонстрационного кода вы go, вы увидите, что await control.DoStuffAsync(album) устанавливает изображение на контроле. К сожалению, синхронизация между альбомом / песнями и изображением все еще отключена. Я оставлю это сейчас, так как это не объясняется в первоначальном вопросе. Тем не менее, кажется, что использование асинхронного c метода внутри обработчика событий DependencyProperty в сочетании с VirtualizingStackPanel не является хорошей идеей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...