2017-03-02 2 views
0

Ich versuche, eine Fortschrittsanzeige in meinem Hauptfenster zu integrieren, nachdem eine Schaltfläche gedrückt und die Schaltfläche ausgeführt wird. Ich weiß, dass ich einfach etwas Einfaches vermisse, aber ich bin immer noch neu in WPF, da ich hauptsächlich Windows Forms verwende.Wie verwende ich Fortschrittsbalken in WPF

Meine XML ist wie folgt aufgebaut:

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

ich meine bevöl Button-Klick-Methode haben, wie folgt:

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

Die Frage, die ich gegenüber habe ist, dass ich diese Störung erhalte:

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

und ich bin mir nicht sicher, wie ich auf das ProgressBar-Steuerelement von meiner Schaltfläche Klick-Methode zugreifen kann.

Ich weiß, ich vermisse wahrscheinlich etwas Einfaches, aber ich versuche immer noch, den Rahmen von WPF zu bekommen.

+0

Zuerst lernen, ist die Markup-XAML (das die Genehmigung erteilt, XML ist technisch). Sie sollten auch das MVVM-Muster untersuchen. mit WPF wie WinForms ist ein schneller Weg zu Schmerzen. – BradleyDotNET

+1

Sie können WPF-Steuerelemente nicht aus einem anderen Thread als dem verwenden, auf dem sie erstellt wurden. (Normalerweise der UI-Thread) Also, was Sie tun, wird nicht funktionieren. –

+1

Während @BrandonKramer korrekt ist; Wenn Sie über eine Bindung aktualisiert würden, wären Sie in Ordnung (diese werden automatisch dem UI-Thread zugeordnet). Sie können auch mit dem 'Dispatcher.BeginInvoke' zum UI-Thread marschieren. – BradleyDotNET

Antwort

0

Sie können nicht auf Steuerelemente von einem Thread zugreifen, der sie nicht erstellt hat (alte Win32-Einschränkung). Sie haben UI Sync-Kontext verwenden, um UI-Elemente von Hintergrund-Thread so etwas wie diese

Irgendwo in der Klasse

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

Feld zuzugreifen definieren und dann verwenden:

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

Sie können auch verwenden, um Aufgaben mit Taskscheduler:

private readonly TaskScheduler uiSyncContext; 

dann definieren sie

this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext(); 

und

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

Sie sollten Dispatcher.Invoke oder Dispatcher.BeginInvoke Methoden verwenden, da progressBar zu einem anderen Thread gehört. Mit anderen Worten, statt

progressBar.Value = n; 

Verwendung

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

und Ihr Code sollte funktionieren, es sei denn, es einige Tippfehler in den Namen sind.

Weitere Informationen zum Füllen einer ProgressBar finden Sie unter this post.

Darüber hinaus sind Grid und Margin keine gute Wahl. Verwenden Sie stattdessen DockPanel oder fügen Sie RowDefinitions oder ColumnDefinitions zu Ihrem Grid hinzu.

0

Wo wird Ihre btnPopulate_Click()-Methode deklariert? Wenn in der MainWindow Klasse, dann sollte das Feld, das den Verweis auf das Element enthält, vorhanden sein. Geben Sie eine gute Minimal, Complete, and Verifiable code example an, die die von Ihnen beschriebene Kompilierungsfehlermeldung zuverlässig reproduziert.

In der Zwischenzeit & hellip;

Beachten Sie, dass Ihr Code ansonsten auch völlig falsch ist. Es wäre am besten, MVVM zu verwenden und einfach den Fortschrittsbalkenstatuswert auf eine Ansichtsmodelleigenschaft festzulegen und diese Eigenschaft an Ihre Fortschrittsleiste zu binden. Sie sollten auch einen anderen Mechanismus verwenden als einen dedizierten Thread für Hintergrundoperationen zu starten. Ich verstehe, dass der Code, den Sie gepostet haben, nur zum Üben gedacht ist, aber es ist gut, sich daran zu gewöhnen, die Dinge richtig zu machen.

Hier sind einige Optionen, die besser als das, was Sie jetzt haben, und wäre auch besser als eine der beiden anderen Antworten, die bisher gepostet wurden.

Wenn mit einer einzigen lang andauernden Operation zu tun, die gute intermittierendes Checkpoints, wo Sie Fortschritte berichten können:

Zuerst Ihre Ansicht nach Modell definieren:

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

dann in C# -Code für das Fenster :

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

Und dann in XAML binden die ProgressValue Eigenschaft auf die ProgressBar.Value Unterkunft Ansicht Modell:

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

Wenn Ihr lang andauernde Operation tatsächlich aus kleineren, asynchrone Operationen durchgeführt wird, dann könnte man so etwas wie dies stattdessen tun:

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

Beachten Sie, dass in diesem zweiten Beispiel, Sie konnte Überspringen Sie das Ansichtsmodell vollständig, da die Zuweisung für den Fortschrittswert im UI-Thread erfolgt und Sie daher problemlos direkt der Eigenschaft ProgressBar.Value dort zuweisen können. Aber Sie sollten immer noch ein Ansichtsmodell verwenden, da dies mehr mit dem WPF-Standardparadigma und den Erwartungen der WPF-API übereinstimmt (dh Sie können es anders machen, aber Sie werden die Absicht der Designer der WPF-API bekämpfen WPF API, die zu mehr Frustration und Schwierigkeiten führen wird).

0

simplified version: Sie starten eine weitere Thread, die Ihren UI-Thread-Inhalt nicht ändern kann.

Diese Lösung löst es, aber man sollte immer noch über 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(); 
     } 
Verwandte Themen