WPF - TabControl 和 ZIndex 中重叠的自定义选项卡

发布于 2024-08-27 02:21:55 字数 1833 浏览 14 评论 0原文

问题
我有一个自定义选项卡控件,使用绑定到 ViewModel 的 Chrome 形状选项卡。由于形状的原因,边缘有点重叠。我有一个在 TabControl_SelectionChanged 上设置 tabItem 的 ZIndex 的函数,它可以很好地选择选项卡和拖/放选项卡,但是当我通过中继命令添加或关闭选项卡时,我得到了不寻常的结果。有人有什么想法吗?

默认视图:

删除标签页:

连续添加 2 个或更多选项卡:

一次添加超过 1 个选项卡不会重置其他最近添加的选项卡的 zindex,因此它们会位于右侧的选项卡,并且关闭选项卡不会正确呈现替换它的 SelectedTab 的 ZIndex,并且它显示在右侧选项卡的后面。

设置 ZIndex 的代码

private void PrimaryTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.Source is TabControl)
        {
            TabControl tabControl = sender as TabControl;
            ItemContainerGenerator icg = tabControl.ItemContainerGenerator;
            if (icg.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
            {
                foreach (object o in tabControl.Items)
                {
                    UIElement tabItem = icg.ContainerFromItem(o) as UIElement;
                    Panel.SetZIndex(tabItem, (o == tabControl.SelectedItem ? 100 :
                        90 - tabControl.Items.IndexOf(o)));
                }
            }
        }
    }

通过使用断点,我可以看到它正确地将 ZIndex 设置为我想要的值,但是布局没有显示更改。我知道一些更改正在生效,因为如果它们都不起作用,那么选项卡边缘将被反转(右侧选项卡将绘制在左侧选项卡的顶部)。单击选项卡将正确设置所有选项卡(包括应在顶部绘制的选项卡)的 zindex,并且拖放它们以重新排列它们也会正确呈现(这会删除并重新插入选项卡项目)。我能想到的唯一区别是我使用 MVVM 设计模式,并且添加/关闭选项卡的按钮是中继命令。

有谁知道为什么会发生这种情况以及我该如何解决它?

ps 我确实尝试在 ViewModel 中设置 ZIndex 并绑定到它,但是通过中继命令添加/删除选项卡时会发生同样的事情。

Problem

I have a custom tab control using Chrome-shaped tabs that binds to a ViewModel. Because of the shape, the edges overlap a bit. I have a function that sets the tabItem's ZIndex on TabControl_SelectionChanged which works fine for selecting tabs, and dragging/dropping tabs, however when I Add or Close a tab via a Relay Command I am getting unusual results. Does anyone have any ideas?

Default view:

Removing Tabs:

Adding 2 or more Tabs in a row:

Adding more then 1 tab at a time will not reset the zindex of other recently-added tabs so they go behind the tab on the Right, and closing tabs does not correctly render the ZIndex of the SelectedTab that replaces it and it shows up behind the tab on its right.

Code to set ZIndex

private void PrimaryTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.Source is TabControl)
        {
            TabControl tabControl = sender as TabControl;
            ItemContainerGenerator icg = tabControl.ItemContainerGenerator;
            if (icg.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
            {
                foreach (object o in tabControl.Items)
                {
                    UIElement tabItem = icg.ContainerFromItem(o) as UIElement;
                    Panel.SetZIndex(tabItem, (o == tabControl.SelectedItem ? 100 :
                        90 - tabControl.Items.IndexOf(o)));
                }
            }
        }
    }

By using breakpoints I can see that it is correctly setting the ZIndex to what I want it to, however the layout is not displaying the changes. I know some of the changes are in effect because if none of them were working then the tab edges would be reversed (the right tabs would be drawn on top of the left ones). Clicking a tab will correctly set the zindex of all tabs (including the one that should be drawn on top) and dragging/dropping them to rearrange them also renders correctly (which removes and reinserts the tab item). The only difference I can think of is I am using the MVVM design pattern and the buttons that Add/Close tabs are relay commands.

Does anyone have any idea why this is happening and how I can fix it??

p.s. I did try setting a ZIndex in my ViewModel and binding to it, however the same thing happens when adding/removing tabs via the relay command.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一身软味 2024-09-03 02:21:55

谢谢阿部,你的第二条评论引导我找到了解决方案!

我将 tabItem.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); 添加到循环的每次迭代中。

我仍然有兴趣了解是否有其他人找到解决此问题的方法,而无需在每次更改时刷新每个 tabItem。我尝试在循环结束时刷新整个选项卡控件,但这仅适用于关闭选项卡,而不能添加它们。我知道 Panel.ZIndex 设置正确,只是在渲染时不尊重该属性。

编辑:上面的代码行在拖/放选项卡时会导致异常闪烁,该选项卡会短暂显示被拖动选项卡后面的选项卡。我将代码移动到一个单独的函数中,并以较低的调度程序优先级调用它,这解决了问题。最终代码如下:

private void PrimaryTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.Source is TabControl)
        {
            TabControl tabControl = sender as TabControl;

            tabControl.Dispatcher.BeginInvoke(
                new Action(() => UpdateZIndex(sender as TabControl)),
                DispatcherPriority.Background);
        }
    }

    private void UpdateZIndex(TabControl tabControl)
    {
        ItemContainerGenerator icg = tabControl.ItemContainerGenerator;

        if (icg.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
        {
            foreach (object o in tabControl.Items)
            {
                UIElement tabItem = icg.ContainerFromItem(o) as UIElement;
                if (tabItem != null)
                {
                    // Set ZIndex
                    Panel.SetZIndex(tabItem, (o == tabControl.SelectedItem ? 100 :
                        90 - tabControl.Items.IndexOf(o)));
                }
            }
        }
    }

Thank you Abe, your second comment lead me to my solution!

I added tabItem.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); to each iteration of the loop.

I would still be interested in learning if anyone else finds a way around this without refreshing each tabItem on every change. I tried refreshing the entire tab control at the end of the loop but that only worked for closing tabs, not adding them. I know that Panel.ZIndex is getting set correctly, it's just not honoring that property when rendering.

EDIT: The above line of code was causing an unusual flicker when Dragging/Dropping the tabs that would briefly show the tab behind the one being dragged. I moved the code to a separate function and called it at a lower dispatcher priority and this fixed the problem. Final code is below:

private void PrimaryTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.Source is TabControl)
        {
            TabControl tabControl = sender as TabControl;

            tabControl.Dispatcher.BeginInvoke(
                new Action(() => UpdateZIndex(sender as TabControl)),
                DispatcherPriority.Background);
        }
    }

    private void UpdateZIndex(TabControl tabControl)
    {
        ItemContainerGenerator icg = tabControl.ItemContainerGenerator;

        if (icg.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
        {
            foreach (object o in tabControl.Items)
            {
                UIElement tabItem = icg.ContainerFromItem(o) as UIElement;
                if (tabItem != null)
                {
                    // Set ZIndex
                    Panel.SetZIndex(tabItem, (o == tabControl.SelectedItem ? 100 :
                        90 - tabControl.Items.IndexOf(o)));
                }
            }
        }
    }
っ〆星空下的拥抱 2024-09-03 02:21:55

听起来您只需要在集合发生变化时再次运行算法即可。由于您正在测试 ItemContainerGenerator.Status 属性,因此该算法可能无法运行。您可能需要考虑监听 StatusChanged 事件,并在其更改为 ContainersGenerate 时再次运行算法。

It sounds like you just need to run your algorithm again when the collection changes. Since you are testing the ItemContainerGenerator.Status property, the algorithm may not run. You may want to consider listening to the StatusChanged event, and when it changes to ContainersGenerated run the algorithm again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文