I'm creating a backup utility in WPF and have a general question about threading:
In the method backgroundWorker.DoWork(), the statement Message2.Text = "..." gives the error "The calling thread cannot access this object because a different thread owns it.".
Is there no way for me to directly access the UI thread within backgroundWorker.DoWork(), i.e. change text in a XAML TextBox at that point? Or do I need to store all display information in an internal variable, and then display it in backgroundWorker.ProgressChanged(), as I had to do with e.g. 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>
code-behind:
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();
}
}
}