2017-03-02 58 views
0

我想在按下按钮并且按钮正在运行其进程后,在主窗口中包含一个进度条。我知道我只是缺少一些简单的东西,但我仍然对WPF不熟悉,因为我主要使用Windows窗体。如何使用WPF中的progressBar

我的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(); 
} 

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

The name 'progressBar' does not exist in the current context

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

我知道我很可能错过了一些简单的东西,但我仍然试图获得WPF的窍门。

+0

首先,标记为XAML(这理所当然的,在技术上是XML)。您还应该调查使用MVVM模式;像WinForms一样使用WPF是一种快速的痛苦方式。 – BradleyDotNET

+1

您不能使用WPF控件从一个线程以外的线程创建它们。 (通常是UI线程)所以你在做什么都行不通。 –

+1

@BrandonKramer是正确的;如果你通过绑定更新你会没事的(那些自动编组到UI线程)。你也可以使用'Dispatcher.BeginInvoke'编组到UI线程 – BradleyDotNET

回答

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

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

progressBar.Value = n; 

使用

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

和你的代码应该工作,除非有名字的一些错字。

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

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

0

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

与此同时&hellip;

请注意,您的代码也完全错误。最好使用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线程中,因此直接将其分配给ProgressBar.Value属性是安全的。但是你仍然应该使用视图模型,因为这更符合标准的WPF范式和WPF API的期望(也就是说,你可以用另一种方式来做到这一点,但是你会打击设计师的意图WPF API,这将导致更多的挫折和困难)。

0

简单版本:您开始另一个Thread,无法修改您的UI线程内容。

该解决方案解决了,但你还是应该了解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(); 
     }