Ambie
Ambie

Reputation: 4977

MVVMLight WPF ProgressBar binding in asynchronous Task

Can anyone tell me why my ProgressBar is failing to update until the Task ends?

The ProgressBar value is bound to my ViewModel property, called ProgressCurrentValue; and the routine is within a Task. I've also tried updating the ProgressCurrentValue on the UI Thread, using MVVMLight's DispatcherHelper.CheckBeginInvokeOnUI() function, but still with no success. I'm sure I'm missing something obvious; can anyone see it?

The view

<UserControl x:Class="xxxxxx.Views.ProcessingView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:xxxxxx.Views"
             mc:Ignorable="d" 
             DataContext="{Binding ProcessingVM, Source={StaticResource Locator}}"
             x:Name="RunningControl">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ProgressBar Grid.Row="0" Maximum="{Binding ProgressMaximum}" Minimum="{Binding ProgressMinimum}" Value="{Binding ProgressCurrentValue}" Height="20" />
        <!-- etc. -->
    </Grid>
</UserControl>

The ViewModel (relevant parts)

    private int _progressMinimum;
    /// <summary>
    ///   Gets or sets the minimum of the progress control.
    /// </summary>
    public int ProgressMinimum
    {
        get { return _progressMinimum; }
        set { Set(ref _progressMinimum, value); }
    }

    private int _progressMaximum;
    /// <summary>
    ///   Gets or sets the maximum of the progress control.
    /// </summary>
    public int ProgressMaximum
    {
        get { return _progressMaximum; }
        set { Set(ref _progressMaximum, value); }
    }

    private int _progressCurrentValue;
    /// <summary>
    ///   Gets or sets the current value of the progress control.
    /// </summary>
    public int ProgressCurrentValue
    {
        get { return _progressCurrentValue; }
        set { Set(ref _progressCurrentValue, value); }
    }

    private async Task ProcessCSVFiles(string[] fullpaths)
    {
        // Get the current settings for use in the data processing.
        ObjectStringPair setting = await _service.FirstSettingAsync();
        if (!(setting.Item is Setting s))
        {
            await SendFlashMessageAsync(new FlashMessage("Unable to locate settings.", AlertLevels.Danger));
            return;
        }

        // Set the progress parameters.
        ProgressProcessText = "Processing " + ((fullpaths.Length == 1) ? Path.GetFileName(fullpaths[0]) : "multiple files") + " ...";
        ProgressMinimum = 0;
        ProgressMaximum = 100;
        ProgressCurrentValue = 0;

        // Convert the CSV files to DataTables.
        List<DataTable> tables = new List<DataTable>();
        int i = 0;
        int rowCount = 0;
        foreach(string fullname in fullpaths)
        {
            ObjectStringPair reader = await _service.GetDataTableWithCSVHeadersAsync(fullname, headers);
            if (reader.Item is DataTable dt)
            {
                tables.Add(dt);
                rowCount += dt.Rows.Count;
            }
            i++;
        }

        // Abandon if there are no datatables.
        if (tables.Count == 0) { return; }

        double progressIncrement = rowCount / 100;

        rowCount = 0;
        foreach (DataTable table in tables)
        {
            foreach (DataRow row in table.Rows)
            {
                // Update the progress control.
                rowCount++;
                if ((int)(rowCount / progressIncrement) > ProgressCurrentValue)
                {
                    ProgressCurrentValue = (int)(rowCount / progressIncrement);
                }

                // Processing code.
            }
        }

        // Close the progress control.
    }

Upvotes: 0

Views: 532

Answers (1)

Ambie
Ambie

Reputation: 4977

Ach, I'm such a numpty.

The gotcha is that the Task was called from a callback for an MVVMLight IMessenger that itself was sent from a Button RelayCommand. As a result it seems that everything remained on the UIThread.

Perhaps this is obvious to most, but I hadn't spotted it. The answer is explicitly to call the Task on a background thread. So instead of:

await ProcessCSVFiles(...);

I had to call:

await Task.Run(() => ProcessCSVFiles(...);

Not sure if I've got my diagnosis right, so I'd be happy for anyone to correct me, but the solution above seems to work.

Upvotes: 1

Related Questions