如何实现每个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; } }
关键修改点
- 移除全局ProgressBar变量:每个Tab创建时使用局部变量
currentPr,确保每个Tab拥有独立的ProgressBar实例。 - 在点击事件中定位对应ProgressBar:通过
sender获取点击的按钮,再向上找到父容器StackPanel,最后从容器中筛选出当前Tab的ProgressBar,实现独立控制。 - 优化线程调度:使用
Application.Current.Dispatcher.Invoke确保UI操作在主线程执行,避免跨线程访问UI控件的异常。 - 补充删除Tab功能:实现
btnDelete_Click事件,支持删除多余的Tab(保留“+”Tab)。 - 优化进度可见性:在
DoSomeWork中添加Task.Delay(1),让进度条的变化更直观。
内容的提问来源于stack exchange,提问作者JuniorDev13
相关产品推荐
相关产品推荐

