TabControl WPF 问题中与 SelectedItem 的异步绑定
Posted
技术标签:
【中文标题】TabControl WPF 问题中与 SelectedItem 的异步绑定【英文标题】:Async binding to SelectedItem in TabControl WPF issues 【发布时间】:2016-06-04 14:25:19 【问题描述】:我有一个带有标签的面板。我的这个面板的视图模型包含ObservableCollection
的选项卡视图模型,以及选定选项卡的属性。
当某些操作请求聚焦一个选项卡或创建一个新选项卡时,我更改Selected
并且选项卡选择正确更改,差不多,因为内容有效,但所有标题看起来都没有被选中。
我找到了一个解决方案,将IsAsync=True
添加到我的绑定中。这解决了问题,但增加了许多新问题。
第一件事是,当我在调试模式下运行程序时,添加带有按钮的选项卡可以正常工作,选项卡会被切换并正确选择,但是当我尝试单击选项卡以选择它时出现异常
调用线程无法访问此对象,因为另一个线程拥有它。
在设置代表当前选定标签的属性时抛出:
private Tab selected;
public Tab Selected
get return Selected;
set SetProperty(ref Selected, value); // <<< here (I use prism BindableBase)
另一个问题是,当我快速切换选项卡时,可能会出现这样一种情况,即我选择了 Tab1,但它显示了 Tab2 的内容,多次切换选项卡会使事情恢复正常。
我的问题是,我该如何解决这个问题,即在 Selected
更改时选择我的标签标题(有点突出显示),而不会出现 IsAsync
导致的问题。
编辑
这是允许重现问题的代码。它使用棱镜 6.1.0
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel DockPanel.Dock="Top"
Orientation="Horizontal"
Margin="0,5"
Height="25">
<Button
Command="Binding AddNewTabCommand"
Content="New Tab"
Padding="10,0"/>
<Button
Command="Binding OtherCommand"
Content="Do nothing"
Padding="10,0"/>
</StackPanel>
<TabControl
SelectedItem="Binding Selected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True" <!--remove IsAsync to break tab header selecting-->
ItemsSource="Binding Tabs">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="Binding Name" Margin="5"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="Binding Text"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
后面的代码:
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
this.DataContext = new TabGroup();
Tab.cs
public class Tab : BindableBase
public Tab(string name, string text)
this.name = name;
this.text = text;
private string name;
public string Name
get return name;
set SetProperty(ref name, value);
private string text;
public string Text
get return text;
set SetProperty(ref text, value);
TabGroup.cs
public class TabGroup : BindableBase
private Random random;
public TabGroup()
this.random = new Random();
this.addNewTabCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(AddNewTab, () => true));
this.otherCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(Method, () => Selected != null).ObservesProperty(() => Selected));
Tabs.CollectionChanged += TabsChanged;
private void Method()
private void TabsChanged(object sender, NotifyCollectionChangedEventArgs e)
var newItems = e.NewItems?.Cast<Tab>().ToList();
if (newItems?.Any() == true)
Selected = newItems.Last();
private void AddNewTab()
Tabs.Add(new Tab(GetNextName(), GetRandomContent()));
private string GetRandomContent()
return random.Next().ToString();
private int num = 0;
private string GetNextName() => $"num++";
private Tab selected;
public Tab Selected
get return selected;
set SetProperty(ref selected, value);
public ObservableCollection<Tab> Tabs get; = new ObservableCollection<Tab>();
private readonly Lazy<DelegateCommand> addNewTabCommand;
public DelegateCommand AddNewTabCommand => addNewTabCommand.Value;
private readonly Lazy<DelegateCommand> otherCommand;
public DelegateCommand OtherCommand => otherCommand.Value;
准备这个让我弄清楚异常来自哪里。这是因为 OtherCommand 观察选定的属性。我仍然不知道如何使它正确。对我来说最重要的是让选项卡在应该选择的时候被选中,这样选定的选项卡就不会与选项卡控件显示的内容不同步。
这是一个带有此代码的 github 存储库
https://github.com/lukaszwawrzyk/TabIssue
【问题讨论】:
该错误是由从另一个线程更改绑定到 UI 的内容引起的,但在您的代码中没有证据表明这一点。请提供minimal reproducible example。 好的,我能够重现我所说的一切。我会在一分钟内输入代码。 【参考方案1】:我将专注于您的原始问题,不包括异步部分。
添加新选项卡时选项卡未正确选择的原因是因为您在CollectionChanged
事件处理程序中设置了Selected
值。引发事件会导致处理程序的顺序调用按添加顺序。由于您在构造函数中添加了处理程序,因此它将始终是第一个被调用的处理程序,重要的是,它将始终在更新TabControl
的处理程序之前被调用。因此,当您在处理程序中设置Selected
属性时,TabControl
还不“知道”集合中有这样一个选项卡。更准确地说,选项卡的标题容器尚未生成,并且无法将其标记为已选中(这会导致您缺少视觉效果),而且最终生成时也不会。 TabControl.SelectedItem
仍在更新,因此您可以看到选项卡的内容,但它也会导致之前标记为已选择的标题容器未标记,最终您最终没有明显选择选项卡。
根据您的需要,有几种方法可以解决此问题。如果添加新标签的唯一方法是通过AddNewTabCommand
,您只需修改AddNewTab
方法:
private void AddNewTab()
var tab = new Tab(GetNextName(), GetRandomContent());
Tabs.Add(tab);
Selected = tab;
在这种情况下,您不应该在CollectionChanged
处理程序中设置Selected
值,因为它会阻止PropertyChanged
在正确的时间被引发。
如果AddNewTabCommand
不是添加标签的唯一方法,我通常会创建一个专门的集合来执行所需的逻辑(这个类嵌套在TabGroup
中):
private class TabsCollection : ObservableCollection<Tab>
public TabsCollection(TabGroup owner)
this.owner = owner;
private TabGroup owner;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
base.OnCollectionChanged(e); //this will update the TabControl
var newItems = e.NewItems?.Cast<Tab>()?.ToList();
if (newItems?.Any() == true)
owner.Selected = newItems.Last();
然后只需在TabGroup
构造函数中实例化集合:
Tabs = new TabsCollection(this);
如果这种情况出现在不同的地方并且您不喜欢重复您的代码,您可以创建一个可重用的集合类:
public class MyObservableCollection<T> : ObservableCollection<T>
public event NotifyCollectionChangedEventHandler AfterCollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
base.OnCollectionChanged(e);
AfterCollectionChanged?.Invoke(this, e);
然后在您需要确保所有CollectionChanged
订阅者都已收到通知时订阅AfterCollectionChanged
。
【讨论】:
感谢您提供如此详细的回答!我有更多添加选项卡的方法,但我只是在这两种方法中都使用了第一种方法。这是如此简单和如此明显,以至于我不知道我为什么不首先这样做。现在一切似乎都很完美。【参考方案2】:当您收到错误“调用线程无法访问此对象,因为另一个线程拥有它”时。这意味着您正在尝试访问另一个并发线程上的对象。为了向您展示如何解决这个问题,我想举一个例子。首先,您必须找到每个运行时对象,例如列表框和列表视图等。 (基本上是 GUI 控件)。它们在 GUI 线程上运行。当您尝试在另一个线程(例如后台工作线程或任务线程)上运行它们时,会出现错误。所以这就是你想要做的:
//Lets say i got a listBox i want to update in realtime
//this method is for the purpose of the example running async(background)
public void method()
//get data to add to listBox1;
//listBox1.Items.Add(item); <-- gives the error
//what you want to do:
Invoke(new MethodInvoker(delegate listBox1.Items.Add(item); ));
//This invokes another thread, that we can use to access the listBox1 on.
//And this should work
希望对您有所帮助。
【讨论】:
我认为我的问题可能来自TabsChanged
方法,但我不确定除非某些命令侦听此属性,否则它不会发生。这是什么Invoke
?我在哪里可以找到它?还要注意我使用 mvvm,我不直接访问控件。此外,我不知道即使解决了异常,这是否会解决我的其他问题。以上是关于TabControl WPF 问题中与 SelectedItem 的异步绑定的主要内容,如果未能解决你的问题,请参考以下文章
DockPanel 中的 WPF4 TabControl/Grid 隐藏了 StatusBar