【问题标题】:Progressbar progress during long task长任务期间的进度条进度
【发布时间】:2017-11-07 14:40:38
【问题描述】:

这是我在这里的第一篇文章,所以我希望我做的一切都是正确的。

我正在使用 .NET Framework 4 客户端配置文件。

我想将 .doc 文件中的数据加载到我的程序中并使用这些信息。这可能需要很多时间,因为我需要浏览文档的表格并检查里面的内容。这已经可以了,唯一的问题是屏幕死机了,你看不到是否发生了什么。

我也知道这在 excel 中会更快更容易,但由于这种类型的数据在我们公司一直存储在 word 文档中,所以我必须保持这样。

所以我想做的是计算我必须读取的表中的所有行,将其设置为进度条的最大值,然后在每一行之后我将计算值 + 1。

我的负载 ButtonCommand 绑定到 LoadWordDocCmd 和进度条:

<Button Name="btnLoadFile" 
        Content="Load" Height="23" 
        Command="{Binding LoadWordDocCmd}"
        HorizontalAlignment="Right" Margin="0,22,129,0" 
        VerticalAlignment="Top" Width="50" 
        Visibility="{Binding VisModeAddNew}"
        />

<ProgressBar HorizontalAlignment="Left" Height="24" Margin="574,52,0,0" 
             VerticalAlignment="Top" Width="306"
             Name="prgBarAddNewLoadWord"
             Minimum="0"
             Maximum="{Binding AddNewProgressBarMaxVal, Mode=OneWay}"
             Value="{Binding AddNewProgressBarValue, Mode=OneWay}"
             Visibility="{Binding AddNewProgressBarVisible}"/>

这里是RelayCommand

/// <summary>
/// Relaycommand for Function loadWordDocument
/// </summary>
public RelayCommand LoadWordDocCmd
{
  get
  {
    if (this.m_loadWordDocCmd == null)
    {
      this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
    }
    return m_loadWordDocCmd;
  }
  private set
  {
    this.m_loadWordDocCmd = value;
  }
}

/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
  bool ret = false;

  if (this.m_fileSelected)
  {
    ret = true;
  }
  return ret;
}

我已经做的是使用BackgroundWorker。 我能够将 Button-Command 绑定到具有 RelayCommandBackgroundWorker 的函数,但后来我无法再检查 canExecute 函数。

我用它来测试进度条,它正在工作:

xaml:

  <Button   ...
            Command="{Binding Path=InstigateWorkCommand}" 
            />

cs:

     private BackgroundWorker worker; 
     private ICommand instigateWorkCommand;

     public ProggressbarSampleViewModel()
     {
        this.instigateWorkCommand = new 
                      RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);
        this.worker = new BackgroundWorker();
        this.worker.DoWork += this.DoWork;
        this.worker.ProgressChanged += this.ProgressChanged;
    }


    public ICommand InstigateWorkCommand
    {
        get { return this.instigateWorkCommand; }
    }

    private int _currentProgress;
    public int CurrentProgress
    {
        get { return this._currentProgress; }
        private set
        {
            if (this._currentProgress != value)
            {
                this._currentProgress = value;
                OnPropertyChanged("CurrentProgress"); 
            }
        }
     }

     private void ProgressChanged(object sender, ProgressChangedEventArgs e)
     {
        this.CurrentProgress = e.ProgressPercentage;
     }

    private void DoWork(object sender, DoWorkEventArgs e)
    {
        // do time-consuming work here, calling ReportProgress as and when you can   
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(1000);
            _currentProgress = i;
            OnPropertyChanged("CurrentProgress");
        }
    }

但是我怎样才能让它与 canExecute 一起工作?这是我的函数标题:

/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)

这是中继命令类:

