Asynchronous "Loading..." text in WPF

Asked by At

I'm trying to get a TextBlock to show up on screen with the words "Loading..." with the number of dots changing every half a second to indicate that a file is currently being parsed. Unfortunately, it doesn't animate like I want it to when the file is being parsed. This is what I have currently:

private void MainWindow_MIDIBrowseClick(object sender, RoutedEventArgs e)
{
    MIDIBrowseClick?.Invoke(this, e);

    OpenFileDialog browseDialog = new OpenFileDialog
    {
        Filter = "MIDI files (*.mid)|*.mid|All files (*.*)|*.*"
    };

    if (browseDialog.ShowDialog() == true)
    {
        try
        {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { ShowLoadingText(tokenSource.Token); }));

            MIDIParser midiParse = new MIDIParser(File.ReadAllBytes(browseDialog.FileName));

            midiParse.fileName = browseDialog.SafeFileName;

            midiParse.ParseFile();
            NoteParser noteParse = new NoteParser(midiParse);
            noteParse.ParseEvents();

            tokenSource.Cancel();
            DataContext = new PianoRollView(midiParse, noteParse);
        }
        catch (InvalidOperationException)
        {
            MessageBox.Show("Error parsing MIDI file!", "Error");
        }
    }
}

That calls the below method which animates the "Loading..." text:

private async void ShowLoadingText(CancellationToken token)
{
    txtLoading.Visibility = Visibility.Visible;

    try
    {
        while (!token.IsCancellationRequested)
        {
            txtLoading.Text = "Loading";
            await Task.Delay(500, token);
            txtLoading.Text = "Loading.";
            await Task.Delay(500, token);
            txtLoading.Text = "Loading..";
            await Task.Delay(500, token);
            txtLoading.Text = "Loading...";
            await Task.Delay(500, token);
        }
    }
    catch (TaskCanceledException)
    {
        txtLoading.Visibility = Visibility.Hidden;
    }
}

Not sure what I'm doing wrong. Any help would be appreciated!

1 Answers

1
Community On

It seems like you try to invoke the parsing operation from the UI thread synchronously. That's why it locked UI and the text isn't animated. You should try to add an asynchronous invocation of your parsing operation. Something like this:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { ShowLoadingText(tokenSource.Token); }));

            await Task.Run(() => Thread.Sleep(5000)); // your parsing operation

            tokenSource.Cancel();
        }

        private async void ShowLoadingText(CancellationToken token)
        {
            txtLoading.Visibility = Visibility.Visible;

            try
            {
                while (!token.IsCancellationRequested)
                {
                    txtLoading.Text = "Loading";
                    await Task.Delay(500, token);
                    txtLoading.Text = "Loading.";
                    await Task.Delay(500, token);
                    txtLoading.Text = "Loading..";
                    await Task.Delay(500, token);
                    txtLoading.Text = "Loading...";
                    await Task.Delay(500, token);
                }
            }
            catch (TaskCanceledException)
            {
                txtLoading.Visibility = Visibility.Hidden;
            }
        }
    }
}

In the sample above you will see Loading animation with 5 sec duration. This is achieved by asynchronous invocation of Thread.Sleep(5000). In your case you should write a method which will implement parsing operation:

    private void Parse()       
    {
        MIDIParser midiParse = new MIDIParser(File.ReadAllBytes(browseDialog.FileName));

        midiParse.fileName = browseDialog.SafeFileName;

        midiParse.ParseFile();
        NoteParser noteParse = new NoteParser(midiParse);
        noteParse.ParseEvents();
    }

And replace Thread.Sleep(5000) with its invocation.