【问题标题】:How to use progressBar in WPF如何在 WPF 中使用进度条
【发布时间】:2017-03-03 17:28:39
【问题描述】:

在按下按钮并且按钮正在运行其进程后,我试图在我的主窗口中合并一个进度条。我知道我只是缺少一些简单的东西,但我对 WPF 还是很陌生,因为我主要使用 Windows 窗体。

我的 XML 结构如下:

<Window x:Class="Program1.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:Program1"
        mc:Ignorable="d"
        Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True" BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
        x:Name="FirstWindow">
    <Grid x:Name="Grid1">
        <Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left" Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnPopulate_Click"/>
        <Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left" Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnClear_Click"/>
        <ProgressBar x:Name="progressBar" HorizontalAlignment="Left" Height="30" Margin="10,943,0,0" VerticalAlignment="Top" Width="351"/>
    </Grid>
</Window>

我的填充按钮点击方法如下:

private void btnPopulate_Click(object sender, RoutedEventArgs e)
{
    Thread backgroundThread = new Thread(
            new ThreadStart(() =>
            {
                for (int n = 0; n < 100; n++)
                {
                    Thread.Sleep(50);
                    progressBar.Value = n;
                };
            }
        ));
    backgroundThread.Start();
}

我面临的问题是我收到此错误:

当前上下文中不存在名称“progressBar”

我不确定如何通过按钮单击方法访问 progressBar 控件。

我知道我可能遗漏了一些简单的东西,但我仍在尝试掌握 WPF。

【问题讨论】:

  • 首先,标记是 XAML(这在技术上是 XML)。您还应该使用 MVVM 模式进行调查;使用像 WinForms 这样的 WPF 是一种快速解决问题的方法。
  • 您不能使用创建它们的线程以外的线程中的 WPF 控件。 (通常是 UI 线程)所以你正在做的不会工作。
  • 虽然@BrandonKramer 是正确的;如果您通过绑定进行更新,您会很好(那些自动编组到 UI 线程)。您还可以使用Dispatcher.BeginInvoke 编组到 UI 线程
  • 所有这些对于 SO 来说都太宽泛了,但是一旦你获得 20 个代表,你可以考虑在 WPF 聊天室中提问,同时沿着 MVVM 路径前进:chat.stackoverflow.com/rooms/18165/wpf

标签: c# wpf


【解决方案1】:

您无法从未创建控件的线程访问控件(旧的 Win32 限制)。您必须使用 UI Sync Context 从后台线程访问 UI 元素,例如

类中的某处定义字段

SynchronizationContext ctx = SynchronizationContext.Current ?? new SynchronizationContext();

然后使用它:

void RunOnGuiThread(Action action)
{
    this.ctx.Post(o => action(), null);
}

您还可以使用 TaskScheduler 来使用任务:

private readonly TaskScheduler uiSyncContext;

然后定义它

this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();

并使用

var task = Task.Factory.StartNew(delegate
{
   /// do something
});

this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
   /// do something that use UI controls
});

public void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
{
   task.ContinueWith(delegate
   {
      action(task);
      task.Dispose();
   }, CancellationToken.None, options, this.uiSyncContext);
}

