1

I am using Extended WPF Toolkit and MVVM Light libraries.

I would like to implement the WPF Toolkit busy indicator and periodically informs the user with some info (binding with BusyMessage property in the viewmodel).

However, when button 'Start' is clicked, the busy indicator (IsBusy bound to IsBusy property in viewmodel) is not shown. What I am doing wrong? Oddly enough, it works when IsBusy is set to true in the constructor of the viewmodel.

App.xaml

public partial class App : Application
{
    public App()
    {
        DispatcherHelper.Initialize();
    }
}

Window

<Window xmlns:xctk='http://schemas.xceed.com/wpf/xaml/toolkit'
    DataContext='{Binding Main, Source={StaticResource Locator}}'>
 <StackPanel>
 <xctk:BusyIndicator IsBusy='{Binding IsBusy}'>
  <xctk:BusyIndicator.BusyContentTemplate>
    <DataTemplate>
      <StackPanel Margin='4'>
        <TextBlock Text='{Binding DataContext.BusyMessage, RelativeSource={RelativeSource AncestorType={x:Type Window}}}' />
      </StackPanel>
    </DataTemplate>
  </xctk:BusyIndicator.BusyContentTemplate>      
<Button Content='Start ...'
        Command='{Binding StartCommand}'
        HorizontalAlignment='Center'
        VerticalAlignment='Center' />
</xctk:BusyIndicator>

Viewmodel

public class MainViewModel : ViewModelBase
{
    private string _busyMessage;

    public string BusyMessage
    {
        get { return _busyMessage; }
        set
        {
            if (_busyMessage != value)
            {
                _busyMessage = value;
                RaisePropertyChanged(nameof(_busyMessage));
            }
        }
    }

    private bool _isBusy;

    public bool IsBusy
    {
        get { return _isBusy; }
        set {
            if (_isBusy != value)
            {
                _isBusy = value;
                RaisePropertyChanged(nameof(_isBusy));
            }
        }
    }

    public RelayCommand StartCommand
    {
        get { return new RelayCommand(() => StartExecute()); }
    }


    private async void StartExecute()
    {
        IsBusy = true;
        await Task.Run(() =>
        {
            //update UI from worker thread
            DispatcherHelper.CheckBeginInvokeOnUI(() => BusyMessage = "Work 1 Done");
            Thread.Sleep(1000);
            //update UI from worker thread
            DispatcherHelper.CheckBeginInvokeOnUI(() => BusyMessage = "Work 2 Done");
        });
        IsBusy = false;
    }

    public MainViewModel()
    {
        //Works when boolean is set to 'true' in constructor
        //IsBusy = true;
    }
}
BertAR
  • 425
  • 3
  • 18
  • Don't use async void outside of event handlers, as a rule of thumb; return a Task so the method can be awaited properly. See https://stackoverflow.com/questions/12144077/async-await-when-to-return-a-task-vs-void – Alex Paven Jan 09 '18 at 09:45
  • @AlexPaven Better code should be public RelayCommand StartCommand { get { return new RelayCommand(async() => await StartExecute()); } }. That is correct? – BertAR Jan 09 '18 at 09:54
  • async lambdas have pitfalls of their own and should be carefully researched first (in some ways they're equivalent to async void). I prefer creating a separate RelayCommand with better async support. See https://stackoverflow.com/questions/32591462/is-using-an-an-async-lambda-with-task-run-redundant - haven't had my morning coffee tho so I might be confusing things. – Alex Paven Jan 09 '18 at 10:02

1 Answers1

4

That is exactly why I prefer ReactiveUI - it has "busy indicator" built into ReactiveCommand, even async one.

As for your problem, I would say that your RaisePropertyChanged(nameof(_isBusy)); is wrong and it should be RaisePropertyChanged(nameof(IsBusy)); // name of the public property

Also, depending on your RaisePropertyChanged implementation, you can propably leave the argument empty and just use RaisePropertyChanged();

Krzysztof Skowronek
  • 2,796
  • 1
  • 13
  • 29