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

如何实现每个Tab内点击Start按钮启动独立ProgressBar并行运行

实现多Tab独立ProgressBar并行运行

需求说明

点击界面上的“+”按钮添加新的TabItem,每个Tab内包含一个Start按钮和一个ProgressBar;点击Tab内的Start按钮时,启动对应Tab的ProgressBar,实现多个ProgressBar同时独立运行。

界面示意图:
界面示意图

原代码问题分析

原代码使用了全局ProgressBar变量,每次创建新Tab时都会覆盖该变量,导致所有Start按钮点击后都会操作最后一个创建的ProgressBar,无法实现每个Tab的ProgressBar独立运行。

修复后的完整代码

MainWindow.xaml.cs

private List<TabItem> _tabItems;
private TabItem _tabAdd;

public MainWindow()
{
    try
    {
        InitializeComponent();

        // 初始化TabItem列表
        _tabItems = new List<TabItem>();

        // 添加标题为“+”的TabItem
        _tabAdd = new TabItem();
        _tabAdd.Header = "+";

        _tabItems.Add(_tabAdd);

        // 添加第一个Tab
        this.AddTabItem();

        // 绑定TabControl
        tabDynamic.DataContext = _tabItems;

        tabDynamic.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

private TabItem AddTabItem()
{
    int count = _tabItems.Count;
    // 创建当前Tab专属的ProgressBar(局部变量,不再用全局)
    ProgressBar currentPr = new ProgressBar();
    currentPr.Width = 150;
    currentPr.Height = 30;
    // 创建新的TabItem
    TabItem tab = new TabItem();

    tab.Header = string.Format("Tab {0}", count);
    tab.Name = string.Format("tab{0}", count);
    tab.HeaderTemplate = tabDynamic.FindResource("TabHeader") as DataTemplate;

    tab.MouseDoubleClick += tab_MouseDoubleClick;

    // 向TabItem中添加控件,此处添加了按钮和进度条
    StackPanel st = new StackPanel();
    st.Width = 350;
    st.Height = 350;
    Button btn = new Button();
    btn.Width = 150;
    btn.Height = 30;
    st.HorizontalAlignment = HorizontalAlignment.Center;
    st.VerticalAlignment = VerticalAlignment.Center;
    btn.Name = "txt";
    btn.Content = "Start";
    btn.Click += Btn_Click;
    Border b = new Border();
    b.BorderBrush = Brushes.Transparent;
    b.BorderThickness = new Thickness(0, 0, 0, 10);
    st.Children.Add(btn);
    st.Children.Add(b);
    st.Children.Add(currentPr);
    st.Orientation = Orientation.Vertical;
    tab.Content = st;

    // 将新TabItem插入到最后一个“+”TabItem之前
    _tabItems.Insert(count - 1, tab);

    return tab;
}

private async void Btn_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;
    if (btn == null) return;
    // 找到当前按钮所在的StackPanel容器
    StackPanel panel = btn.Parent as StackPanel;
    if (panel == null) return;
    // 从容器中找到对应的ProgressBar
    ProgressBar targetPr = panel.Children.OfType<ProgressBar>().FirstOrDefault();
    if (targetPr == null) return;

    await Task.Run(async () =>
    {
        var progress = new Progress<int>(percent =>
        {
            // 确保在UI线程更新进度条
            Application.Current.Dispatcher.Invoke(() =>
            {
                targetPr.Value = percent;
            });
        });

        Application.Current.Dispatcher.Invoke(() =>
        {
            targetPr.Value = 1;
        });

        int value = 90000;
        await DoSomeWork(value, progress);
    });
}

public async Task DoSomeWork(int iterations, IProgress<int> progress)
{
    int s = 0;
    for (int i = 0; i < iterations; i++)
    {
        // 模拟耗时操作
        s *= s * i;
        progress.Report(i * 100 / iterations);
        // 添加短暂延迟,让进度变化可见
        await Task.Delay(1);
    }
}

private void tab_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    TabItem tab = sender as TabItem;

    TabProperty dlg = new TabProperty();

    // 获取当前Tab的标题文本
    dlg.txtTitle.Text = tab.Header.ToString();

    if (dlg.ShowDialog() == true)
    {
        // 修改Tab的标题文本
        tab.Header = dlg.txtTitle.Text.Trim();
    }
}