【讨论】:

    【解决方案2】:

    简化版:您启动另一个Thread,它不能修改您的 UI-Thread-Content。

    这个方案解决了,不过你还是要了解一下MVVM

     private void btnPopulate_Click(object sender, RoutedEventArgs e)
            {
                SynchronizationContext context = SynchronizationContext.Current;
    
                Thread backgroundThread = new Thread(
                        new ThreadStart(() =>
                        {
                            for (int n = 0; n < 100; n++)
                            {
                                Thread.Sleep(50);
                                context?.Post(new SendOrPostCallback((o) =>
                                {
    
                                    progressBar.Value = n;
                                }), null);
                            };
    
    
                        }
                    ));
                backgroundThread.Start();
            }
    

    【讨论】:

      【解决方案3】:

      您应该使用Dispatcher.InvokeDispatcher.BeginInvoke 方法,因为progressBar 属于另一个线程。换句话说,而不是

      progressBar.Value = n;
      

      使用

      Dispatcher.Invoke(new Action(()=> { progressBar.Value = n; }));
      

      并且您的代码应该可以工作,除非名称中有一些拼写错误。

      请参阅this post 了解填充进度条的更好选择。

      此外,Grid 和Margin 不是一个好的选择。而是使用 DockPanel 或将 RowDefinitions 或 ColumnDefinitions 添加到您的网格中。

      【讨论】:

        【解决方案4】:

        您的btnPopulate_Click() 方法在哪里声明?如果在MainWindow 类中,则包含对元素的引用的字段应该存在。请提供一个好的Minimal, Complete, and Verifiable code example,它可以可靠地重现您描述的编译时错误消息。

        与此同时……

        请注意,您的代码在其他方面也是完全错误的。最好使用 MVVM 并简单地在视图模型属性上设置进度条状态值,将该属性绑定到您的进度条。除了启动专用线程来处理后台操作之外,您还应该使用其他一些机制。我知道您发布的代码只是为了练习,但最好养成正确做事的习惯。

        这里有一些选项会比您现在拥有的更好,并且也会比迄今为止发布的其他两个答案中的任何一个更好。

        如果处理具有良好间歇性检查点的单个长时间运行的操作,您可以在其中报告进度:

        首先,定义你的视图模型:

        class ViewModel : INotifyPropertyChanged
        {
            private double _progressValue;
        
            public double ProgressValue
            {
                get { return _progressValue; }
                set { _UpdatePropertyField(ref _progressValue, value); }
            }
        
            public event PropertyChangedEventHandler PropertyChanged;
        
            private void _UpdatePropertyField<T>(
                ref T field, T value, [CallerMemberName] string propertyName = null)
            {
                if (!EqualityComparer.Default.Equals(field, value))
                {
                    field = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
        

        然后在你的 C# 代码中为窗口:

        class MainWindow : Window
        {
            private readonly ViewModel _viewModel = new ViewModel();
        
            public MainWindow()
            {
                DataContext = _viewModel;
                InitializeComponent();
            }
        
            private void btnPopulate_Click(object sender, RoutedEventArgs e)
            {
                Task.Run(() =>
                {
                    for (int n = 0; n < 100; n++)
                    {
                        // simulates some costly computation
                        Thread.Sleep(50);
        
                        // periodically, update the progress
                        _viewModel.ProgressValue = n;
                    }
                });
            }
        }
        

        然后在您的 XAML 中,将视图模型的 ProgressValue 属性绑定到 ProgressBar.Value 属性:

        <Window x:Class="Program1.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:Program1"
                mc:Ignorable="d"
                Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True"
                BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
                x:Name="FirstWindow">
          <Grid x:Name="Grid1">
            <Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left"
                    Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
                    Click="btnPopulate_Click"/>
            <Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left"
                    Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
                    Click="btnClear_Click"/>
            <ProgressBar HorizontalAlignment="Left" Height="30" Margin="10,943,0,0"
                         VerticalAlignment="Top" Width="351" Value="{Binding ProgressValue}"/>
          </Grid>
        </Window>
        

        如果您的长时间运行的操作实际上由较小的异步操作组成,那么您可以改为执行以下操作:

        private async void btnPopulate_Click(object sender, RoutedEventArgs e)
        {
            for (int n = 0; n < 100; n++)
            {
                // simulates one of several (e.g. 100) asynchronous operations
                await Task.Delay(50);
        
                // periodically, update the progress
                _viewModel.ProgressValue = n;
            }
        }
        

        请注意,在第二个示例中,您可以完全跳过视图模型,因为进度值的分配发生在 UI 线程中,因此直接分配给 @987654330 是安全的@ 那里的财产。但是你仍然应该使用视图模型,因为这更符合标准 WPF 范式和 WPF API 的期望(即你可以用另一种方式来做,但你会与设计者的意图作斗争。 WPF API,这将导致更多的挫败感和困难)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-12
          • 1970-01-01
          相关资源
          最近更新 更多