0

I have a WPF App and I'm using MVVM.

In my view model I have:

    private string logs;
    public string Logs
    {
        get { return logs; }
        set
        {
            logs = value;
            OnPropertyChanged("Logs");
        }
    }

    private void ExecLoadData()
    {
        using (new WaitCursor())
            Logs = LogFile.ReturnContent();
    }

    private RelayCommand loadData;
    public ICommand LoadData
    {
        get
        {
            if (loadData == null)
                loadData = new RelayCommand(param => this.ExecLoadData());
            return loadData;
        }
    }

In View:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LoadData}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

I'm noticing that between the shooting of the OnPropertyChanged and presentation of data on the page occurs a delay.

I need a way to display the wait cursor to the data to be displayed on the screen.

Already implemented the method WaitCursor() but the wait cursor only appears until the data file is loaded into memory, that is, between the loading of data in memory until the data is displayed on the page the cursor remains normal.

Any tips?

Edit (Final solution with help of AngelWPF):

    private Boolean isBusy = false;
    public Boolean IsBusy
    {
        get { return isBusy; }
        set
        {
            if (isBusy == value)
                return;
            isBusy = value;
            OnPropertyChanged("IsBusy");
        }
    }

    private string logs;
    public string Logs
    {
        get { return logs; }
        set
        {
            logs = value;
            OnPropertyChanged("Logs");
        }
    }

    public void ExecuteBusy(DoWorkEventHandler doWorkEventHandler)
    {
        IsBusy = true;

        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += doWorkEventHandler;
        backgroundWorker.RunWorkerCompleted += (sender, e) => { IsBusy = false; }; 
        backgroundWorker.RunWorkerAsync();
    }

    protected override void ExecLoadData()
    {
        LoadLogs();
    }

    private void LoadLogs()
    {
        ExecuteBusy((sender, e) => 
        { 
            Logs = LogFile.ReturnContent(); 
        });
    }

<Page.Resources>
    <ut:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter" />
</Page.Resources>

<Page.DataContext>
    <vm:ManutencaoMonitoracaoLogsViewModel/>
</Page.DataContext>

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LoadData}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>

    <TextBox Text="{Binding Logs, Mode=OneWay}" VerticalScrollBarVisibility="Auto" IsReadOnly="True" BorderBrush="White" />

    <Border BorderBrush="Black" BorderThickness="1" Background="#80DBDBDB" Grid.RowSpan="3"
            Visibility="{Binding IsBusy, Converter={StaticResource BooleanVisibilityConverter}}">
        <Grid>
            <ct:LoadingAnimation HorizontalAlignment="Center" VerticalAlignment="Center"/>            
        </Grid>
    </Border>

</Grid>

Alexandre
  • 23
  • 6

2 Answers2

0

You shouldn't have any reference to your cursor in a model implementation, how do you call from UI the ExecelLoadData() method? I suggest to change cursor status before to make the call and rechange when it returned

MaRuf
  • 1,834
  • 2
  • 19
  • 22
  • Hi Mackho! I edit the post e put more code. So, I just need know how get the event "post binding" in the view. – Alexandre Oct 20 '11 at 11:30
0

This needs orchestrating any heavy functionalities via something we call as AsyncWorker. This is a asynchrnous command execution using background worker. It has a one time trigger flag that is initiated from the view model as true, so that it runs in the adorner of your window when any heavy functionality is delegated to it. When the functionality is executing the animation indicates the user that a possibly delayed functionality is running and he/she should wait. Then when the delegate finishes, AsyncWorker itself hides the animation and displays the Page properly back to user.

http://elegantcode.com/2009/08/21/a-simple-wpf-loading-animation/

I can envision it can be done this way...

  Characteristics of `AsyncWorker` 
   1. It is a Control that runs an animation such as 
       a neverending progressing progress bar
         or rotating circles etc. in the adorner of the UI.
   2. It accepts the parent panel on which the waiter animation is shown.
   3. It has a boolean dependency property say "StartOperation".           
      When this changes to true we start the animation. 
      When true this also begins excuting the `WorkerDelegateCommand` given below.
   4. It also has a ICommand dependency property called "WorkerDelegateCommand"
      This will be supplied from your `ViewModel`.
      It will hold the time consuming operation as a delegate. 

