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

摘要:
Mode=OneWayToSource}“><//删除重复的项目(如果有)SipRegistrations.Add(绑定);//临时集合;//临时收集添加新项目;privateobject_lockObj=newobject();x.SIPAccount.SIPUsername==SIPAccount.SIPUser);tempList.Add(捆绑);

解决 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,如下图。

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

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

原始(无效果):

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

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

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

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

但是本人之前使用 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<SIPAccountBinding> tempList = SipRegistrations;
    SipRegistrations = new List<SIPAccountBinding>();
    
    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 


更新:

经过在 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);
});

同步首发:

http://dlgcy.com/wpf-binding-list-update/ 

微信公众号
 

免责声明:文章转载自《解决 WPF 绑定集合后数据变动界面却不更新的问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇库会因为权限问题无法打开——selinux开启严格模式Linux 之 rsyslog下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SpringCloud微服务实战-Zuul-APIGateway(十)

本文转自:http://blog.csdn.net/qq_22841811/article/details/67637786#准备工作 1 API Gateway 2 Zuul介绍 2.1 zuul的功能 Routing in an integral part of a microservice architecture. For example, /...

android登录实现,存储数据到/data/data/包名/info.txt

1.一个简单登录界面布局代码如下: @1采用线性布局加相对布局方式 @2线性布局采用垂直排列 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:t...

解决Django-1.8.2应用部署到Apache后无法显示admin应用的CSS

  在将Django-1.8.2应用部署到Apache后,无法显示admin应用的静态内容,而在“manage.py runserver”命令下可以正常显示,主要是Apache没有找到Django静态内容的位置,修改:   操作系统:CentOS 6.5   Django版本: 1.8.2   Python版本: 2.7.10   Apache版本: 2....

c# 获取多个集合的组合结果

我的命题是多个int的list,每个集合任取一个,求组合。 最终代码为: List<int> a = new List<int>() { 1, 2, 3 }; List<int> b = new List<int>() { 1, 2, 6 }; List<int> c = new List<...

vue3逻辑分离和页面快速展示数据

逻辑分层 我们在使用vue3开发项目的时候, 如何进行【区域分层】呢???? 举一个简单的小粒子 一个区域有【查询逻辑、修改后的保存逻辑、新增逻辑、删除逻辑】 这个页面可能还有其他的区域。A区域、B区域,C区域...【有很多逻辑】 这个时候我们可以将一个区域的逻辑分离出去 将各个区域业务分开 export default { setup () {...

freeswitch的拨号规则配置

当一个呼叫在ROUTING状态下达到命中拨号规则解析器时,相应的拨号规则就开始解析了。随着解析的进行,在xml文件中的符合条件的或标签中的指令形成一个指令表,安装到这个通道中。 你可以将拨号规则文件放到conf/dialplan/default下,这个目录下的拨号规则要比enum拨号规则优先处理。这个目录下的文件执行优先级是按其文件名开头的数字排序(由小到...