private void tabDynamic_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    TabItem tab = tabDynamic.SelectedItem as TabItem;
    if (tab == null) return;

    if (tab.Equals(_tabAdd))
    {
        // 清除TabControl的数据绑定
        tabDynamic.DataContext = null;

        TabItem newTab = this.AddTabItem();

        // 重新绑定TabControl
        tabDynamic.DataContext = _tabItems;

        // 选中新添加的TabItem
        tabDynamic.SelectedItem = newTab;
    }
}

// 补充删除Tab的事件(原代码中引用但未实现)
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    Button btn = sender as Button;
    if (btn == null) return;
    TabItem targetTab = btn.CommandParameter as TabItem;
    if (targetTab == null || targetTab.Equals(_tabAdd)) return;

    _tabItems.Remove(targetTab);
    tabDynamic.DataContext = null;
    tabDynamic.DataContext = _tabItems;
}

MainWindow.xaml

<Grid>
    <TabControl Name="tabDynamic" ItemsSource="{Binding}" SelectionChanged="tabDynamic_SelectionChanged">
        <TabControl.Resources>
            <DataTemplate x:Key="TabHeader" DataType="TabItem">
                <DockPanel>
                    <Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnDelete_Click" 
                            CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}">
                        <TextBlock Text="×" FontSize="12" />
                    </Button>
                    <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
                </DockPanel>
            </DataTemplate>
            <Style TargetType="TextBox">
                <Setter Property="VerticalAlignment" Value="Stretch"></Setter>
                <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
                <Setter Property="AcceptsReturn" Value="True"></Setter>
                <Setter Property="TextWrapping" Value="WrapWithOverflow"></Setter>
                <Setter Property="MaxLines" Value="5000"></Setter>
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"></Setter>
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"></Setter>
            </Style>
        </TabControl.Resources>
    </TabControl>
</Grid>

TabProperty.xaml

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="60px"></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.Resources>
        <Style TargetType="Label">
            <Setter Property="HorizontalAlignment" Value="Right"></Setter>
        </Style>
    </Grid.Resources>
    <Label Content="Tab标题:" Grid.Row="0" Grid.Column="0" Height="25" />
    <TextBox Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="25" />
    <StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
        <Button Content="确定" Name="btnOK" IsDefault="True" Height="23" Click="btnOK_Click" Margin="0,0,10,0"></Button>
        <Button Content="取消" Name="btnCancel" IsCancel="True" Height="23"></Button>
    </StackPanel>
</Grid>

TabProperty.xaml.cs

public partial class TabProperty : Window
{
    public TabProperty()
    {
        InitializeComponent();
    }
    private void btnOK_Click(object sender, RoutedEventArgs e)
    {
        this.DialogResult = true;
    }
}

关键修改点

  1. 移除全局ProgressBar变量:每个Tab创建时使用局部变量currentPr,确保每个Tab拥有独立的ProgressBar实例。
  2. 在点击事件中定位对应ProgressBar:通过sender获取点击的按钮,再向上找到父容器StackPanel,最后从容器中筛选出当前Tab的ProgressBar,实现独立控制。
  3. 优化线程调度:使用Application.Current.Dispatcher.Invoke确保UI操作在主线程执行,避免跨线程访问UI控件的异常。
  4. 补充删除Tab功能:实现btnDelete_Click事件,支持删除多余的Tab(保留“+”Tab)。
  5. 优化进度可见性:在DoSomeWork中添加Task.Delay(1),让进度条的变化更直观。

内容的提问来源于stack exchange,提问作者JuniorDev13

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 05:15:37