October 6, 2010

General Purpose Progess Dialog

This is a general purpose progress dialog. It takes a background worker object as a parameter. Here is the Xaml for the dialog:
<Window x:Class="AsyncMessageDlg"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="AsyncMessageDlg" Height="314" Width="750"
         WindowStartupLocation="CenterOwner" 
         Closing="Window_Closing" Loaded="Window_Loaded">
    <Grid>
        <Ellipse Fill="Azure" Height="56" Margin="0,27,120,0" Name="ellipse2" 
        Stroke="Beige" VerticalAlignment="Top" HorizontalAlignment="Right" Width="82" />
        <Ellipse Fill="Azure" Height="56" Margin="120,0,0,20" Name="ellipse1" 
        Stroke="Beige" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="82" />
        <TextBox Margin="12,35,12,63" Name="textBox" IsReadOnly="True" 
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" />
        <Button Height="23" Margin="560,0,0,6" VerticalAlignment="Bottom" 
Name="butOK" HorizontalAlignment="Left" Width="75" IsDefault="True" Click="butOK_Click">_Ok</Button>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,6" 
Name="butCancel" VerticalAlignment="Bottom" Width="75" IsCancel="True" Click="butCancel_Click">_Cancel</Button>
        <Label Height="28" Margin="12,7,12,0" Name="label" 
VerticalAlignment="Top">Label</Label>
        <ProgressBar Height="16" Margin="120,0,181,41" Name="progressBar" 
VerticalAlignment="Bottom" Visibility="Hidden" />
        <TextBlock Height="16" HorizontalAlignment="Right" Margin="0,0,144,41" 
Name="tbProgressPercent" Text="0%" TextAlignment="Right" VerticalAlignment="Bottom" Width="31" Visibility="Hidden" />
    </Grid>
</Window>
The dialog takes a string for the dialog title and a string for the label above the output text window. When the background worker task sends a progress update it tries to convert the UserState object passed in the event to a string which is added to the output window. Here is the code for the dialog:
public partial class AsyncMessageDlg : Window
{
    BackgroundWorker m_WorkerThread = null;
    bool m_Completed = false;

    public AsyncMessageDlg(
        string textBoxTitle,
        string dlgTitle, 
        BackgroundWorker bw)
    {
        Debug.Assert(bw != null);
        InitializeComponent();
        this.Title = dlgTitle;
        this.label.Content = textBoxTitle;
        m_Completed = false;
        InitialiseBackgrounWorker(bw);
    }

    private void InitialiseBackgrounWorker(BackgroundWorker bw)
    {
        Debug.Assert(bw != null);
        Debug.Assert(bw.WorkerSupportsCancellation);
        this.m_WorkerThread = bw;
        //if (m_WorkerThread.WorkerReportsProgress)
        butCancel.IsEnabled = m_WorkerThread.WorkerSupportsCancellation;
        butOK.IsEnabled = false;
        tbProgressPercent.Visibility = Visibility.Visible;
        progressBar.Visibility = Visibility.Visible;

        m_WorkerThread.ProgressChanged += new 
            ProgressChangedEventHandler(
              m_WorkerThread_ProgressChanged);
        m_WorkerThread.RunWorkerCompleted += new 
            RunWorkerCompletedEventHandler(
              m_WorkerThread_RunWorkerCompleted);
    }

    void m_WorkerThread_RunWorkerCompleted(object sender, 
        RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
            return;
        m_Completed = true;
        string result = e.Result as string;
        if (result != null)
        {
            label.Content = result as string;
        }
        progressBar.Value = progressBar.Maximum;
        tbProgressPercent.Text = "100%";
        butOK.IsEnabled = true;
        butCancel.IsEnabled = false;
    }

    void m_WorkerThread_ProgressChanged(object sender, 
        ProgressChangedEventArgs e)
    {
        Debug.Assert((e.ProgressPercentage >= 0) &&
            (e.ProgressPercentage <= 100));
        progressBar.Value = e.ProgressPercentage;
        tbProgressPercent.Text = e.ProgressPercentage.ToString() +
            "%";
        if (e.UserState != null) 
        {
            if (e.UserState is string)
            {
                textBox.Text += e.UserState as string;
            }
            else
            {
                textBox.Text += e.UserState.ToString();
            }
        }
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        m_WorkerThread.RunWorkerAsync();
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
        CancelWorkerThread();
        m_WorkerThread.Dispose();
    }

    private void butOK_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = m_Completed;
        this.Close();
    }

    private void butCancel_Click(object sender, RoutedEventArgs e)
    {
        if (!m_Completed)
        {
            CancelWorkerThread();
        }
        DialogResult = m_Completed;
    }

    private void CancelWorkerThread()
    {
        if (m_WorkerThread.IsBusy && 
            m_WorkerThread.WorkerSupportsCancellation)
            m_WorkerThread.CancelAsync();
    }
}
Here is the code for a sample background worker
// Background worker to check out some files
internal class AsynchAddCheckOutFiles : BackgroundWorker
{
    List<FileSystemInfo> m_fileSystemInfoList;

    // Pass required info in through constructor, cant change it
    // whilst a search is underway
    public AsynchAddCheckOutFiles(ref List<FileSystemInfo> fileSystemInfoList) :
        base()
    {
        this.m_fileSystemInfoList = fileSystemInfoList;
        // Set clients refernce to null, we own the list.
        fileSystemInfoList = null; 
        this.WorkerReportsProgress = true;
        this.WorkerSupportsCancellation = true;
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        bool cancelled = false;
        double progress = 0;
        double prevProgress = 0;

        int m_NumberFiles = m_fileSystemInfoList.Count;
        int count = 0;
        if (!cancelled)
        {
            foreach (FileSystemInfo fsi in m_fileSystemInfoList)
            {
                if (CancellationPending)
                {
                    cancelled = true;
                    break;
                }

                Perforce.P4CommandTransaction res = 
                    Perforce.Instance.AddCheckOutFile(fsi);
                string state = res.ToString();

                ++count;
                // progress as a percentage
                progress = (100.0 * (double)count /
                    (double)m_NumberFiles) + 0.5;
                // Report every 1% of progress
                if ((progress - prevProgress) > 1.0)
                {
                    ReportProgress((int)(progress), state);
                    prevProgress = progress;
                }
                
            }
        }
        e.Cancel = cancelled;
    }

}

No comments: