Random programming things I'd want to remember

Monday, October 15, 2012

Responsive UI while uploading stuff in the background Part 3


In this final post, I will make sure that the form can do work on the background, that it can inform the user about the progress, and that the user can cancel the job at any time. I will build on the code that I posted in parts 1 and 2. At part 2, I left the form fully responsive while it was doing work, but without an ability for the user to cancel work. In order to be able to cancel, I will add a Cancel property to my Uploader class like so:

        public bool Cancel { get; set; }

Then, I will modify my UploadFiles method to check whether the work has been cancelled and quit the upload if needed.

        public void UploadFiles()
        {
            int partTracker = 0;

            if (OnStartingUpload != null) //Here I need number of pieces that will be uploaded
                OnStartingUpload(this, new UploaderEventArgs 
                {  
                    DataToUpload_Length = this.DataToUpload_Length, 
                    NumberOfFilesToUpload = FileNames.Count,
                    PartsToUpload_TotalCount = this.DataToUpload_Length,
                    PartsToUpload_UploadedSoFar = 0
                });

            for (int i = 0; i < FileNames.Count; i++)
            {                
                if (Cancel) 
                    break;

                //Log.UploadingFile(FileNames[i]);
                if (OnStartingFileUpload != null) //here I need the number of pieces in the file that will be uploaded, and file name
                    //OnStartingFileUpload(this, new UploaderEventArgs {NumberOfFilesToUpload=FileNames.Count, FileName = FileNames[i], FileUpload = true, FilePartUpload = false, PartsToUpload = 0, PartsUploaded = 0 });
                    OnStartingFileUpload(this, new FileUploaderEventArgs { FileName = FileNames[i], Cancelled=this.Cancel/*, PartsInFile = FileNames[i].Length*/ });


                for (int k = 0; k < FileNames[i].Length; k++)
                {
                    if (Cancel) 
                        break;
                    //Log.UploadingPartAOfB(k+1, partsPerFile);
                    if (OnStartingFilePartUpload != null) //here I need the number of pieces in the file that were uploaded so far and total number of pieces to upload
                        //OnStartingFilePartUpload(this, new UploaderEventArgs { NumberOfFilesToUpload = FileNames.Count, FileName = FileNames[i], FileUpload = false, FilePartUpload = true, PartsToUpload = partsPerFile, PartsUploaded = k });
                        OnStartingFilePartUpload(this, new FilePartUploaderEventArgs { PartsUploadedSoFar = k, PartsToUpload = FileNames[i].Length, FileName = FileNames[i], Cancelled = this.Cancel });

                    Thread.Sleep(new TimeSpan(partUploadDelayNs*1000));
                    
                    partTracker += 1;

                    //Log.UploadedPartAOfB(k+1, partsPerFile);
                    if (OnFinishedFilePartUpload != null) //here I need the number of pieces in the file that were uploaded so far and total number of pieces to upload
                        //OnFinishedFilePartUpload(this, new UploaderEventArgs { NumberOfFilesToUpload = FileNames.Count, FileName = FileNames[i], FileUpload = false, FilePartUpload = true, PartsToUpload = partsPerFile, PartsUploaded = k });
                        OnFinishedFilePartUpload(this, new FilePartUploaderEventArgs { PartsUploadedSoFar = k + 1, PartsToUpload = FileNames[i].Length, FileName = FileNames[i], Cancelled = this.Cancel });
                }

                if (OnFinishedFileUpload != null) //Here I need the file name
                    //OnFinishedFileUpload(this, new UploaderEventArgs { NumberOfFilesToUpload = FileNames.Count, FileName = FileNames[i], FileUpload = true, FilePartUpload = false, PartsToUpload = 0, PartsUploaded = 0 });
                    OnFinishedFileUpload(this, new FileUploaderEventArgs { FileName = FileNames[i], Cancelled = this.Cancel/*, PartsInFile = FileNames[i].Length*/ });
            }

            if (OnFinishedUpload != null) //Here I need the number of files uploaded
                OnFinishedUpload(this, new UploaderEventArgs { NumberOfFilesToUpload = FileNames.Count, Cancelled = this.Cancel });
        }

