解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

解决 WPF 绑定集合后数据变动界面却不更新的问题

独立观察员 2020 年 9 月 9 日

 

在 .NET Core 3.1 的 WPF 程序中打算用 ListBox 绑定显示一个集合(满足需求即可,无所谓什么类型的集合),以下是 Xaml 代码(瞟一眼就行,不是本文讨论重点):

<ListBox ItemsSource="{Binding SipRegistrations, Mode=OneWay}" SelectedValue="{Binding SelectedAccountBinding, Mode=OneWayToSource}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding SIPAccount.SIPUsername}"></TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

 

ViewModel 中有一个目标集合,当前是一个 List。

属性变动通知有两种实现方式,一是使用 PropertyChanged.Fody,二是使用自定义绑定基类 BindableBase,如下图。

 

下面主要谈论数据变动(集合增加内容)后,前台的界面却没有更新的问题。具体来说就是,List.Add 之后,第一次有效果,但后面就没效果了,界面始终只显示一条数据。

 

原始(无效果):

SipRegistrations.RemoveAll(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername); // 移除重复项(如果有的话)
SipRegistrations.Add(binding); // 添加新项

猜想是因为 List 的引用并没有变化,所以被认为该属性没有改变,进而也就没有变动通知。

其实这种需要变动通知的情况,推荐使用的是 ObservableCollection:

 

但是本人之前使用 ObservableCollection 没有成功过,反而是使用 List 是可以的,所以还是先看看用 List 怎么解决吧。

 

变体一(调试时有几率有效果):

// 添加联系人到集合并处理界面绑定;
SipRegistrations.RemoveAll(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername);
List<SIPAccountBinding> tempList = SipRegistrations;    // 临时集合;
SipRegistrations = new List<SIPAccountBinding>();       // 目标集合先置为空;
tempList.Add(binding);                                  // 临时集合添加新项;
SipRegistrations = tempList;                            // 临时集合赋值给目标集合;

变体一通过临时变量做中转,强制让目标集合(的引用)发生改变,但结果是只在调试时以很小的概率成功过。

由于这部分代码是在异步逻辑里,所以有可能是在多线程环境,而 List 不是线程安全的,所以有了以下加锁版本的变体二。

 

变体二(无效果,应该是和变体一类似):

#region 成员


/// <summary>
/// 加锁对象
/// </summary>
private object _lockObj = new object();


#endregion


// 加锁;
lock (_lockObj)
{
    // 添加联系人到集合并处理界面绑定;
    SipRegistrations.RemoveAll(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername);
    List<SIPAccountBinding> tempList = SipRegistrations;
    SipRegistrations = new List<SIPAccountBinding>();
    tempList.Add(binding);
    SipRegistrations = tempList;
}

 

加了锁还是不行(不过锁还是需要的),又想到,既然调试的时候有几率成功,那么是不是和代码运行速度有关呢?于是在目标集合置空和重新赋值之间加了个线程休眠,竟然真的可以,也就是以下的变体三。

 

变体三(有效果):

lock (_lockObj)
{
    // 添加联系人到集合并处理界面绑定;
    SipRegistrations.RemoveAll(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername);
    List tempList = SipRegistrations;
    SipRegistrations = new List();
    
    Thread.Sleep(500); // 关键代码;
    
    tempList.Add(binding);
    SipRegistrations = tempList;
}

 

好了,以上就是解决方法了。

 

接下来再尝试一下 ObservableCollection 吧:

#region 成员


/// <summary>
/// 加锁对象
/// </summary>
private object _lockObj = new object();


public ObservableCollection<SIPAccountBinding> SipRegistrations { get; set; } = new ObservableCollection<SIPAccountBinding>();


#endregion


lock (_lockObj)
{
    SipRegistrations.Remove(SipRegistrations.FirstOrDefault(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername));
    SipRegistrations.Add(binding);          // 情况一
    //SipRegistrations.Append (binding);     // 情况二
}


Console.WriteLine($" 注册联系人 [{binding.RegisteredContact}] 为 [{sipAccount.SIPUsername}].");

 

这个有两种情况(都不能成功):

情况一,使用 Add 方法,结果是执行完 Add 方法后就返回了,后面的方法不再执行(不知道为什么),界面上是有几率能添加一条。

情况二,使用 Append 方法,执行完 Append 后倒是可以继续执行后面的代码,但是界面上一条也出现不了。

 

后记:本文主要是抛砖引玉,大家有什么更好的方法,或者能解释文中所描述现象的原理,请不吝赐教。

项目地址:https://gitee.com/DLGCY_GB28181/SimpleSIPServer


更新(20200909):

经过在 https://dotnet9.com/ 站长的技术讨论群的讨论,决定还是要使用 ObservableCollection。加上在网上搜到了文章《WPF ViewModel 中对 ObservableCollection 集合操作》,所以最终代码为:

#region 成员


/// <summary>
/// 加锁对象
/// </summary>
private object _lockObj = new object();


/// <summary>
/// 集合对象
/// </summary>
public ObservableCollection<SIPAccountBinding> SipRegistrations { get; set; } = new ObservableCollection<SIPAccountBinding>();


#endregion


lock (_lockObj)
{
    ThreadPool.QueueUserWorkItem(delegate
    {
        SynchronizationContext.SetSynchronizationContext(new System.Windows.Threading.DispatcherSynchronizationContext(Application.Current.Dispatcher));
        SynchronizationContext.Current?.Post(pl =>
        {
            SipRegistrations.Remove(SipRegistrations.FirstOrDefault(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername));
            SipRegistrations.Add(binding);
        }, null);
    });
}


Console.WriteLine($" 注册联系人 [{binding.RegisteredContact}] 为 [{sipAccount.SIPUsername}].");

 

甚至还可以简化:

Application.Current.Dispatcher.Invoke(delegate
{
    SipRegistrations.Remove(SipRegistrations.FirstOrDefault(x => x.SIPAccount.SIPUsername == sipAccount.SIPUsername));
    SipRegistrations.Add(binding);
});

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用数据绑定时,WPF 会自动处理数据更改并更新界面,但是在某些情况下可能会出现界面更新有延迟的情况。这可能是因为 WPF 在处理大量数据或在 UI 线程上执行长时间操作时可能会出现性能问题。 以下是一些可能导致界面更新延迟的原因: 1. 数据量过大:如果你绑定数据集非常大,WPF 可能需要花费大量时间来更新界面。在这种情况下,你可以使用虚拟化技术,例如使用虚拟化面板或仅加载可见项的数据。 2. 长时间操作:如果你在 UI 线程上执行长时间操作,例如在数据更改事件处理程序中进行复杂计算或网络请求,WPF 可能无法及时更新界面。在这种情况下,你可以考虑将操作移到后台线程中执行,或者使用异步方法并在其完成时更新界面。 3. 控件样式过于复杂:如果你的控件样式过于复杂,例如使用复杂的模板或窗口效果,WPF 可能需要花费大量时间来更新界面。在这种情况下,你可以考虑简化控件样式,或使用基于硬件加速的效果。 4. 数据绑定周期:当绑定数据发送更改通知时,数据绑定会在下一个 UI 帧中更新界面。如果你的数据更改非常频繁,可能会导致界面更新有延迟。在这种情况下,你可以使用 `UpdateSourceTrigger` 属性将数据绑定更新模式更改为 `PropertyChanged`,以实现即时更新。 希望这些提示能帮助你解决界面更新延迟的问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值