如何在执行期间使用进度条GUI更新列表框

时间:2017-11-29 20:06:11

标签: c# wpf mvvm listbox progress-bar

我在wpf窗口中有一个列表框,它绑定到viewmodel对象中的列表。当我在viewmodel对象中运行一个方法时,它处理列表的成员,每个成员都有一个进度。我想在执行期间不断更新gui。就像现在一样,它只在处理完成后更新gui。

在这里,我试图创建一个我现在所拥有的小例子:

MainWindow.xaml:

<Window x:Class="WPF_MVVM_Thread_Progressbar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPF_MVVM_Thread_Progressbar"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <local:TestViewModel/>
  </Window.DataContext>
    <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="8*"/>
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="8*"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ListBox Grid.Column="0"  Grid.Row="0" Margin="5" ItemsSource="{Binding TestWorker.TestList}">
      <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
          <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
      </ListBox.ItemContainerStyle>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <Grid>
            <ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress, Mode=OneWay}" Background="Bisque">
              <ProgressBar.Style>
                <Style TargetType="{x:Type ProgressBar}">
                  <Style.Triggers>
                    <DataTrigger Binding="{Binding Progress}" Value="0">
                      <Setter Property="Visibility" Value="Hidden"/>
                    </DataTrigger>
                  </Style.Triggers>
                </Style>
              </ProgressBar.Style>
            </ProgressBar>
            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Background="Transparent"/>
          </Grid>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>



    <Button Grid.Column="0" Grid.Row="1" Content="TestRun" Command="{Binding TestRunCommand}"></Button>
    <TextBlock Text="{Binding SelectedIdx}" Grid.Column="1" Grid.Row="1"/>
  </Grid>
</Window>

MainWindowl.xaml.cs:

using Prism.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace WPF_MVVM_Thread_Progressbar
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }
  }

  public class TestViewModel : INotifyPropertyChanged
  {
    private WorkingClass _testWorker;

    private DelegateCommand _testRunCommand;

    public DelegateCommand TestRunCommand
    {
      get { return _testRunCommand; }
      set { _testRunCommand = value; }
    }

    public WorkingClass TestWorker
    {
      get { return _testWorker; }
      set { _testWorker = value; RaisePropertyChanged("TestWork"); }
    }

    private int _selectedIdx;

    public int SelectedIdx
    {
      get { return _selectedIdx; }
      set { _selectedIdx = value; RaisePropertyChanged("SelectedIdx"); }
    }


    public TestViewModel()
    {
      _testWorker = new WorkingClass();
      _testRunCommand = new DelegateCommand(TestRun, canRun);
    }

    public async void TestRun()
    {
      //await Task.Run(() => _testWorker.Work());
      _testWorker.Work();
    }

    private bool canRun()
    {
      return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(propertyName));
      }
    }

  }

  public class WorkingClass : INotifyPropertyChanged
  {
    private ObservableCollection<TestObject> _testList;

    public ObservableCollection<TestObject> TestList
    {
      get { return _testList; }
      set { _testList = value; RaisePropertyChanged("TestList"); }
    }

    public WorkingClass()
    {
      _testList = new ObservableCollection<TestObject>();
      _testList.Add(new TestObject("Object A"));
      _testList.Add(new TestObject("Object B"));
      _testList.Add(new TestObject("Object C"));
      RaisePropertyChanged("TestList");

    }

    public void Work()
    {
      foreach (var obj in TestList)
      {
        obj.TestWork();
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(propertyName));
      }
    }

  }

  public class TestObject : INotifyPropertyChanged
  {
    private string _name;

    public string Name
    {
      get { return _name; }
      set { _name = value; }
    }

    private int _progress;

    public int Progress
    {
      get { return _progress; }
      set { _progress = value; RaisePropertyChanged("Progress"); }
    }

    public TestObject(string name)
    {
      this._name = name;
      _progress = 0;
    }

    public void TestWork()
    {
      for (int i = 0; i < 100; i++)
      {
        System.Threading.Thread.Sleep(10);
        Progress++;
      }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(propertyName));
      }
    }
  }
}

我曾尝试使用ObservableCollection和INotifyPropertyChanged,但这似乎还不够。

最终我希望能够使用来自TestViewModel.TestRun()的async / await调用获得相同的效果。

有人可能会就此提供一些见解吗?非常感谢。

2 个答案:

答案 0 :(得分:1)

我认为您在UI完成后只更新UI的当前原因是您在UI线程上运行所有这些。我会尝试这样做:

Task.Run(async delegate
{ 
   await _testWorker.Work();
});

Task.Run(() =>
{ 
    _testWorker.Work();
});

或者

Task.Factory.StartNew(() =>
{ 
    _testWorker.Work();
});

或者

var newThread = new Thread(new ThreadStart(_testWorker.Work());
newThread.Start();

这将立即返回到UI,但允许您的代码继续。

注意:您必须小心使用UI线程中的对象。 ObservableCollections只能在与处理UI工作的调度程序相同的线程上创建。如果您使用双向绑定,则必须小心线程安全性。

答案 1 :(得分:0)

我过去使用BackgroundWorker成功完成了此操作。

public class TestObject : INotifyPropertyChanged {
    private BackgroundWorker worker;

    public TestObject() {
        worker = new BackgroundWorker() {
            WorkerReportsProgress = true
        };
        worker.DoWork += DoWork;
        worker.ProgressChanged += WorkProgress;
        worker.RunWorkerCompleted += WorkFinished;
    }

    public int Progress
    {
      get { return _progress; }
      set { _progress = value; RaisePropertyChanged("Progress"); }
    }

    // Begin doing work
    public void TestWork() {
        worker.RunWorkerAsync();
    }

    private void DoWork(object sender, DoWorkEventArgs eventArgs) {
        worker.ReportProgress(0, "Work started");

        for (int i = 0; i < 100; i++) {
            System.Threading.Thread.Sleep(10);
            worker.ReportProgress(i, "Message");
        }
    }

    // Fires when the progress of a job changes.
    private void WorkProgress(object sender, ProgressChangedEventArgs e) {
        // Do something with the progress here
        Progress = e.ProgressPercentage;
    }

    // Fires when a job finishes.
    private void WorkFinished(object sender, RunWorkerCompletedEventArgs e) {
        // The work finished. Do something?
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
      // NOTE: If you're running C#6 use the null conditional operator for this check.
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(e));
    }
}

BackgroundWorker基本上在一个单独的线程上运行所有内容,并在其进度发生变化或完成工作时报告。您可以从进度报告中提取ProgressPercentage并在UI中使用它。希望有所帮助。为了使示例简单,我没有包含您的一些代码,但这应该让您知道如何完成它。