在异步WPF TabControl的问题结合的SelectedItem [英] Async binding to SelectedItem in TabControl WPF issues

查看:404
本文介绍了在异步WPF TabControl的问题结合的SelectedItem的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个选项卡的面板。我认为此面板模型包含的ObservableCollection 视图模型选项卡,并为选定标签的属性。

I have a panel with tabs. My view model for this panel contains ObservableCollection of view models for tabs, and a property for selected tab.

在一些动作的请求集中选项卡,或创建一个新的标签,更改和标签选择更改不当,以及差不多,因为内容是有效的,但所有的头看起来没有什么选择等。

When some action requests to focus a tab, or a new tab is created, I change Selected and tab selection changes properly, well almost, because the content is valid, but all headers look like nothing is selected.

我发现了一个解决方案,说要 IsAsync = TRUE 添加到我的绑定。这解决了这个问题,但增加了许多新的问题。

I found a solution that says to add IsAsync=True to my binding. This solved the problem but added a bunch of new issues.

第一件事是,当我在调试模式下运行程序,添加标签的按钮工程确定,得到的标签交换和适当的选择,但是当我尝试点击一个标签来选择它,我得到异常

First thing is that when I run program in debug mode, adding tabs with buttons works ok, tabs get switched and selected properly but when I try to click a tab to select it I get exception

因为不同的线程拥有它调用线程不能访问该对象。

The calling thread cannot access this object because a different thread owns it.

这是在设置属性重新presenting当前所选选项抛出:

it is thrown while setting property representing currently selected tab:

private Tab selected;
public Tab Selected
{
    get { return Selected; }
    set { SetProperty(ref Selected, value); } // <<< here (I use prism BindableBase)
}

另一个问题是,当我快速切换标签,就可以得出一个情况我有TAB1选择,但它显示TAB2内容,切换标签夫妇多次被东西回去工作了。

Other problem is that when I quickly switch tabs, it can come to a situation where I have Tab1 selected but it shows content of Tab2, switching tabs couple more times gets things back to work.

我的问题是,我怎么能解决这个问题,即选择当被改变,而无需事项,assing <$ C(样突出)我的标签头$ C> IsAsync 的原因。

My question is, how can I solve this, i.e. have my tab headers selected (kind of highlighted) when Selected is changed, without having issues that assing IsAsync causes.

下面是code,使重现的问题。它采用棱镜6.1.0

Here is the code that allows to reproduce issues. It uses prism 6.1.0

MainWindow.xaml

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>

背后code:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new TabGroup();
    }
}

Tab.cs

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

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;
}

preparing这让我弄清楚哪里异常从哪里来。这是因为OtherCommand观察选定的属性。我还是不知道该如何作出正确选择。对我来说最重要的是获得标签被选择时,他们应该和这样选择的标签不会有什么标签控件显示失步。

Preparing this let me figure where does the exception come from. It is because the OtherCommand observes selected property. I still don't know how to make it right. Most important for me is to get tabs to be selected when they should be and so that selected tab won't desynchronize with what tab control shows.

下面是github上回购本code

Here is a github repo with this code

https://github.com/lukaszwawrzyk/TabIssue

推荐答案

我会专注于自己原来的问题,没有异步的部分。

I'll focus on your original problem, without the async part.

为什么添加一个新的标签时,标签不正确选择的原因是因为你设置中值 Col​​lectionChanged 事件处理程序。引发事件导致它们被加入以便处理程序的顺序调用。既然你在构造函数中添加处理器,它总是会被调用的第一位的,什么是重要的,它永远是更新的TabControl 一前调用。所以,当你在处理程序设置属性,的TabControl 还没有的知道这有集合在这样一个标签。更precisely,尚未产生用于标签的报头的容器,并作为选择它不能标记(这将导致您缺少视觉效果),而且,也不会当它最终生成可以。 TabControl.SelectedItem 还是更新,让你看到标签的内容,但它也会引起头容器$ P $的选择为无标记pviously标记,并且你最终结束与没有选项卡中选择明显

The reason why the tabs are not properly selected when adding a new tab is because you set the Selected value in the CollectionChanged event handler. Raising an event causes sequential invocation of handlers in order in which they were added. Since you add your handler in the constructor, it will always be the first one to be invoked, and what's important, it will always be invoked before the one that updates the TabControl. So when you set the Selected property in your handler, TabControl doesn't yet "know" that there's such a tab in the collection. More precisely, the header container for the tab is not yet generated, and it cannot be marked as selected (which causes the visual effect you're missing), moreover, it won't be when it's finally generated. TabControl.SelectedItem is still updated, so you see the content of the tab, but it also causes header container previously marked as selected to be unmarked, and you eventually end up with no tab visibly selected.

根据您的需要,有几种方法来解决这个问题。如果添加新的选项卡的唯一途径就是通过 AddNewTabCommand ,你可以只修改 AddNewTab 方法:

Depending on your needs, there are several ways to solve this problem. If the only way of adding new tabs is through the AddNewTabCommand, you could just modify the AddNewTab method:

private void AddNewTab()
{
    var tab = new Tab(GetNextName(), GetRandomContent());
    Tabs.Add(tab);
    Selected = tab;
}

在这种情况下,你不应该设置在 Col​​lectionChanged 处理程序价值,因为它会prevent 的PropertyChanged 从在合适的时间被提出。

In this case you should not set the Selected value in the CollectionChanged handler, because it will prevent PropertyChanged from being raised at the right time.

如果 AddNewTabCommand 不加入标签,我通常做的唯一方法是创建一个专门的回收这将做必要的逻辑(这个类是嵌套在 TabGroup

If AddNewTabCommand is not the only way of adding tabs, what I usually do is to create a dedicated collection which would do the required logic (this class is nested in 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);

如果这种情况出现在不同的地方,你不喜欢重复你的code,你可以创建一个可重用的集合类:

If this scenario appears in various places and you don't like repeating your code, you could create a reusable collection class:

public class MyObservableCollection<T> : ObservableCollection<T>
{
    public event NotifyCollectionChangedEventHandler AfterCollectionChanged;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        AfterCollectionChanged?.Invoke(this, e);
    }
}

,然后订阅 AfterCollectionChanged 每当你需要确保所有的 Col​​lectionChanged 用户已收到通知。

and then subscribe to AfterCollectionChanged whenever you need to be sure that all CollectionChanged subscribers have been notified.

这篇关于在异步WPF TabControl的问题结合的SelectedItem的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