2017-08-03 182 views
1

我有一個主要遵循MVVM模式編寫的小型WPF應用程序。程序的要點是讀取文本文件的行,解析數據,將數據寫入對象列表,然後將這些對象中的數據寫入特定格式的.CSV文件。如何使用MVVM/ICommand模式在WPF中正確實現BackgroundWorker

儘管我已經使用與其他應用程序一樣的方式實現BackgroundWorker類,但是這次我從我的ICommand的Execute()方法中調用RunWorkAsync()方法。雖然最終輸出是正確的,並且應用程序實際上提供了期望的結果,但在BackgroundWorker運行時,UI STILL鎖定。

我用一個構造函數將我的BackgroundWorker成員和一個名爲「ReaderWriter」的類中的所有邏輯都包含在我的ViewModel中作爲參數。

通過調用我的ReaderWriter實例的「StartProcess」方法,BackgroundWorker的RunWorkerAsync()被調用 - 這是我希望它會接管另一個線程並執行長時間運行的讀取源文件的過程,解析數據並寫入新文件;同時定期執行ReportProgress()來更新ProgressBar。

這裏是我的讀寫器類的代碼:

class ReaderWriter 
{ 
    private LogDataViewModel vm { get; set; } 
    private BackgroundWorker bw { get; set; } 

    public ReaderWriter(LogDataViewModel viewModel) 
    { 
     vm = viewModel; 
    } 

    public void StartProcess() 
    { 
     bw = new BackgroundWorker(); 

     bw.WorkerReportsProgress = true; 
     bw.WorkerSupportsCancellation = true; 
     bw.DoWork += new DoWorkEventHandler(ReadFromSource); 
     bw.ProgressChanged += new ProgressChangedEventHandler(UpdateProgress_Read); 
     bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed_Read); 

     bw.RunWorkerAsync(); 
    } 

    private void ReadFromSource(object sender, DoWorkEventArgs e) 
    { 
     double records = 0; 
     string[] lines = File.ReadAllLines(vm.SourcePath); 
     int lineCount = lines.Length; 
     double currentLine = 0; 

     bw.ReportProgress(0, lineCount); 

     foreach (var line in lines) 
     { 
      if (line.Length > 0) 
      { 
       string syntax = line.Substring(17, 6); 

       switch (syntax) 
       { 
        case "$WIMDA": 
         string[] segments = line.Replace(": <- ", ",").Split(','); 
         vm.LineItems.Add(new LineItem() 
         { 
          Time = segments[0], 
          HgPressure = segments[2], 
          BarPressure = segments[4], 
          AirTemp = segments[6], 
          RelHumidity = segments[10], 
          TrueWindDir = segments[14], 
          KnotsWindSpeed = segments[18], 
          MpsWindSpeed = segments[20] 
         }); 
         break; 

        case "$GPGGA": 
         break; 

        default: 
         break; 
       } 
      } 
      currentLine++; 
      bw.ReportProgress(1, currentLine); 
     } 
     using (StreamWriter writer = new StreamWriter(vm.OutputPath)) 
     { 
      writer.WriteLine($"Time,Pressure(Bar),Pressure(Hg),AirTemp({((vm.ConvertTempSetting) ? "F" : "C")}),RelativeHumidity,TrueWindDirection,WindSpeed(Knots),WindSpeed(M/s)"); 
      foreach (var lineItem in vm.LineItems) 
      { 
       writer.WriteLine($"{lineItem.Time},{lineItem.BarPressure},{lineItem.HgPressure},{((vm.ConvertTempSetting) ? Converters.ConvertFromCelcius(Convert.ToDouble(lineItem.AirTemp)).ToString() : lineItem.AirTemp)},{lineItem.RelHumidity},{lineItem.TrueWindDir},{lineItem.KnotsWindSpeed},{lineItem.MpsWindSpeed}"); 
       records++; 
      } 
     } 
     e.Result = records; 
    } 

    private void UpdateProgress_Read(object sender, ProgressChangedEventArgs e) 
    { 
     vm.IncrementProgress(); 
     switch (Type.GetTypeCode(e.UserState.GetType())) 
     { 
      case TypeCode.Double: 
       vm.IncrementProgress(); 
       break; 

      case TypeCode.String: 
       break; 

      case TypeCode.Int32: 
       vm.AppendStatus(DateTime.Now, $"{(int)e.UserState} lines parsed from log file"); 
       break; 

      default: 
       break; 
     } 
     if (vm.IsFirst) 
     { 
      vm.ProgressIsVisible = true; 
      vm.IncrementProgress(); 
      vm.SetMaximum((int)e.UserState); 
      vm.IsFirst = false; 
     } 
    } 
    private void Completed_Read(object sender, RunWorkerCompletedEventArgs e) 
    { 
     if (e.Cancelled) 
     { 
      vm.AppendStatus(DateTime.Now, $"Conversion was cancelled by user"); 
     } 
     else 
     { 
      vm.AppendStatus(DateTime.Now, $"{(double)e.Result} records written to {vm.OutputPath}"); 
     } 
     vm.LineItems.Clear(); 
    } 
} 