public class RelayCommand : ICommand
  {
    private readonly Action<object> methodToExecute;

    private readonly Func<object, bool> canExecute;

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
      EventHandler handler = CanExecuteChanged;
      if (handler != null)
      {
        handler(this, EventArgs.Empty);
      }
    }

    public RelayCommand(Action<object> execute)
      : this(execute, null) { }

    public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecute)
    {
      this.methodToExecute = methodToExecute;
      this.canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
      // wird keine canExecute-Funktion übergeben, so liefert diese
      // true zurück, ansonsten wird die custom canExecute-Funktion
      // mit den übergebenen Parametern aufgerufen.
      return canExecute == null ? true : canExecute.Invoke(parameter);
    }

    public void Execute(object parameter)
    {
      methodToExecute(parameter);
    }
  }

感谢您的帮助,希望我发布的这个问题是正确的!

【问题讨论】:

  • 您是否可以选择使用 Task 和 IProgress 而不是 BackgroundWorker?见stackoverflow.com/a/35316334/982149
  • 当你想刷新Button的状态时,你应该调用命令的RaiseCanExecuteChanged()方法。什么有效,什么无效……?
  • 我什至无法让 RelayCommand 与 canExecuteChange 一起工作。当使用 new RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);我不知道如何让 canLoadDoc 的函数指针工作
  • 我从您的问题标题中删除了强制标签。阅读here为什么。另外,我从您的代码示例中删除了空的 doc-cmets,因为它们只会降低代码的可读性。
  • 阅读您的问题后,我并不完全清楚您的特殊孤立问题是什么。处理时 GUI 无响应?按钮的启用状态未更新?

标签: c# wpf xaml mvvm progress-bar


【解决方案1】:

希望我能正确理解您的问题。

GUI 应用程序的基本规则是:不要使用 GUI 线程进行(耗时的)数据处理。您必须在后台线程上执行此任务。

由于您使用的是 .NET 4.0 客户端配置文件,因此您无法使用 async/await 功能。然而,这将是最简单的解决方案。

您可以改用ThreadPool 来执行此操作。不再推荐BackgroundWorker

在您的 XAML 中,您将 ProgressBar.Value 属性绑定到 AddNewProgressBarValue 属性,因此我假设您已经拥有一个具有该属性的视图模型。您必须确保更改 AddNewProgressBarValue 会引发 PropertyChanged 事件。好消息是,WPF 绑定引擎自动将属性值传输操作编组到 GUI 线程,因此您无需关心哪个线程正在更改您的进度条绑定到的属性。

所以解决方案可能看起来像这样(不是生产代码,只是一个想法!):

