You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ObservableCollection项变更时ListView不更新UI问题求助

问题:ObservableCollection项更新后ListView未同步UI变化

我开发一款应用,需要下载文件并在ListView中展示,但遇到问题:ObservableCollection内的项已变更、数值已更新,但ListView未感知到变化,始终不更新UI。

已尝试的无效方案:

  • 修改ObservableCollection时调用[ListView].UpdateLayout();
  • 在ViewModel和SoundBoardItem定义中实现INotifyPropertyChanged接口。

相关代码

SoundBoardItem类

public class SoundBoardItem : INotifyPropertyChanged
{
    public string SoundName { get; set; }

    public string SoundLocation { get; set; }
    public string SoundLocationIcon { get; set; }
    public string SoundIconColor { get; set; }
    public string SoundIconVisible { get; set; }
    public string SoundIconTooltip { get; set; }

    public string SoundKeybindForecolor { get; set; }
    public string SoundKeybind { get; set; }
    public bool ProgressRingEnabled { get; set; }
    public bool ProgressRingIntermediate { get; set; }
    public int ProgressRingProgress { get; set; }
    public bool BtnEnabled { get; set; }

    public SoundBoardItem(string soundName, string soundLocation, string soundLocationIcon, bool soundIconVisible, string soundIconTooltip, string soundKeybind, bool progressRingEnabled, bool btnEnabled, bool progressRingIntermediate = true, int progressRingProgress = 100, string soundIconColor = null, string soundKeybindForecolor = null)
    {
        SoundName = soundName;
        SoundLocation = soundLocation;
        SoundLocationIcon = soundLocationIcon;
        SoundIconColor = soundIconColor;
        SoundIconTooltip = soundIconTooltip;
        SoundKeybindForecolor = soundKeybindForecolor;
        SoundKeybind = soundKeybind;
        ProgressRingEnabled = progressRingEnabled;
        ProgressRingIntermediate = progressRingIntermediate;
        ProgressRingProgress = progressRingProgress;
        BtnEnabled = btnEnabled;
        SoundIconVisible = soundIconVisible ? "Visible" : "Collapsed"; // 简化写法
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string message)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(message));
    }
}

SoundBoardItemViewmodel类

public class SoundBoardItemViewmodel
{
    public ObservableCollection<SoundBoardItem> SoundBoardItems = new();
}

页面内下载方法

private async void DownloadSoundFile(object sender, RoutedEventArgs e, string Name, string Url)
{
    if (soundBoardItemViewmodel.SoundBoardItems.Any(x => x.SoundLocation == Url || x.SoundLocation == "Downloading... (" + Url + ")")) return;

    YoutubeClient youtube = new();

    StreamManifest streamManifest;
    IStreamInfo streamInfo;
    Stream audioOnlyStreamInfo;
    string CurrentName;

    var FilePath = AppDomain.CurrentDomain.BaseDirectory + "DownloadedSounds\\";

    if (!Directory.Exists(FilePath))
        Directory.CreateDirectory(FilePath);

    System.Diagnostics.Debug.WriteLine(FilePath);

    try
    {
        streamManifest = await youtube.Videos.Streams.GetManifestAsync(Url);
        streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate();
        audioOnlyStreamInfo = await youtube.Videos.Streams.GetAsync(streamInfo);

        CurrentName = !string.IsNullOrEmpty(Name) ? Name : (await youtube.Videos.GetAsync(Url)).Title;

        SoundBoardItem soundboardItem = new(CurrentName, "Downloading... (" + Url + ")", DownloadedFileIcon, false, "Downloaded File", "None", true, false, false, 0);
        soundBoardItemViewmodel.SoundBoardItems.Add(soundboardItem);

        var ProgressHandler = new Progress<double>(p => 
        { 
            soundboardItem.ProgressRingProgress = Convert.ToInt32(p * 100); // 此处变更未同步到UI
            System.Diagnostics.Debug.WriteLine("Progress: " + Convert.ToInt32(p * 100) + "%"); 
        });

        await youtube.Videos.Streams.DownloadAsync(streamInfo, FilePath + $"video_DEBUG.{streamInfo.Container}", ProgressHandler);

        // 以下属性变更均未同步到UI
        soundboardItem.ProgressRingIntermediate = true;
        soundboardItem.ProgressRingEnabled = false;
        soundboardItem.BtnEnabled = true;
        soundboardItem.SoundIconVisible = "Visible";
        soundboardItem.SoundLocation = Url;
    }
    catch (Exception ex)
    {
        await ShellPage.g_AppMessageBox.ShowMessagebox("Download Error", "An error has occured while downloading the youtube video: " + Url + "\n\nPlease make sure that the URL is correct and links to a valid youtube video.\nPlease note that age restricted videos are not supported.\n\nException: " + ex.Message, "", "", "Okay", ContentDialogButton.Close);
        return;
    }
}

内存与UI差异截图

内存中ListView项的截图
ListView项的截图


解决方法

1. 修复SoundBoardItem的属性通知

你虽然实现了INotifyPropertyChanged接口,但所有属性都是自动属性,没有在setter中触发OnPropertyChanged事件,导致UI无法感知属性变更。需要将每个绑定属性改为带私有字段的实现,示例如下:

private string _soundName;
public string SoundName
{
    get => _soundName;
    set
    {
        if (_soundName != value)
        {
            _soundName = value;
            OnPropertyChanged(nameof(SoundName));
        }
    }
}

private int _progressRingProgress;
public int ProgressRingProgress
{
    get => _progressRingProgress;
    set
    {
        if (_progressRingProgress != value)
        {
            _progressRingProgress = value;
            OnPropertyChanged(nameof(ProgressRingProgress));
        }
    }
}

// 所有需要绑定的属性都要按此方式修改,包括ProgressRingEnabled、BtnEnabled等

2. 将ViewModel中的集合改为属性

WPF/UWP的数据绑定系统只能识别属性,而你当前的SoundBoardItems是字段,需要改为属性:

public class SoundBoardItemViewmodel
{
    public ObservableCollection<SoundBoardItem> SoundBoardItems { get; } = new ObservableCollection<SoundBoardItem>();
}

3. 确保在UI线程更新属性

Progress<double>的回调可能运行在后台线程,直接更新绑定属性会导致线程异常,也可能无法触发UI更新。需要切换到UI线程执行更新:

WPF环境

var ProgressHandler = new Progress<double>(p => 
{ 
    Application.Current.Dispatcher.Invoke(() =>
    {
        soundboardItem.ProgressRingProgress = Convert.ToInt32(p * 100);
    });
    System.Diagnostics.Debug.WriteLine("Progress: " + Convert.ToInt32(p * 100) + "%"); 
});

UWP环境

var ProgressHandler = new Progress<double>(p => 
{ 
    DispatcherQueue.TryEnqueue(() =>
    {
        soundboardItem.ProgressRingProgress = Convert.ToInt32(p * 100);
    });
    System.Diagnostics.Debug.WriteLine("Progress: " + Convert.ToInt32(p * 100) + "%"); 
});

原因说明

  • ObservableCollection仅会通知集合的增、删、移动等操作,集合内项的属性变更需要项自身通过INotifyPropertyChanged触发事件;
  • 绑定系统无法识别字段,必须使用属性才能建立正确的绑定关系;
  • 非UI线程直接更新绑定属性会破坏UI线程独占规则,导致更新失效或异常。

内容的提问来源于stack exchange,提问作者Davide_24

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 02:07:06