October 6, 2010

More Complex Progress Dialog

This sample does a file search asynchronously. At the end of the search the progress dialog pauses and closes itself. Internally a timer is used to initiate this automatic close of the progress dialog. AsynchFileFinder is the BackgroundWorker derived object. Here is th Xaml for the dialog:
<Window x:Class="FindingFilesDlg"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Finding modified files" Height="167" Width="514" Closing="Window_Closing" Loaded="Window_Loaded">
    <Grid>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="butCancel" VerticalAlignment="Bottom" Width="91" Click="butCancelDone_Click">_Cancel</Button>
        <TextBlock Height="21" Margin="12,15,0,0" Name="tbActivity" VerticalAlignment="Top"  Text="Searching for modified files:" HorizontalAlignment="Left" Width="Auto" />
        <ProgressBar Margin="12,61,0,52" Name="progressBar" HorizontalAlignment="Left" Width="427" />
        <TextBlock Margin="0,61,16,52" Name="tbPercentage" Text="0%" HorizontalAlignment="Right" Width="31" TextAlignment="Right" />
    </Grid>
</Window>
Here is the code for the dialog:
public partial class FindingFilesDlg : Window
{
    AsyncFileFinder m_AsyncFileFinder = null;
    DispatcherTimer m_Timer = new DispatcherTimer();
    IEnumerable<FileSystemInfo> m_FilesFound = 
               new FileSystemInfo[0];
    int countDown = 1;
    const int SECOND = 10000000;
    bool m_Completed = false;

    public FindingFilesDlg(string rootDir, FileSearchRules fsr)
    {
        InitializeComponent();
        tbActivity.Text = "Preparing to search \'" +
            rootDir + "\' for modified files:";

        InitialiseTimer();
        InitialiseAsyncFileFinder(rootDir, fsr);
    }

    private void InitialiseAsyncFileFinder(
        string rootDir, 
        FileSearchRules fsr)
    {
        // The BackgroundWorker derived object
        m_AsyncFileFinder = new AsyncFileFinder(rootDir, fsr);
        m_AsyncFileFinder.ProgressChanged +=
            new ProgressChangedEventHandler(
                AsynchFileFinder_ProgressChanged);
        m_AsyncFileFinder.RunWorkerCompleted += new
            RunWorkerCompletedEventHandler(
            AsynchFileFinder_RunWorkerCompleted);
    }

    private void InitialiseTimer()
    {
        // Disable timer
        m_Timer.IsEnabled = false;
        // Set timer event interval
        m_Timer.Interval = new TimeSpan((long)(1 * SECOND));
        // Timer events
        m_Timer.Tick += new EventHandler(timer_Tick);
    }

    public IEnumerable<FileSystemInfo> FilesFound
    {
        get { return m_FilesFound; }
    }

    void AsynchFileFinder_RunWorkerCompleted(
        object sender, 
        RunWorkerCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            m_Completed = true;
            m_FilesFound = m_AsyncFileFinder.Files;
            tbActivity.Text = "Finished searching \'" + 
                m_AsyncFileFinder.SearchDirectory + 
                "\', " + m_FilesFound.Count().ToString() + 
                " files found";
            progressBar.Value = progressBar.Maximum;
            tbPercentage.Text = "100%";
            UpdateDoneButton();
            // wait several seconds before closing the dialog
            m_Timer.Start(); 
        }
    }

    void UpdateDoneButton()
    {
        string str = "_Done (" + 
              countDown.ToString() + ")";
        butCancel.Content = str;
    }
    
    bool textUpdated = false;
    void AsynchFileFinder_ProgressChanged(object sender, 
        ProgressChangedEventArgs e)
    {
        Debug.Assert((e.ProgressPercentage >= 0) && 
            (e.ProgressPercentage <= 100));
        progressBar.Value = e.ProgressPercentage;
        tbPercentage.Text = e.ProgressPercentage.ToString() +
                "%";
        if ((e.ProgressPercentage > 1) && !textUpdated)
        {
            textUpdated = true;
            tbActivity.Text = "Searching \'" + 
                m_AsyncFileFinder.SearchDirectory + 
                "\' for modified files (" + 
                m_AsyncFileFinder.NumFilesToSearch.ToString() + 
                " files to search) :";
        }
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
        CancelAsyncFileFinder();
        if (m_Timer.IsEnabled)
            StopTimer();
        m_AsyncFileFinder.Dispose();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        m_AsyncFileFinder.RunWorkerAsync();
        tbActivity.Text = "Searching \'" + 
            m_AsyncFileFinder.SearchDirectory + 
            "\' for modified files:";
    }

    // wait several seconds before closing the dialog
    void timer_Tick(object sender, EventArgs e)
    {
        --countDown;
        UpdateDoneButton();
        if (countDown == 0)
        {
            StopTimer();
            DialogResult = m_Completed;
        }
    }

    private void StopTimer()
    {
        m_Timer.Stop();
    }

    private void butCancelDone_Click(object sender, 
          RoutedEventArgs e)
    {
        if (!m_Completed)
        {
            CancelAsyncFileFinder();
            if (m_Timer.IsEnabled)
                StopTimer();
        }
        DialogResult = m_Completed;
    }

    private void CancelAsyncFileFinder()
    {
        if (m_AsyncFileFinder.IsBusy)
            m_AsyncFileFinder.CancelAsync();
    }
}

Here is the code for the background worker object:
internal class AsyncFileFinder : BackgroundWorker
{
    private FileSearchRules FileSearchRules { get; set; }
    string m_RootDir = string.Empty;
    List<System.IO.FileSystemInfo> files = new 
        List<System.IO.FileSystemInfo>();
    int m_NumberFilesToSearch = 0;

    // Pass required info in through constructor, 
    // cant change it whilst a search is underway
    public AsyncFileFinder(string rootDir, 
        FileSearchRules fsr) : base()
    {
        this.WorkerReportsProgress = true;
        this.WorkerSupportsCancellation = true;
        m_RootDir = rootDir;
        FileSearchRules = new FileSearchRules(fsr);
    }

    public string SearchDirectory
    {
        get { return m_RootDir; }
    }

    public IEnumerable<FileSystemInfo> Files
    {
        get
        {
            if (IsBusy) // No access while thread active
                return new List<FileSystemInfo>();
            else
                return files.AsReadOnly();
        }
    }

    // Total number of files that will be checked
    public int NumFilesToSearch
    {
        get
        {
            return m_NumberFilesToSearch;
        }
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        bool cancelled = false;
        double progress = 0;
        double prevProgress = 0;
        files = new List<FileSystemInfo>();
        DirectoryInfo targ = new DirectoryInfo(m_RootDir);
        // Count the number of files to process first
        foreach (System.IO.FileSystemInfo fsi in
            targ.IterateFiles(true))
        {
            if (CancellationPending)
            {
                cancelled = true;
                break;
            }
            
            Interlocked.Increment(ref 
                m_NumberFilesToSearch);
        }
        int count = 0;
        if (!cancelled)
        {
            foreach (FileSystemInfo fsi in 
                targ.IterateFiles(true))
            {
                if (CancellationPending)
                {
                    cancelled = true;
                    break;
                }

                ++count;
                // progress as a percentage
                progress = (100.0 * (double)count /
                    (double)m_NumberFilesToSearch) + 0.5;
                // Report every 1% of progress
                if ((progress - prevProgress) > 1.0)
                {
                    ReportProgress((int)(progress));
                    prevProgress = progress;
                }

                if (FileSearchRules.PassesRules(fsi))
                {
                    files.Add(fsi);
                }
            }
        }
        e.Cancel = cancelled;
    }
 
}

No comments: