1

I want to change the background of a WPF Window from another thread, after a delay (Thread.Sleep() in the code tries to mimic an I/O operation). The action will be invoked when a button is pressed.

More specifically, when the button is clicked I want to

  1. The button to disabled
  2. The color of the background changed after a delay of seconds
  3. The button to be enabled again

When I run the application and fire the event I cannot see the button disabled. The background change does happen though, and I'm guessing it has to do with the UI thread and the Dispatcher. But I've also tried to disable it like this

MyButton.Dispatcher.Invoke(() => MyButton.IsEnabled = false)

without any success.

Is anyone able to explain why this is happening and how can I solve it?

XAML

<Window x:Class="ThreadingModel.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ThreadingModel"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
  <StackPanel x:Name='Parent' Background='Green'>
    <TextBlock HorizontalAlignment='Center'
               Margin='10'
               FontSize='32'>Threading and UI</TextBlock>
    <TextBlock Margin='10'
               FontSize='22'
               TextWrapping='Wrap'>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</TextBlock>
    <Button Content='Change Color'
            Width='150'
            Height='50'
            Margin='10'
            FontSize='22'
            Click='Button_Click'
            x:Name='MyButton'/>
  </StackPanel>
</Window>

Code (C#)

public partial class MainWindow : Window
    {
        private delegate void OneArgumentDelegate(int arg);

        private bool isGreen = true;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //MyButton.IsEnabled = false;  
            MyButton.Dispatcher.Invoke(() => MyButton.IsEnabled = false);
            /* This starts a background thread from the pool, right?*/
            MyButton.Dispatcher.BeginInvoke(new OneArgumentDelegate(Change_Background_After), DispatcherPriority.Normal, 3000);
        }

        private void Change_Background_After(int delay)
        {
            Thread.Sleep(delay);
            if (isGreen)
            {
                Parent.Background = Brushes.Red;
                isGreen = false;
            }
            else
            {
                Parent.Background = Brushes.Green;
            }
            MyButton.IsEnabled = true;
        }
    }
Themelis
  • 4,048
  • 2
  • 21
  • 45
  • 2
    *"This starts a background thread from the pool, right?"* - nope, you are queuing something what will run at some point later in UI thread blocking it. You have to use `Task.Run(...)` somewhere. Without parameters [Dispatcher.Invoke](https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,73e16fb1fb6d2b98) uses [Send](https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherpriority) priority, which is lower than `Normal` so it is probably processed after next invoke, this is why you don't see button disabled. – Sinatr Jun 06 '19 at 09:21
  • Thanks for the clarification @Sinatr, I got tricked by that https://stackoverflow.com/a/5577921/9164071 answer which states *its a Threadpool thread* – Themelis Jun 06 '19 at 09:24
  • 1
    Declare your Button_Click handler async. Call `((Button)sender).IsEnabled = false;`. Then `await`a Task that performs your long running operation, e.g. `await Task.Run(() => { ... });`. Finally, call `((Button)sender).IsEnabled = true;` – Clemens Jun 06 '19 at 09:24
  • How can it be done without `async`? @Clemens – Themelis Jun 06 '19 at 09:30
  • Why would you not want to use `async`? – Clemens Jun 06 '19 at 09:31
  • Because its first time in the .NET world and the last 3 days I've trying to learn C#, XAML etc. I haven't reached to `async` yet. – Themelis Jun 06 '19 at 09:33
  • 1
    I'd strongly recommend to learn async/await first, before trying to deal with Threads or BackgroundWorkers manually. Anyway, take a look at the second original question that I've linked, at the top of your question. – Clemens Jun 06 '19 at 09:35
  • You may also want to take a look at how to do this with an asynchronous ICommand: https://stackoverflow.com/a/56462453/1136211. You would bind the Button's `Command` property to the ICommand property. – Clemens Jun 06 '19 at 10:05
  • Thank you @Clemens. That's more advanced stuff and there are so many ways to do the same thing. I wanted simply to understand why the button was not disabled and I've managed to accomplish the task using traditional `Thread`s. – Themelis Jun 06 '19 at 10:10
  • That answer though is tricky https://stackoverflow.com/a/5577921/9164071 and has a lot of upvotes. Indeed if `BeginInvoke()` executes `Thread.CurrentThread.IsBackground` you get a `false` value.. – Themelis Jun 06 '19 at 10:12
  • 1
    Don't bother with BeginInvoke. It does not start a Thread or schedule a task on a ThreadPool thread, and is hence not the thing you are looking for. In order to explicitly schedule a task on a ThreadPool thread use [Queue​User​Work​Item](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool.queueuserworkitem?view=netframework-4.8) – Clemens Jun 06 '19 at 10:15

0 Answers0