添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

独立观察员 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 

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

原创文章,转载请注明: 转载自 独立观察员(dlgcy.com)

本文链接地址: [解决 WPF 绑定集合后数据变动界面却不更新的问题](https://dlgcy.com/wpf-binding-list-update/)

关注微信公众号 独立观察员博客(DLGCY_BLOG) 第一时间获取最新文章

  • 『笔记分享』GridView 自定义分页 - 109,227 views
  • 使用 GB28181.Solution + ZLMediaKit + MediaServerUI 进行摄像头推流和播放 - 31,296 views
  • Windows端最稳定的DLNA音乐播放器推荐 - 23,198 views
  • 几十款 WPF 控件 – UI 库,总有一款适合你 - 22,528 views
  • 安卓手机Google账户管理程序无法添加现有帐号的问题 - 21,505 views
  • 使用Git Extensions简单入门Git - 18,835 views
  • C#的WPF中的Image控件中载入图片的方法 - 14,980 views
  • 已安装好的系统(MBR常规启动)如何改成UEFI启动? - 12,647 views
  • 使用腾讯微证券入门可转债打新 - 12,344 views
  • 自用WordPress插件推荐 - 11,605 views
  • SEO插件All in One SEO Pack新版本&汉化下载 - 11,418 views
  • 下载中转加速器 VPSDownloader.NET(.NET Core 程序部署到 Linux 系统) - 11,261 views
  • WPF 用户控件翻转与内部的内容控件反翻转 - 2周前
  • 【编辑】解决 Wpf TabControl 在所有选项卡上仅创建一个视图 的问题 - 3周前
  • 使用通用附加属性来减少 WPF 元素自定义样式的多余代码 - 4周前
  • WPF 中某元素执行旋转动画时另一元素如何进行跟随 - 2月前
  • 几十款 WPF 控件 – UI 库,总有一款适合你 - 2月前
  • Prism 中如何判断界面当前是否显示 - 3月前
  • 傲梅分区助手扩容C盘后进不了系统的解决方法 - 4月前
  • WPF 从 用户控件 到 自定义控件 - 4月前
  • 图片标注工具 labelme 中的 AI 多边形(AI-Polygon)如何使用 - 12月前
  • OxyPlot.WPF 公共属性一览 - 3年前
  • 解决转发新浪博客插件wp2sinablog修改文章后重新发布的问题 - 10年前
  • [.NET]控制只启动单个指定外部程序 - 5年前
  • Unity容器依赖注入之属性注入使用备忘 - 6年前
  • 【编辑】手机病毒“XX神器”分析 - 10年前
  • 傲梅分区助手扩容C盘后进不了系统的解决方法 - 4月前
  • 『独立观察员』个人观影 - 10年前
  •