2016-04-21 60 views
1

我有一個wpf應用程序,在主窗口中有一個文本框,用於在用戶運行一個長過程時顯示日誌信息。Backgroundworker更新UI上的日誌

<TextBox Grid.Row="1" Margin="10,10,10,10" AcceptsReturn="True" Name="txtLogging" TextWrapping="WrapWithOverflow" 
       Text="{Binding Path=LogText, Mode=TwoWay}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" /> 

public string LogText 
{ 
    get { return _logText; } 
    set 
    { 
     _logText = value; 
     OnPropertyChanged(); 
    } 
} 

ui上的一個按鈕啓動了一個過程,該過程至少需要30秒,有時甚至需要幾個小時。不用說,在後臺工作者上運行它是首選。問題在於程序中的日誌記錄類正在UI線程上創建,並且必須在工作人員執行期間被訪問,以使用當前正在發生的日誌更新UI。

記錄器看起來像這樣;

using System; 
using System.IO; 

namespace BatchInvoice 
{ 
    public enum LoggingLevel 
    { 
     Verbose = 0, 
     Info = 1, 
     Warning = 2, 
     Error = 3 
    } 
    public sealed class Logger 
    { 

     string _logFile; 
     static Logger() { } 
     public bool LogToDataBase = false; 
     public bool LogToFile = true; 
     public bool LogToScreen = false; 
     private Logger() 
     { 
      //string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 
      string filePath = Directory.GetCurrentDirectory(); 
      filePath = filePath + @"\LogFiles"; 
      string extension = ".log"; 
      if (!Directory.Exists(filePath)) 
      { 
       Directory.CreateDirectory(filePath); 
      } 
      /*string currentDir = Environment.CurrentDirectory; 
      DirectoryInfo directory = new DirectoryInfo(currentDir); 
      string fullDirectory = directory.FullName;*/ 
      string date = (DateTime.Now).ToString("yyyyMMddHHmmss"); 
      _logFile = filePath + "\\" + date + extension; 
      minimumLoggingLevel = LoggingLevel.Info; 
     } 
     private LoggingLevel minimumLoggingLevel; 
     public static void SetMinimumLoggingLevel(LoggingLevel minimum) 
     { 
      Instance.minimumLoggingLevel = minimum; 
     } 
     public static LoggingLevel GetMinimumLoggingLevel() 
     { 
      return Instance.minimumLoggingLevel; 
     } 
     private static readonly Logger instance = new Logger(); 
     public static Logger Instance 
     { 
      get 
      { 
       return instance; 
      } 
     } 
     public static void Write(string content) 
     { 
      using (StreamWriter fileWriter = File.AppendText(Instance._logFile)) 
      { 
       fileWriter.WriteLine(content); 
      } 
     } 
     public static void Write(string content, LoggingLevel warningLevel) 
     { 
      if (Instance.minimumLoggingLevel <= warningLevel) 
      { 
       if (Instance.LogToFile) 
       { 
        using (StreamWriter fileWriter = File.AppendText(Instance._logFile)) 
        { 
         fileWriter.WriteLine(warningLevel.ToString() + ": " + content); 
        } 
       } 
       if (Instance.LogToScreen) 
        ScreenLogging.Write(content, warningLevel); 
       if (Instance.LogToDataBase) 
       { 
        //enter database loggign code here. 
       } 
      } 
     } 
    } 
} 

using System.Windows; 
using System.Windows.Controls; 

namespace BatchInvoice 
{ 
    public class ScreenLogging 
    { 
     private static ScreenLogging _instance; 
     private ScreenLogging() { } 
     public static ScreenLogging Instance 
     { 
      get 
      { 
       if(_instance == null) 
       { 
        _instance = new ScreenLogging(); 
       } 
       return _instance; 
      } 
     } 
     private TextBox _target; 
     public static void SetTarget(TextBox target) 
     { 
      Instance._target = target; 
     } 
     public static void Write(string content, LoggingLevel warningLevel) 
     { 
      //MessageBox.Show(content, warningLevel.ToString()); 
      Instance._target.AppendText(warningLevel.ToString() + ": " + content + "\n"); 
     } 
    } 
} 

