views:

105

answers:

4

In the following example, how can I get:

  • the button to be "disabled-grey"
  • the message to say "working..."

while the work is being done, not after the work is done?

XAML:

<Window x:Class="TestIsEnabled8938.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel Margin="10" HorizontalAlignment="Left">

        <Button x:Name="Button_Refresh" 
                    HorizontalAlignment="Left"  
                    DockPanel.Dock="Top" 
                    Content="Refresh" 
                    Click="Button_Refresh_Click" 
                    Height="25" 
                    Width="200"/>

        <TextBlock x:Name="Message" Text="Button is ready to click."/>
    </StackPanel>
</Window>

code-behind:

using System.Windows;
using System.Threading;

namespace TestIsEnabled8938
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Refresh_Click(object sender, RoutedEventArgs e)
        {
            Message.Text = "working...";
            Button_Refresh.IsEnabled = false;

            //do work
            Thread.Sleep(2000);

            Message.Text = "Button is ready to click again.";
            Button_Refresh.IsEnabled = true;
        }
    }
}

This doesn't work either:

Dispatcher.Invoke(new Action(() => { Message.Text = "working..."; }));
Dispatcher.Invoke(new Action(() => { Button_Refresh.IsEnabled = false; }));

Answer:

Thanks Heinzi, this code works:

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

namespace TestIsEnabled8938
{
    public partial class Window1 : Window
    {
        BackgroundWorker backgroundWorker;

        public Window1()
        {
            InitializeComponent();

            backgroundWorker = new BackgroundWorker();

            backgroundWorker.DoWork += (sender, args) =>
            {
                Thread.Sleep(3000);
            };

            backgroundWorker.RunWorkerCompleted += (sender, args) =>
            {
                Message.Text = "button is ready to click again";
                Button_Refresh.IsEnabled = true;
            };
        }

        private void Button_Refresh_Click(object sender, RoutedEventArgs e)
        {
            Message.Text = "working...";
            Button_Refresh.IsEnabled = false;
            backgroundWorker.RunWorkerAsync();
        }
    }
}
+1  A: 

You'll have to run your tasks on a separate thread. One easy way to accomplish is to use the Button.Invoke-method.

Read more about it here!

Mickel
Right, I've been trying various Invoke commands but none are working, the one above with Dispatcher compiles but doesn't refresh the UI. Otehrwise I get: System.Windows.Controls.Button' does not contain a definition for 'Invoke' and no extension method 'Invoke' accepting a first argument of type 'System.Windows.Controls.Button' could be found.
Edward Tanguay
In WPF, you have to use Button.Dispatcher.Invoke(). Button.Invoke() is Winforms.
AdamRalph
+3  A: 

If you want the UI to update (and stay responsive) while your task is running, you need to use a separate thread, for example by using a BackgroundWorker.

Code example (untested):

BackgroundWorker bwButtonWorker;

public Window1() {
    InitializeComponent();

    bwButtonWorker = new BackgroundWorker();

    bwButtonWorker.DoWork += (sender, args) => {
        // do your lengthy stuff here -- this happens in a separate thread
        Thread.Sleep(2000);
    }

    bwButtonWorker.RunWorkerCompleted += (sender, args) => {
        // this happens in the UI thread, so you can modify your UI elements here
        Message.Text = "Button is ready to click again.";
        Button_Refresh.IsEnabled = true;
    }
}

private void Button_Refresh_Click(object sender, RoutedEventArgs e)
{
    Message.Text = "working...";
    Button_Refresh.IsEnabled = false;
    bwButtonWorker.RunWorkerAsync();
}
Heinzi
+1 this solution is better than mine.
Mickel
+2  A: 

Another method, for what it's worth:-

    private void Button_Refresh_Click(object sender, RoutedEventArgs e)
    {
        Message.Text = "working...";
        Button_Refresh.IsEnabled = false;

        ThreadPool.QueueUserWorkItem(delegate(object state)
        {
            Thread.Sleep(2000);
            Message.Dispatcher.Invoke((Action)delegate()
               {
                   Message.Text = "Button is ready to click again.";
                   Button_Refresh.IsEnabled = true;
               });
        });
    }
AdamRalph
+1 also works, useful, thanks!
Edward Tanguay
A: 

Do not use Control.Invoke() - its internal implementation totally sucks. Invoke() causes one thread to wait for another thread and is therefore likely to introduce deadlocks. You should always use BeginInvoke(), which is non-blocking.

JoshH