2009-11-21 83 views
2

我创建在WPF备份实用程序,并有关于线程一个一般问题:如何直接从WPF中的BackgroundWorker线程访问UI线程?

在该方法中backgroundWorker.DoWork(),声明Message2.Text =“...”给出了错误“调用线程不能访问此对象,因为不同的线程拥有它。”“。

有没有办法让我直接访问内backgroundWorker.DoWork(),即改变文本的UI线程在一个XAML文本框在这一点?或是否需要存储在一个内部变量所有显示信息,然后在backgroundWorker.ProgressChanged()显示它,我曾与例如做percentageFinished?

XAML:

<Window x:Class="TestCopyFiles111.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="350" Width="525"> 
    <DockPanel LastChildFill="True" HorizontalAlignment="Left" VerticalAlignment="Top" 
       Margin="10"> 

     <StackPanel Orientation="Horizontal" DockPanel.Dock="Top"> 
      <Button x:Name="Button_Start" 
        HorizontalAlignment="Left" 
        DockPanel.Dock="Top" 
        Content="Start Copying" 
        Click="Button_Start_Click" 
        Height="25" 
        Margin="0 0 5 0" 
        Width="200"/> 
      <Button x:Name="Button_Cancel" 
        HorizontalAlignment="Left" 
        DockPanel.Dock="Top" 
        Content="Cancel" 
        Click="Button_Cancel_Click" 
        Height="25" 
        Width="200"/> 
     </StackPanel> 

     <ProgressBar x:Name="ProgressBar" 
        DockPanel.Dock="Top" 
        HorizontalAlignment="Left" 
        Margin="0 10 0 0" 
        Height="23" 
        Width="405" 
        Minimum="0" 
        Maximum="100" 
        /> 

     <TextBlock DockPanel.Dock="Top" x:Name="Message" Margin="0 10 0 0"/> 
     <TextBlock DockPanel.Dock="Top" x:Name="CurrentFileCopying" Margin="0 10 0 0"/> 
     <TextBlock DockPanel.Dock="Top" x:Name="Message2" Margin="0 10 0 0"/> 
    </DockPanel> 
</Window> 

后台代码:

using System.Windows; 
using System.ComponentModel; 
using System.Threading; 
using System.IO; 
using System.Collections.Generic; 
using System; 

namespace TestCopyFiles111 
{ 
    public partial class Window1 : Window 
    { 
     private BackgroundWorker backgroundWorker; 

     float percentageFinished = 0; 
     private int totalFilesToCopy = 0; 
     int filesCopied = 0; 

     string currentPathAndFileName; 

     private List<CopyFileTask> copyFileTasks = new List<CopyFileTask>(); 
     private List<string> foldersToCreate = new List<string>(); 

     public Window1() 
     { 
      InitializeComponent(); 
      Button_Cancel.IsEnabled = false; 
      Button_Start.IsEnabled = true; 
      ProgressBar.Visibility = Visibility.Collapsed; 

     } 

     private void Button_Start_Click(object sender, RoutedEventArgs e) 
     { 
      Button_Cancel.IsEnabled = true; 
      backgroundWorker = new BackgroundWorker(); 
      backgroundWorker.WorkerReportsProgress = true; 
      backgroundWorker.WorkerSupportsCancellation = true; 
      ProgressBar.Visibility = Visibility.Visible; 

      AddFilesFromFolder(@"c:\test", @"C:\test2"); 

      Message.Text = "Preparing to copy..."; 

      MakeSureAllDirectoriesExist(); 

      CopyAllFiles(); 

     } 


     void AddFilesFromFolder(string sourceFolder, string destFolder) 
     { 
      if (!Directory.Exists(destFolder)) 
       Directory.CreateDirectory(destFolder); 
      string[] files = Directory.GetFiles(sourceFolder); 
      foreach (string file in files) 
      { 
       string name = Path.GetFileName(file); 
       string dest = Path.Combine(destFolder, name); 
       copyFileTasks.Add(new CopyFileTask(file, dest)); 
       totalFilesToCopy++; 
      } 
      string[] folders = Directory.GetDirectories(sourceFolder); 
      foreach (string folder in folders) 
      { 
       string name = Path.GetFileName(folder); 
       string dest = Path.Combine(destFolder, name); 
       foldersToCreate.Add(dest); 
       AddFilesFromFolder(folder, dest); 
      } 
     } 

     void MakeSureAllDirectoriesExist() 
     { 
      foreach (var folderToCreate in foldersToCreate) 
      { 
       if (!Directory.Exists(folderToCreate)) 
        Directory.CreateDirectory(folderToCreate); 
      } 
     } 

     void CopyAllFiles() 
     { 
      backgroundWorker = new BackgroundWorker(); 
      backgroundWorker.WorkerReportsProgress = true; 
      backgroundWorker.WorkerSupportsCancellation = true; 

      backgroundWorker.DoWork += (s, args) => 
      { 
       filesCopied = 0; 
       foreach (var copyFileTask in copyFileTasks) 
       { 
        if (backgroundWorker.CancellationPending) 
        { 
         args.Cancel = true; 
         return; 
        } 

        DateTime sourceFileLastWriteTime = File.GetLastWriteTime(copyFileTask.SourceFile); 
        DateTime targetFileLastWriteTime = File.GetLastWriteTime(copyFileTask.TargetFile); 

        if (sourceFileLastWriteTime != targetFileLastWriteTime) 
        { 
         Message2.Text = "dates are not the same"; 
        } 
        else 
        { 
         Message2.Text = "dates are the same"; 
        } 

        if (!File.Exists(copyFileTask.TargetFile)) 
         File.Copy(copyFileTask.SourceFile, copyFileTask.TargetFile); 

        currentPathAndFileName = copyFileTask.SourceFile; 

        UpdatePercentageFinished(); 
        backgroundWorker.ReportProgress((int)percentageFinished); 

        filesCopied++; 
       } 

      }; 

      backgroundWorker.ProgressChanged += (s, args) => 
      { 
       percentageFinished = args.ProgressPercentage; 
       ProgressBar.Value = percentageFinished; 
       Message.Text = percentageFinished + "% finished"; 
       CurrentFileCopying.Text = currentPathAndFileName; 
      }; 

      backgroundWorker.RunWorkerCompleted += (s, args) => 
      { 
       Button_Start.IsEnabled = true; 
       Button_Cancel.IsEnabled = false; 
       ProgressBar.Value = 0; 
       UpdatePercentageFinished(); 
       CurrentFileCopying.Text = ""; 

       if (percentageFinished < 100) 
       { 
        Message.Text = String.Format("cancelled at {0:0}% finished", percentageFinished); 
       } 
       else 
       { 
        Message.Text = "All files copied."; 
       } 
      }; 

      backgroundWorker.RunWorkerAsync(); 
     } 

     void UpdatePercentageFinished() 
     { 
      percentageFinished = (filesCopied/(float)totalFilesToCopy) * 100f; 
     } 


     class CopyFileTask 
     { 
      public string SourceFile { get; set; } 
      public string TargetFile { get; set; } 
      public CopyFileTask(string sourceFile, string targetFile) 
      { 
       SourceFile = sourceFile; 
       TargetFile = targetFile; 
      } 
     } 

     private void Button_Cancel_Click(object sender, RoutedEventArgs e) 
     { 
      backgroundWorker.CancelAsync(); 
     } 

    } 
} 

回答

7

你看过使用Dispatcher.Invoke吗?

Dispatcher.Invoke(new Action(() => { Button_Start.Content = i.ToString(); })); 

或者如果您想异步发生某事,请使用BeginInvoke。

+0

似乎完美谢谢 –

2

你最好的选择是继续使用.ReportProgress和.ProgressChanged。有没有特别的原因,这是不够的?

+0

不,它似乎是多余的拷贝“复制状态”为变量,然后变量为XAML元素,认为可能有办法复制“复制状态”到XAML元素直接 –

2

您无法直接从另一个线程访问UI。唯一的解决方案是在线程中引发事件,然后在UI线程中捕获它。

如果你不想使用BackgroundWorker线程,你需要这样的事情,以提高在线程的事件:其声明如下

 // Final update 
     if (Library_Finished != null) 
     { 
      Library_Finished(this, null); 
     } 

public event EventHandler Library_Finished; 

那么在UI线程中需要类似的东西来捕捉和处理事件:

private void Library_Finished(object sender, EventArgs e) 
    { 
     Action action =() => FinalUpdate(); 
     if (Thread.CurrentThread != Dispatcher.Thread) 
     { 
      Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, action); 
     } 
     else 
     { 
      action(); 
     } 
    } 

但即使使用BackgroundWorker,在访问UI元素之前仍需要实现线程检查代码。