(是的,有是screenlogging被分成不同的類的理由,但我真的希望我沒有更改)我能做些什麼,使這個日誌類來電反思來自後臺工作者的UI?我是否應該將LogText屬性更改爲從外部文件或這些行中讀取?目前我沒有實現後臺工作,所以日誌只顯示任務完成後,但我需要能夠監視其運行進度。當我嘗試將它放入後臺工作器時,它遇到了試圖訪問記錄器的代碼行時出現錯誤。

+0

你可以使用Bgworker或任務和更新您的文本框中有隻 – Firoz

回答

1

由於您的問題似乎不是重寫所有日誌電話,我會後做,僅僅通過改變ScreenLogging.Write方法的另一種方式。我希望這對你有用,因爲你不需要改變你的調用Logger.Write方法。

public class ScreenLogging 
{ 
    private static ScreenLogging _instance; 
    private ScreenLogging() { } 
    public static ScreenLogging Instance 
    { 
     get 
     { 
      if (_instance == null) 
      { 
       _instance = new ScreenLogging(); 
      } 
      return _instance; 
     } 
    } 
    private TextBox _target; 
    public static void SetTarget(TextBox target) 
    { 
     Instance._target = target; 
    } 
    public static void Write(string content, LoggingLevel warningLevel) 
    { 
     var appendTextAction = new Action(() => 
     { 
      var text = warningLevel.ToString() + ": " + content + "\n"; 
      Instance._target.AppendText(text); 
     }); 

     // Only the thread that the Dispatcher was created on may access the 
     // DispatcherObject directly. To access a DispatcherObject from a 
     // thread other than the thread the DispatcherObject was created on, 
     // call Invoke and BeginInvoke on the Dispatcher the DispatcherObject 
     // is associated with. 
     // You can set the priority to Background, so you guarantee that your 
     // key operations will be processed first, and the screen updating 
     // operations will happen only after those operations are done. 
     Instance._target.Dispatcher.Invoke(appendTextAction, 
      DispatcherPriority.Background); 
    } 
} 
+0

大約一小時前剛剛更改爲此。我爲記錄器和屏幕記錄類的實例添加了一個鎖,它似乎工作正常。謝謝! – dragoncmd

+0

快樂你找到解決方案!對不起,延誤了,我很忙,只是再次看到你的問題。 – Ismael

2

由於您試圖從另一個線程更新UI,因此必須以特殊方式執行此操作,其中線程必須同步以在它們之間傳輸數據。換句話說,就像BackgroundWorker需要暫停來更新UI。它可以使用BackgroundWorker的ProgressChanged事件和ReportProgress方法完成。下面是一個簡單的例子:

private void Window_Loaded(object sender, RoutedEventArgs e) 
    { 
     // I guess this is how you are using your logger, right? 
     ScreenLogging.SetTarget(this.txtLogging); 

     BackgroundWorker worker = new BackgroundWorker(); 

     // Your classic event to do the background work... 
     worker.DoWork += Worker_DoWork; 

     // Here you can sender messages to UI. 
     worker.ProgressChanged += Worker_ProgressChanged; 

     // Don't forget to turn this property to true. 
     worker.WorkerReportsProgress = true; 

     worker.RunWorkerAsync(); 
    } 

    private void Worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     var worker = sender as BackgroundWorker; 

     Thread.Sleep(3000); 

     // ReportProgress sends two values to the ProgressChanged method, for the 
     // ProgressChangedEventArgs object. The first one is the percentage of the 
     // work, and the second one can be any object that you need to pass to UI. 
     // In a simple example, I am passing my log message and just putting 
     // any random value at progress, since it does not matter here. 
     worker.ReportProgress(0, "Test!"); 
    } 

    private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     // Here you get your UserState object, wich is my string message passed on 
     // with the ReportProgress method above. 
     var message = e.UserState as string; 

     // Then you call your log as always. Simple, right? 
     ScreenLogging.Write(message, LoggingLevel.Info); 
    } 
+0

這是有道理的,但要求所有來電改寫到當前存在的記錄。這些文件也被用於相關的命令行應用程序中。是否有可能檢測到一段代碼是從主線程運行還是不是?如果我可以使用可以檢查其是否從後臺線程運行的東西來覆蓋Logger.Write()方法,並且如果是,則調用報告進度方法,那會更好。 – dragoncmd