如何在WPF中使用progressBar

时间:2017-03-02 19:45:55

标签: c# wpf

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

我的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。

4 个答案:

答案 0 :(得分:0)

您无法从未创建控件的线程访问控件(旧的Win32限制)。您必须使用UI同步上下文从后台线程访问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);
}

答案 1 :(得分:0)

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

progressBar.Value = n;

使用

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

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

请参阅this post以获得更好的填充ProgressBar的选择。

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

答案 2 :(得分:0)

Where is your btnPopulate_Click() method being declared? If in the MainWindow class, then the field containing the reference to the element should exist. Please provide a good Minimal, Complete, and Verifiable code example that reliably reproduces the compile-time error message you describe.

In the meantime…

Do note that your code is otherwise entirely wrong as well. It would be best to use MVVM and simply set the progress bar state value on a view model property, binding that property to your progress bar. You also should use some other mechanism than starting a dedicated thread for dealing with background operations. I understand the code you posted is just for practice, but it's good to get into the habit of doing things the right way.

Here are some options that would be better than what you have now, and would also be better than either of the other two answers posted so far.

If dealing with a single long-running operation that has good intermittent checkpoints where you can report progress:

First, define your view model:

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));
        }
    }
}

Then in your C# code for the window:

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;
            }
        });
    }
}

And then in your XAML, bind the view model's ProgressValue property to the ProgressBar.Value property:

<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>

If your long-running operation is actually made up of smaller, asynchronous operations, then you could do something like this instead:

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;
    }
}

Note that in this second example, you could skip the view model altogether, because the assignment for the progress value occurs in the UI thread, and so it's safe to just assign directly to the ProgressBar.Value property there. But you should still use a view model anyway, because that's more in keeping with the standard WPF paradigm and the expectations of the WPF API (i.e. you can do it the other way, but you'll be fighting the intent of the designers of the WPF API, which will lead to more frustration and difficulty).

答案 3 :(得分:0)

简化版本:您启动另一个无法修改您的UI-Thread-Content的Thread

此解决方案解决了这个问题,但您仍然应该了解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();
        }