2017-03-02 52 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(); 
     }