zoukankan      html  css  js  c++  java
  • 解决 WPF 绑定集合后数据变动界面却不更新的问题

    解决 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<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/ 

     
  • 相关阅读:
    HTML5 WEB SQL
    Android-day04
    Android-day03
    Android-day02
    Android-day01
    java.lang.ClassNotFoundException: org.apache.jsp.index_jsp
    事务管理、整合Junit、整合Web、SSH整合
    AOP、AspectJ、JdbcTemplate
    Spring基础
    Hibernate对连接池的支持
  • 原文地址:https://www.cnblogs.com/weiliuhong/p/wpf-binding-list-update.html
Copyright © 2011-2022 走看看