-1

So, I have this big application that does some long work on the UI Thread. I know it's bad design, but this can't be changed. Thake it as a constraint.

To show the progress of the work, I want to show a WPF dialog with a progressbar. The dialog is using DataBindings to DependencyProperties for its labels and the progressbar.

When I first called Show() on the dialog instance, the dialog wouldn't be displayed, because the Dispatcher is busy working on the long running task. So I called Dispatcher.Yield() after Show(). I also call Yield() after each update of the DependencyProperty of the progressbar. The dialog windows is showing up, but it's still very empty. The DataBindings are not updated on Dispatcher.Yield().

Is there any chance I can get them to update while the Dispatcher is busy?

Example App

Create a new .NET Framework WPF App, name it 'BusyProgress' and change these files.

MainWindow.xaml

<Window x:Class="BusyProgress.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:BusyProgress"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Name="window"
        Title="MainWindow"
        Width="400"
        Height="100"
        mc:Ignorable="d">
    <DockPanel DataContext="{Binding ElementName=window}">
        <ProgressBar Height="20"
                     DockPanel.Dock="Bottom"
                     Maximum="10"
                     Value="{Binding Progress}" />
        <TextBlock Text="{Binding LabelText}" />
    </DockPanel>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace BusyProgress
{
    public partial class MainWindow : Window
    {
        public string LabelText
        {
            get => (string)GetValue(LabelTextProperty);
            set => SetValue(LabelTextProperty, value);
        }
        public static readonly DependencyProperty LabelTextProperty =
            DependencyProperty.Register("LabelText", typeof(string), typeof(MainWindow), new PropertyMetadata("Initial Value"));

        public int Progress
        {
            get => (int)GetValue(ProgressProperty);
            set => SetValue(ProgressProperty, value);
        }
        public static readonly DependencyProperty ProgressProperty =
            DependencyProperty.Register("Progress", typeof(int), typeof(MainWindow), new PropertyMetadata(0));

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

App.xaml

<Application x:Class="BusyProgress.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="Application_Startup" />

App.xaml.cs

using System.Windows;
using System.Windows.Threading;
using System.Threading;
using System;

namespace BusyProgress
{
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            MainWindow window = new MainWindow();
            window.LabelText = "Init";
            window.Show();
            Dispatcher.Yield();

            // Do some long running stuff
            int count = 0;
            while (count++ < 10)
            {
                Console.WriteLine(count);
                window.Progress = count;
                window.LabelText = $"Working hard... ({count}/10)";
                Dispatcher.Yield();

                // Simulate Work
                Thread.Sleep(500);
            }

            MessageBox.Show("Done");
        }
    }
}
SWSBB
  • 61
  • 5
  • Dispatcher queue is priority queue. It will run tasks with higher priority first, while lower priority (which includes bindings update afaik) will have no chance to run. You can try to break your UI occupying task with something like [in this answer](https://stackoverflow.com/a/18445199/1997232): invoke on low enough priority to ensure bindings will get updated. – Sinatr Jun 11 '21 at 07:27
  • Thank you for the input. Shouldn't `Dispatcher.Yield(DispatcherPriority.SystemIdle)` run even the lowest priority tasks on the dispatcher? I just think I'm missing some framework method or property that is disabling the DataBindings... Maybe someone else knows it =) – SWSBB Jun 11 '21 at 07:53
  • I don't have experience with `Dispatcher.Yield`. Try my suggesion from previous comment. Also you are expected to show [mcve] when having problem, it could be something else than `Dispatcher.Yield` as we are currently suspecting. – Sinatr Jun 11 '21 at 07:58
  • I just tried this but it still doesn't update the Bindings. When I skip the `Hide()` call for my dialog and let the long running task on the UI thread finish, the dialog will update and the last state is shown... so I guess the biniding are definied correctly but just not updated while the UI is busy... – SWSBB Jun 11 '21 at 08:06
  • Yes you are right. I'll prepare an example soon – SWSBB Jun 11 '21 at 08:10

1 Answers1

0

Building the example helped to realize that this answer was in fact enough.
My app had another constraint that I didn't realize before. The Dispatcher was disabled -.-... Thank you sintar for your help!

SWSBB
  • 61
  • 5