Since I also notify about cancellation in the raised events, I need to update the EventArgs classes:

    public class FileUploaderEventArgs : EventArgs
    { 
        public string FileName { get; set; }
        public bool Cancelled { get; set; }
        //public int PartsInFile { get; set; }
    }

    public class FilePartUploaderEventArgs : EventArgs
    {
        //Number of parts uploaded so far, total number of parts to upload
        public int PartsUploadedSoFar { get; set; }
        public int PartsToUpload { get; set; }
        public string FileName { get; set; }
        public bool Cancelled { get; set; }
    }

    public class UploaderEventArgs : EventArgs
    {
        public int PartsToUpload_TotalCount { get; set; }
        public int PartsToUpload_UploadedSoFar { get; set; }

        public int DataToUpload_Length { get; set; }
        public int DataToUpload_Uploaded { get; set; }
        public int NumberOfFilesToUpload { get; set; }

        public string FileName { get; set; }
        public int PartsToUpload { get; set; }
        public int PartsUploaded { get; set; }

        public bool FileUpload { get; set; }
        public bool FilePartUpload { get; set; }
        public bool Cancelled { get; set; }
    }

Finally, in the stop button event handler, I need to set the Cancel property of the Uploader global variable to true:

        private void stopButton_Click(object sender, RoutedEventArgs e)
        {
            u.Cancel = true;

            if (bg1.IsBusy)
                bg1.CancelAsync();

            overallStatusLabel.Content = "Cancelling upload";
        }

And now I have a template for a work that is done on the background that can be cancelled at any time.

Thursday, October 11, 2012

Responsive UI while uploading stuff in the background Part 2


In my previous post, I showed a program that is supposed to be doing stuff on the background, even though it was not updating the UI. In this post, I will show how to make it so that we can see the changes in the UI while the program is doing its magic.

Most of the program stayed the same, the only difference is in the MainWindow.xaml.cs. The secret weapon here is System.ComponentModel.BackgroundWorker. To successfully launch a background worker, put the time-consuming operation into the code for the worker's DoWork event. I need the interface updates here, and since my Uploader class is doing time-consuming work on another thread, I wrapped up the calls for the UI update into the Dispatcher.BeginInvoke(... code blocks. The only thing that is not working here is the event for the Stop button. It calls the background worker's CancelAsync() method, but the Uploader class cannot interrupt its actions. I will show a way to fix that in the next post.

    public partial class MainWindow : Window
    {
        Stopwatch s = new Stopwatch();
        Uploader u = new Uploader();
        BackgroundWorker bg1 = new BackgroundWorker();

        public MainWindow()
        {
            InitializeComponent();
            InitializeUploaderEvents();
            InitializeBackgroundWorker();
        }

        public void InitializeUploaderEvents()
        {
            u.OnStartingUpload += new StartingUpload(u_OnStartingUpload);
            u.OnFinishedUpload += new FinishedUpload(u_OnFinishedUpload);
            u.OnStartingFileUpload += new StartingFileUpload(u_OnStartingFileUpload);
            u.OnFinishedFileUpload += new FinishedFileUpload(u_OnFinishedFileUpload);
            u.OnStartingFilePartUpload += new StartingFilePartUpload(u_OnStartingFilePartUpload);
            u.OnFinishedFilePartUpload += new FinishedFilePartUpload(u_OnFinishedFilePartUpload);
        }

        private void InitializeBackgroundWorker()
        {
            bg1.DoWork += new DoWorkEventHandler(bg1_DoWork);
            bg1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg1_RunWorkerCompleted);
            bg1.WorkerReportsProgress = true;
            bg1.WorkerSupportsCancellation = true;
        }

        void bg1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
                MessageBox.Show(e.Error.Message);
            else if (e.Cancelled)
                overallStatusLabel.Content = "Upload cancelled";
            else
            {
                overallStatusLabel.Content = String.Format("Upload finished successfully, took {0} ", s.Elapsed);
            }
        }

        void bg1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker b = sender as BackgroundWorker;
            Uploader u1 = e.Argument as Uploader; //I am not using this variable yet

            u.UploadFiles();
        }
        
        void u_OnStartingFilePartUpload(object source, FilePartUploaderEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
            {
                currentFileStatusLabel.Content = String.Format("Uploading file {0}, {1}/{2} parts done.", e.FileName, e.PartsUploadedSoFar, e.PartsToUpload);
                fileUploadProgressBar.Maximum = e.PartsToUpload;
                fileUploadProgressBar.Value = e.PartsUploadedSoFar;
            });
        }

        void u_OnFinishedFilePartUpload(object source, FilePartUploaderEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
             {
                 currentFileStatusLabel.Content = String.Format("Uploading file {0}, {1}/{2} parts done.", e.FileName, e.PartsUploadedSoFar, e.PartsToUpload);
                 progressBar1.Value += 1;
                 fileUploadProgressBar.Maximum = e.PartsToUpload;
                 fileUploadProgressBar.Value = e.PartsUploadedSoFar;
             });
        }

        void u_OnStartingFileUpload(object source, FileUploaderEventArgs e)
        {
           Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
            { 
                currentFileStatusLabel.Content = String.Format("Starting to upload file {0}", e.FileName); 
            });
        }

        void u_OnFinishedFileUpload(object source, FileUploaderEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
            {
                currentFileStatusLabel.Content = String.Format("Finished uploading file {0}", e.FileName); 
            });
        }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            progressBar1.Maximum = u.DataToUpload_Length;
            progressBar1.Value = 0;
            bg1.RunWorkerAsync();
        }

        void u_OnFinishedUpload(object source, UploaderEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
               {
                   s.Stop();
                   overallStatusLabel.Content = String.Format("Finished uploading {0} files, elapsed time: {1}", e.NumberOfFilesToUpload, s.Elapsed);
               });
        }

        void u_OnStartingUpload(object source, UploaderEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
            {
                overallStatusLabel.Content = String.Format("Started uploading {0} files", e.NumberOfFilesToUpload);
                progressBar1.Maximum = e.PartsToUpload_TotalCount;
                progressBar1.Value = e.PartsToUpload_UploadedSoFar; //0
                s.Start();
            });
        }

        private void stopButton_Click(object sender, RoutedEventArgs e)
        {
            if (bg1.IsBusy)
                bg1.CancelAsync();

            overallStatusLabel.Content = "Cancelling upload";
        }
    }


Monday, October 8, 2012

Responsive UI while uploading stuff in the background part 1


I was working on a program that required multiple uploads to be done in the background while updating the progress status for the user. I decided to go with WPF and C#. In this post, I will provide an outline for the program that simulates the upload without updating the interface. In the next posts I will explain how to update the UI and provide mechanisms to cancel uploads. Let's start with the interface:

<Window x:Class="BackgroundWorkerExperiment.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Button x:Name="startButton" Content="Start" Click="startButton_Click" Margin="5" />
        <Button x:Name="stopButton" Content="Stop" Click="stopButton_Click" Grid.Row="1" Margin="5" />
        <StackPanel Orientation="Horizontal" Grid.Row="2">
            <ProgressBar x:Name="progressBar1" Margin="5" Width="150" />
            <Label x:Name="overallStatusLabel" Margin="5" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="3">
            <ProgressBar x:Name="fileUploadProgressBar" Margin="5" Width="150" />
            <Label x:Name="currentFileStatusLabel" Margin="5" />
        </StackPanel>
    </Grid>
</Window>

Pretty simple form. Next, the class with helper methods that would "generate" some files:

    public static class Helpers
    {
        private static List<char> CharacterVocabulary_Get()
        {
            List<char> vocabulary = new List<char>();

            for (int i = 48; i <= 57; i++)
            {
                char a = (char)(i);
                vocabulary.Add(a);
            }

            for (int i = 64; i < 90; i++)
            {
                char a = (char)(i);
                vocabulary.Add(a);
            }
            for (int i = 97; i <= 122; i++)
            {
                char a = (char)(i);
                vocabulary.Add(a);
            }

            return vocabulary;
        }

        public static List<string> GetListOfNStringsOfKLength(int stringCount, int stringLength)
        {
            List<string> result = new List<string>();

            Random r = new Random();
            List<char> characters = CharacterVocabulary_Get();            

            for (int k = 1; k <= stringCount; k++)
            {
                StringBuilder sb = new StringBuilder(k.ToString(), stringLength+1);

                for (int j = 0; j < stringLength; j++)
                {
                    sb.Append(characters[r.Next(characters.Count)]);

                }
                result.Add(sb.ToString());
            }

            return result;
        }

        public static List<string> GetListOfNStringsOfVariableLength(int stringCount, int minStringLength, int maxStringLength)
        {
            List<string> result = new List<string>();

            Random r = new Random();
            List<char> characters = CharacterVocabulary_Get();

            for (int k = 1; k <= stringCount; k++)
            {
                int strLength = r.Next(minStringLength, maxStringLength);

                StringBuilder sb = new StringBuilder(k.ToString(), strLength+1);

                for (int j = 0; j < strLength; j++)
                {
                    sb.Append(characters[r.Next(characters.Count)]);

                }
                result.Add(sb.ToString());
            }
            return result;
        }

    }

