<
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);
});
责任编辑: