You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义ClosableTab/TabControl的SelectedItem属性随机异常求助

自定义ClosableTabControl标签选中异常求助

我们实现了类似浏览器标签页的自定义ClosableTabControl,将用户控件分组到可创建、切换、关闭的标签页中,整体运行正常,但偶尔会出现诡异的标签选中异常:SelectedIndex属性值始终正确,但交互当前标签内任意UI元素时,界面会跳转到某个固定的“随机”标签,直到用户新建并删除一个标签后,异常才会重置。

上下文

  • 应用基于WPF+C#开发,目标框架为.NET Framework 4.7.2
  • 未遵循MVVM模式,但已尽量分离业务逻辑、数据结构与UI层
  • 该问题已存在3年多,始终未找到根因

相关代码

主窗口TabControl定义(MainWindow.xaml)

<Window ...
    <Grid>
        <TabControl x:Name="tcMain" Margin="10,10,10.286,9.714" SelectionChanged="tcMain_SelectionChanged">
            <TabItem>
                <TabItem.Header>
                    <Tools:AddTabHeader/>
                </TabItem.Header>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

主窗口逻辑代码(MainWindow.xaml.cs)

public void AddItem(Item page)
{
    if (!page.ErrorClose)
    {
        tcMain.Items.Insert(tcMain.Items.Count - 1, page.TabPage);
        tcMain.SelectedItem = page.TabPage;
        page.SetLanguage();
    }
}

public TabItem GetActual()
{
    return tcMain.SelectedItem as TabItem;
}

public void SetActual(TabItem item)
{
    tcMain.SelectedItem = item;
}

private void tcMain_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    //ak je len jedna zalozka, nerob nic
    if (tcMain.Items.Count <= 1)
        return;

    //ak je aktivna posledna treba zmenit
    if (tcMain.SelectedIndex == (tcMain.Items.Count - 1))
        tcMain.SelectedIndex = tcMain.Items.Count - 2;

    //prejdi zalozky a oznac, ktora je aktivna
    foreach (TabItem tab in tcMain.Items)
    {
        if (tab.Content is Item)
        {
            Item itemTab = tab.Content as Item;
            itemTab.Active = tcMain.SelectedContent == itemTab;
            itemTab.ChangeState();
        }
    }
}

自定义TabItem实现

Item.cs(业务控件基类)

public class Item : UserControl, INotifyPropertyChanged
{
private ClosableTab page = null;
public DeviceList devices;
public DispatcherTimer dispatcherTimer;
public int Communication = 0;
public bool Run { get; set; } = false;
public bool Active { get; set; } = true;
public bool ErrorClose { get; set; } = true;

        public Item(string name, bool isFile = false)
        {
            //vytvor stranku
            page = new ClosableTab();
            page.CloseTab += CloseTab;
            page.DataContext = this;
            devices = new DeviceList();
            if (isFile)
                file = name;
            else
            {
                file = name;
                Title = name;
            }
            //timer
            dispatcherTimer = new DispatcherTimer();
            dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 500);
        }
    
        ...
}

ClosableTab.cs(自定义可关闭TabItem)

public class ClosableTab : TabItem
{
// Constructor
public ClosableTab()
{
// Create an instance of the usercontrol
CloseableHeader closableTabHeader = new CloseableHeader();
// Assign the usercontrol to the tab header
this.Header = closableTabHeader;

        // Attach to the CloseableHeader events
        // (Mouse Enter/Leave, Button Click, and Label resize)
        closableTabHeader.button_close.MouseEnter +=
           new MouseEventHandler(button_close_MouseEnter);
        closableTabHeader.button_close.MouseLeave +=
           new MouseEventHandler(button_close_MouseLeave);
        closableTabHeader.button_close.Click +=
           new RoutedEventHandler(button_close_Click);
    }

// Property - Set the Title of the Tab
// Override OnSelected - Show the Close Button
// Override OnUnSelected - Hide the Close Button
// Override OnMouseEnter - Show the Close Button
// Override OnMouseLeave - Hide the Close Button (If it is NOT selected)
// Button MouseEnter - When the mouse is over the button - change color to Red
// Button MouseLeave - When mouse is no longer over button - change color back to black
// Button Close Click - Remove the Tab - (or raise an event indicating a "CloseTab" event has occurred)

控件使用示例(Search标签页)

private void cm_Connect_Click(object sender, RoutedEventArgs e)
{
    MainWindow.AppWindow.AddItem(new Search());
}

/// <summary>
/// Interaction logic for Search.xaml
/// </summary>
public partial class Search
{
public ObservableCollection<ComPort> AvailablePorts { get; set; }

    public Search()
    {
        InitializeComponent();
    
        //daj ze mozem otvorit
        ErrorClose = false;
    
        //nastav priradenie
        AvailablePorts = new ObservableCollection<ComPort>();
        dispatcherTimer = new DispatcherTimer();
        dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
        dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 5);
        Run = true;
        dispatcherTimer.Start();
        dispatcherTimer_Tick(null, null);
    
        Image = ToolFuncs.GetIcon("IcoConnect");
        comboBox_ConnectionType.SelectedIndex = 0;
        comboBox_Port.ItemsSource = AvailablePorts;
    }
    ...
}

已排查的无效方案

  • 排查UI事件冒泡机制,未发现异常
  • 尝试强制设置SelectedItem/SelectedIndex,无法修复异常

内容的提问来源于stack exchange,提问作者Marek Sýkorka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:25:55