Now, the class that does the simulated upload. It may look complex, but it has a lot of events that it raises before and after each of the meaningful states. First, the class signals that it starts the uploading process. Then, it signals that it starts uploading a certain file. Then, it signals that it starts uploading a part of that file. Then, after some time, it signals that it finished uploading the part of the file. The class also lets its user know that it finished uploading a file, and it finished uploading everything.

    public delegate void StartingUpload(object source, UploaderEventArgs e);
    public delegate void FinishedUpload(object source, UploaderEventArgs e);

    public delegate void StartingFileUpload(object source, FileUploaderEventArgs e);
    public delegate void FinishedFileUpload(object source, FileUploaderEventArgs e);

    public delegate void StartingFilePartUpload(object source, FilePartUploaderEventArgs e);
    public delegate void FinishedFilePartUpload(object source, FilePartUploaderEventArgs e);

    class Uploader
    {
        public List<string> FileNames { get; set; }
        public int DataToUpload_Length
        {
            get
            {
                int ret = 0;
                if (FileNames == null)
                    return ret;
                else
                {
                    foreach (string s in FileNames)
                    {
                        ret += s.Length;
                    }
                    return ret;
                }
            }
        }

        private int partsPerFile = 5;
        private long partUploadDelayNs = 10000;

        public event StartingUpload OnStartingUpload;
        public event FinishedUpload OnFinishedUpload;

        public event StartingFileUpload OnStartingFileUpload;
        public event FinishedFileUpload OnFinishedFileUpload;

        public event StartingFilePartUpload OnStartingFilePartUpload;
        public event FinishedFilePartUpload OnFinishedFilePartUpload;

        public Uploader()
        {
            FileNames = Helpers.GetListOfNStringsOfVariableLength(5, 8, 15);
        }

        public Uploader(List<string> fileNames)
        {
            FileNames = fileNames;
        }

        public void UploadFiles()
        {
            int partTracker = 0;

            if (OnStartingUpload != null) //Here I need number of pieces that will be uploaded
                OnStartingUpload(this, new UploaderEventArgs 
                {  
                    DataToUpload_Length = this.DataToUpload_Length, 
                    NumberOfFilesToUpload = FileNames.Count,
                    PartsToUpload_TotalCount = this.DataToUpload_Length,
                    PartsToUpload_UploadedSoFar = 0
                });

            for (int i = 0; i < FileNames.Count; i++)
            {
                //Log.UploadingFile(FileNames[i]);
                if (OnStartingFileUpload != null) //here I need the number of pieces in the file that will be uploaded, and file name
                    OnStartingFileUpload(this, new FileUploaderEventArgs { FileName = FileNames[i]/*, PartsInFile = FileNames[i].Length*/ });

                for (int k = 0; k < partsPerFile; k++)
                {
                    //Log.UploadingPartAOfB(k+1, partsPerFile);
                    if (OnStartingFilePartUpload != null) //here I need the number of pieces in the file that were uploaded so far and total number of pieces to upload
                        OnStartingFilePartUpload(this, new FilePartUploaderEventArgs { PartsUploadedSoFar = k, PartsToUpload = FileNames[i].Length, FileName = FileNames[i] });

                    Thread.Sleep(new TimeSpan(partUploadDelayNs*100));
                    
                    partTracker += 1;

                    //Log.UploadedPartAOfB(k+1, partsPerFile);
                    if (OnFinishedFilePartUpload != null) //here I need the number of pieces in the file that were uploaded so far and total number of pieces to upload
                        OnFinishedFilePartUpload(this, new FilePartUploaderEventArgs { PartsUploadedSoFar = k + 1, PartsToUpload = FileNames[i].Length, FileName = FileNames[i] });
                }

                if (OnFinishedFileUpload != null) //Here I need the file name
                     OnFinishedFileUpload(this, new FileUploaderEventArgs { FileName = FileNames[i]/*, PartsInFile = FileNames[i].Length*/ });
            }

            if (OnFinishedUpload != null) //Here I need the number of files uploaded
                OnFinishedUpload(this, new UploaderEventArgs { NumberOfFilesToUpload = FileNames.Count });
        }
    }

    public class FileUploaderEventArgs : EventArgs
    { 
        public string FileName { get; set; }
    }

    public class FilePartUploaderEventArgs : EventArgs
    {
        public int PartsUploadedSoFar { get; set; }
        public int PartsToUpload { get; set; }
        public string FileName { get; set; }
    }

    public class UploaderEventArgs : EventArgs
    {
        public int PartsToUpload_TotalCount { get; set; }
        public int PartsToUpload_UploadedSoFar { get; set; }

        public int DataToUpload_Length { get; set; }
        public int DataToUpload_Uploaded { get; set; }
        public int NumberOfFilesToUpload { get; set; }

        public string FileName { get; set; }
        public int PartsToUpload { get; set; }
        public int PartsUploaded { get; set; }

        public bool FileUpload { get; set; }
        public bool FilePartUpload { get; set; }
    }

And now it is time to look at the code behind the main form. Nothing fancy here either. Wire up the Uploader class, let the user know what's happening in the events that it raises, make sure the start button initializes the simulation.

    public partial class MainWindow : Window
    {
        Stopwatch s = new Stopwatch();
        Uploader u = new Uploader();

        public MainWindow()
        {
            InitializeComponent();
            InitializeUploaderEvents();
        }

        public void InitializeUploaderEvents()
        {
            u.OnStartingUpload += new StartingUpload(u_OnStartingUpload);
            u.OnFinishedUpload += new FinishedUpload(u_OnFinishedUpload);
            u.OnStartingFileUpload += new StartingFileUpload(u_OnStartingFileUpload);
            u.OnFinishedFileUpload += new FinishedFileUpload(u_OnFinishedFileUpload);
            u.OnStartingFilePartUpload += new StartingFilePartUpload(u_OnStartingFilePartUpload);
            u.OnFinishedFilePartUpload += new FinishedFilePartUpload(u_OnFinishedFilePartUpload);
        }
        
        void u_OnStartingFilePartUpload(object source, FilePartUploaderEventArgs e)
        {
            currentFileStatusLabel.Content = String.Format("Uploading file {0}, {1}/{2} parts done.", e.FileName, e.PartsUploadedSoFar, e.PartsToUpload);
        }

        void u_OnFinishedFilePartUpload(object source, FilePartUploaderEventArgs e)
        {
            currentFileStatusLabel.Content = String.Format("Uploading file {0}, {1}/{2} parts done.", e.FileName, e.PartsUploadedSoFar, e.PartsToUpload);
        }

        void u_OnStartingFileUpload(object source, FileUploaderEventArgs e)
        {
            currentFileStatusLabel.Content = String.Format("Starting to upload file {0}", e.FileName);
        }

        void u_OnFinishedFileUpload(object source, FileUploaderEventArgs e)
        {
            currentFileStatusLabel.Content = String.Format("Finished uploading file {0}", e.FileName);
        }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            progressBar1.Maximum = u.DataToUpload_Length;
            progressBar1.Value = 0;
            u.UploadFiles();
        }

        void u_OnFinishedUpload(object source, UploaderEventArgs e)
        {
            s.Stop();
            overallStatusLabel.Content = String.Format("Finished uploading {0} files, elapsed time: {1}", e.NumberOfFilesToUpload, s.Elapsed);
        }

        void u_OnStartingUpload(object source, UploaderEventArgs e)
        {
            overallStatusLabel.Content = String.Format("Started uploading {0} files", e.NumberOfFilesToUpload);
            progressBar1.Maximum = e.PartsToUpload_TotalCount;
            progressBar1.Value = e.PartsToUpload_UploadedSoFar; //0
            s.Start();
        }

        private void stopButton_Click(object sender, RoutedEventArgs e)
        {

        }
    }

However, if you put this all together and hit the start button, there will be no feedback. The form will freeze and after a while it will unfreeze letting you know that the upload finished, but without letting you know what was happening during the upload process. Stay tuned on how to fix this.