而對於我的ViewModel:

public class LogDataViewModel : LogDataModel 
{ 
    #region Commands 
    public BeginProcessCommand BeginCommand { get; set; } 
    public SelectOutputPathCommand OutputCommand { get; set; } 
    public SelectSourceCommand SourceCommand { get; set; } 
    public ResetCommand ResetCommand { get; set; } 
    #endregion 

    public bool IsFirst { get; set; } 

    public LogDataViewModel() 
    { 
     BeginCommand = new BeginProcessCommand(this); 
     OutputCommand = new SelectOutputPathCommand(this); 
     SourceCommand = new SelectSourceCommand(this); 
     ResetCommand = new ResetCommand(this); 

     PrepareViewModel(); 
    } 


    private void PrepareViewModel() 
    { 
     ProgressValue = 0; 
     ProgressMaximum = 0; 
     ProgressIsVisible = false; 
     IsFirst = true; 

     OutputPath = Properties.Settings.Default.RememberedSavePath; 
     if (LineItems == null) LineItems = new List<LineItem>(); 
     if (StatusActions == null) StatusActions = new ObservableCollection<StatusAction>(); 
     AppendStatus(DateTime.Now, "Initialized Program"); 
    } 
} 

最後,這裏是命令:

public class BeginProcessCommand : ICommand 
{ 
    LogDataViewModel vm; 

    public BeginProcessCommand(LogDataViewModel viewModel) 
    { 
     vm = viewModel; 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 

    public bool CanExecute(object parameter) 
    { 
     bool result = true; 

     if (!File.Exists(vm.SourcePath)) 
      result = false; 
     try 
     { 
      if (!Directory.Exists(Path.GetDirectoryName(vm.SourcePath))) 
       result = false; 
     } 
     catch 
     { 
      result = false; 
     } 
     return result; 
    } 

    public void Execute(object parameter) 
    { 
     ReaderWriter rw = new ReaderWriter(vm); 
     rw.StartProcess(); 
    } 
} 

任何幫助的這一點非常感謝,因爲我現在一直在努力研究這個問題,並試圖研究解決方案對我的特殊情況沒有幫助。這似乎是一個相當獨特的場景,但我希望我們可以使它工作。

謝謝!

+0

這是因爲你用更新來暴露用戶界面。每100行只嘗試報告進度。如果你有很多線條,並且循環快速旋轉,那麼就是這個數字。 – Will

+0

謝謝,威爾。我做了你的建議,它像一個魅力工作! –

回答

2

您正在錯誤地使用ReportProgress並且太頻繁(在文件的每一行上)。它將被打擊,每個通話都會導致用戶界面中的某種更新,從而將其鎖定。

ReportProgress可能是最容易使用的,通過向它傳遞一個百分比。我不確定你在UpdateProgress_Read中做了什麼。最好只在您傳遞總數100行時更新。

你的進度最大爲100

ProgressMaximum = 100; 

計算你的總線路的1%

var x = lineCount/100; 
var y = 0; 

,只有當你經過每1%

currentLine++; 
if((currentLine % x) == 0) 
{ 
    y++; 
    bw.ReportProgress(y); 
} 

和更改報告進度UpdateProgress_Read,所以它只是增量

private void UpdateProgress_Read(object sender, ProgressChangedEventArgs e) 
{ 
    vm.IncrementProgress(); 
} 

你需要想出更好的變量名稱,然後x和y!如果文件中的行數少於100行,也可以計算出該做什麼。

+0

非常感謝您的詳細解答,ajg!您的深入解釋幫助我更好地理解ReportProgress()是如何使用的。我在我的foreach迭代中修改了一小段代碼,現在它工作得很完美。我很高興這是我原本懷疑的一個更簡單的問題! =) –