So basically when we set AsyncWorker.StartOperation to true, we render the adorner of the parent panel with an animating storyboard and kick off a background worker. This background worker runs the WorkerDelegateCommand on a different thread. Thus your slow operation runs on a another thread than UI. meanwhile the async worker animation keeps running. When the WorkerDelegateCommand delegate finishes its slow work, the background woker DoWork call exits and RunCompleted is called. In this we set StartOperation to false.

The way we can configure this AsyncWorker is this way...

   <Grid>
       <Button Content="Do some slow work"
               Command="{Binding RunAsyncWorkerCommand}" />
       <AsyncWorker
               ParentPanel="{Binding RelativeSource={RelativeSource
                                       AncestorType={x:Type Grid}}}"
               StartOperation="{Binding StartSlowWork, Mode=TowWay}"
               WorkerDelegateCommand="{Binding MySlowDelegateCommand}"
               Visibility="Collapsed" />
    </Grid>

So in above example, when the buton is clicked the grid, that contains the button, shows a waiter animation and begins to perform the slow operation. For this your DataContext and / or ViewModel needs three properties...

1. `StartSlowWork` - A boolean flag to start AsyncWorker which is TwoWay bound. 
2. `RunAsyncWorkerCommand` - Command to set the `StartSlowWork` flag to true.
3. `MySlowDelegateCommand` - Command to execute slow work.

Once you have this in place, every slow executing operations can be moved to AsyncWorker.

WPF-it
  • 19,625
  • 8
  • 55
  • 71
  • Hi Angel, nice example, thanks! So, I do not know how to handle this beginning and end of rendering of the screen, do you have an example? that is, start and end event of databinding? – Alexandre Oct 20 '11 at 11:40
  • please see my edit. This may look too much for a simple requirement of yours, but thats how I have been doing it. And no complains for far! :-) – WPF-it Oct 20 '11 at 12:18
  • Hi Angel, I understood your point of view, but I have a question: suppose the heavy activity has been completed, and the result of this activity is a list of 1.000 items of something. So, as I said, in WPF there is a delay between the property assignment at view-model and the property display values ​​at the screen through data binding. I believe that even with this implementation the delay will continue to exist because it is the time it needs for databind to transfer the property values ​​to the visual element. – Alexandre Oct 20 '11 at 15:54
  • The question is: even implemented the heavy activity on a separate thread, after task completion the visual component 'busy' (waiter animation) return to 'idle', and so, during the time of delay, the user will not see the data nor the animation, do you understand? – Alexandre Oct 20 '11 at 15:54
  • Yes... But thats what `Virtualization` was implemented for. When a list of millions of records is bound to sophisticated virtualized component such as ListBox, ListView, DataGrid the component wont hang as it would only render what the scroll view is showing through them. Hence the UI or the animation wont hang for a long time. For a fraction of a second Yes, it is bound to hang as rendering \ layouting is a UI thread blocking operation. – WPF-it Oct 21 '11 at 05:02
  • Angel, your information are very valuable, I am implementing and testing, this is all very new to me. I found another interesting example based on yours (http://stackoverflow.com/questions/6455066/wpf-mvvm-how-to-test-a-viewmodel-that-loads-with-a-backgroundworker), if you have another links on the same issue I am grateful. thanks a lot! – Alexandre Oct 21 '11 at 08:14
  • Lots of links on the internet... Feel free to ask when u stumble upon anything. – WPF-it Oct 21 '11 at 08:45
  • I got it, see above for a final implementation. Thank you very much! Escuse-me, I have another open issue (http://stackoverflow.com/questions/7825939/wpf-page-in-separate-assembly-constructed-always), Maybe you have a help... – Alexandre Oct 21 '11 at 13:57