class ViewModel : INotifyPropertyChanged
{
    private bool isProcessing;
    public bool AddNewProgressBarVisible
    {
        get { return this.isProcessing; }
        // SetProperty here is a PRISM-like helper to set the backing field value
        // and to raise the PropertyChanged event when needed.
        // You might be using something similar.
        private set { this.SetProperty(ref this.isProcessing, value, "AddNewProgressBarVisible");
    }

    private int progressValue;
    public int AddNewProgressBarValue
    {
        get { return this.progressValue; }
        private set { this.SetProperty(ref this.progressValue, value, "AddNewProgressBarValue");
    }

    // This is your command handler
    private void LoadWordDocument(object parameter)
    {
        if (this.isProcessing)
        {
            // don't allow multiple operations at the same time
            return;
        }

        // indicate that we're staring an operation:
        // AddNewProgressBarVisible will set isProcessing = true
        this.AddNewProgressBarVisible = true;
        this.AddNewProgressBarValue = 0;

        // Notify the bound button, that it has to re-evaluate its state.
        // Effectively, this disables the button.
        this.LoadWordDocCmd.RaiseCanExecuteChanged();

        // Run the processing on a background thread.
        ThreadPool.QueueUserWorkItem(this.DoLoadWordDocument);
    }

    private void DoLoadWordDocument(object state)
    {
        // Do your document loading here,
        // this method will run on a background thread.
        // ...

        // You can update the progress bar value directly:
        this.AddNewProgressBarValue = 42; // ...estimate the value first

        // When you're done, don't forget to enable the button.
        this.AddNewProgressBarVisible = false;

        // We have to marshal this to the GUI thread since your ICommand
        // implementation doesn't do this automatically
        Application.Current.Dispatcher.Invoke(() => this.LoadWordDocCmd.RaiseCanExecuteChanged());
    }

    // this is your command enabler method
    private bool CanLoadWordDoc(object parameter)
    {
        // if we're already loading a document, the command should be disabled
        return this.m_fileSelected && !this.isProcessing;
    }
}

【讨论】:

  • 您好,非常感谢您帮助解决了我的问题!这正是我想要做的。现在我认为我必须更改更多功能才能像这样工作,因为现在我正在 GUI 线程上做所有事情:/ 我唯一需要更改的是:Application.Current.Dispatcher.Invoke(new Action(() => this.LoadWordDocCmd.RaiseCanExecuteChanged()));
【解决方案2】:

我认为您的ProggressbarSampleViewModel 代码示例没问题。我测试了它,它可以工作。

我假设您想更改 LoadWordDocCmd 以具有 InstigateWorkCommand 的行为。如果将ProgressbarSampleViewModel 中的代码放入实际的ViewModel,则访问loadWordDocumentcanLoadWordDoc 应该没有问题。另外,正如mm8所提到的,在你的DoWork方法中你需要调用RaiseCanExecuteChanged,否则WPF将不会检查CanExecute方法。

您的 ViewModel 应该如下所示。请参阅大写的 cmets。

private BackgroundWorker worker;
private RelayCommand instigateWorkCommand;  //CHANGE HERE

bool isBusy = false;  // ADD THIS
public ProggressbarSampleViewModel()
{
    //CHANGE NEXT LINE
    this.instigateWorkCommand = new RelayCommand(
        o => this.worker.RunWorkerAsync(),
        o => !isBusy && canLoadWordDoc(null));
    this.worker = new BackgroundWorker();
    this.worker.DoWork += this.DoWork;

    //REMOVE
    //this.worker.ProgressChanged += this.ProgressChanged;
}


public ICommand InstigateWorkCommand
{
    get { return this.instigateWorkCommand; }
}

private int _currentProgress;
public int CurrentProgress
{
    get { return this._currentProgress; }
    private set
    {
        if (this._currentProgress != value)
        {
            this._currentProgress = value;
            OnPropertyChanged("CurrentProgress");
        }
    }
}

//REMOVE
//private void ProgressChanged(object sender, ProgressChangedEventArgs e)
//{
//    this.CurrentProgress = e.ProgressPercentage;
//}

private void DoWork(object sender, DoWorkEventArgs e)
{
    //ADD NEXT LINES
    isBusy = true;
    Application.Current.Dispatcher.BeginInvoke(
            (Action)instigateWorkCommand.RaiseCanExecuteChanged);

    // do time-consuming work here, calling ReportProgress as and when you can   
    for (int i = 0; i <= 100; i++)
    {
        Thread.Sleep(10);
        _currentProgress = i;
        OnPropertyChanged("CurrentProgress");
    }

    //ADD NEXT LINES
    isBusy = false;  
    Application.Current.Dispatcher.BeginInvoke(
        (Action)instigateWorkCommand.RaiseCanExecuteChanged);
}

bool m_fileSelected = true;  //CHANGE TO SEE THE EFFECT

//REMOVE
//RelayCommand m_loadWordDocCmd;
///// <summary>
///// Relaycommand for Function loadWordDocument
///// </summary>
//public RelayCommand LoadWordDocCmd
//{
//    get
//    {
//        if (this.m_loadWordDocCmd == null)
//        {
//            this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
//        }
//        return m_loadWordDocCmd;
//    }
//    private set
//    {
//        this.m_loadWordDocCmd = value;
//    }
//}

/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
    bool ret = false;

    if (this.m_fileSelected)
    {
        ret = true;
    }
    return ret;
}

/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
{
}

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-12
    • 1970-01-01
    • 2020-10-23
    • 1970-01-01
    • 1970-01-01
    • 2014-05-26
    相关资源
    最近更新 更多