0

By lack of any response on the developer's GitHub I will repeat my question here. I hope someone can help me in some way.

This is the first time I am using MvvmLight, so I hope I am not overlooking something obvious.

In my WPF ViewModel I have something like:

  private ICommand readFileCommand;
  public ICommand ReadFileCommand => readFileCommand ?? (readFileCommand = new RelayCommand(ReadFile));

  private void ReadFile()
  {
        FileMessage = "Message.";
  }

  private string fileMessage;
  public string FileMessage
  {
       get { return fileMessage; }
       set
       {
           //Set(ref fileMessage, value);

           fileMessage = value;
           RaisePropertyChanged();
       }
   }

I have a couple of problems with it.

  • Main problem is that setting a property like FileMessage from within a method like ReadFile() does not result in an update of the view until ReadFile is completed.
  • There is a difference between using RaisePropertyChanged() which succeeds at that moment, and using Set() which does nothing at all. Though the latter did work outside such a method.
  • The problem extends to other elements like a DataGrid on a DataView.

In wondered if the called methods should be asynchronous, but that does not seem logical. I have not tried that yet as that does not really fit into what I want to achieve.

So what is happening? Am I overlooking something? Is this a limitation of the framework? Or is this a bug?

Thanks!

Robert
  • 53
  • 6
  • Does this answer your question? [WPF - UI not updating from Command](https://stackoverflow.com/questions/54379417/wpf-ui-not-updating-from-command) – Rekshino May 17 '21 at 11:15
  • 1
    You have to show the view code at least. – Serge May 17 '21 at 12:42
  • Also thanks, Rekshino. It does look similar, the explanation also makes sense. I have been experimenting on it. But I don't get it to make any difference. – Robert May 17 '21 at 17:36

3 Answers3

1

Main problem is that setting a property like FileMessage from within a method like ReadFile() does not result in an update of the view until ReadFile is completed.

This makes sense as you cannot both execute your ReadFile method and update the UI on the same thread simultaneously. This has nothing to do with MvvmLight or commands.

If you set the property before you run any potentially long-running code, either asynchronously or synchronously on a background thread, it should work as expected.

Try this for example:

private async void ReadFile()
{
    FileMessage = "Message.";
    await Task.Delay(5000); //simulate...
    FileMessage = "Done!";
}

Or this:

private async void ReadFile()
{
    FileMessage = "Message.";
    await Task.Run(() => Thread.Sleep(5000));
    FileMessage = "Done!";
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Hi @mm8. I understand what you are saying. But I am still confused. (I am chopping this up.) – Robert May 17 '21 at 20:46
  • - Note that my initial example does nothing with Tasks or Threads. My thought was that at least this should simply work. Unless the Command starts one. – Robert May 17 '21 at 20:47
  • - I have been playing with your example, which does use Tasks. The first message gets displayed when only when starting the Task. The second not when finishing the Task, but when exiting the method, like in my initial example. It’s not that it updates while switching threads, as I imagined. In my actual code this would not be a problem when reading a file. – Robert May 17 '21 at 20:47
  • - But then there is the big question of updating the view WHILE calculating results RECURSIVELY, which is the end goal. I don’t think using Tasks would be wise to begin with. And I also think I would not get the updates through to the view. – Robert May 17 '21 at 20:47
  • - I have been trying to use Dispatcher.Invoke() too. But so far that did not make any difference. – Robert May 17 '21 at 20:47
  • *My thought was that at least this should simply work*. Well, then you thought wrong. Read the first sentence in my answer once again? – mm8 May 18 '21 at 11:38
  • That does no help any way. – Robert Jun 04 '21 at 19:04
0

I 'solved' this the way below, which does for me what I ultimately wanted, but still leaves me with questions.

The code is sort of a complete example to make my point. One remark. Using the debugger with breaks can be very misleading in the behaviour.

Observations

  • What DOES work are the 'Run' messages.
  • However, all of the other messages are not displayed. And that leaves me dumbfounded.

The ReadFile method is synchronous and directly on the UI thread (I checked by the name), and does nothing with threading outside of the Task. So why is it unable to display anything? I even superfluously used Invoke, which did not help.

So that still leaves me doubting whether the RelayCommand properly works.

I even considered switching to Windows Community Toolkit, but that seemed too big a step.

It may end there anyway, as MVVVM Light is already dropped by Windows Template Studio, and development stopped december 2018!

If I overlook anything, clarifications are still appreciated.

using GalaSoft.MvvmLight;
using Application.Contracts.ViewModels;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;

namespace ViewModels
{
    public class ViewModel : ViewModelBase, INavigationAware
    {
        public ViewModel()
        {
            uiDispatcher = Dispatcher.CurrentDispatcher;
        }

        private Dispatcher uiDispatcher;

        public async void OnNavigatedTo(object parameter)
        {
            uiDispatcher.Thread.Name = "OnNavigatedTo";
        }

        public void OnNavigatedFrom()
        { }

        private string fileMessage = "No file yet";
        public string FileMessage
        {
            get { return fileMessage; }
            set
            {
                // Using this simple way instead of Set, which did not work.
                fileMessage = value;
                RaisePropertyChanged();
            }
        }

        private void ReadFile()
        {
            FileMessage = "ReadFile 1.";

            Thread.Sleep(1000);

            uiDispatcher.Invoke(() => FileMessage = "ReadFile Invoke 1.", DispatcherPriority.Send);

            Thread.Sleep(1000);

            // Use Run on a non UI-thread and Dispatcher to enable intermediate updates back on the UI-thread.
            Task.Run(() =>
            {
                uiDispatcher.Invoke(() => FileMessage = "Run 1.", DispatcherPriority.Send);

                Thread.Sleep(1000);

                uiDispatcher.Invoke(() => FileMessage = "Run 2.", DispatcherPriority.Send);

                Thread.Sleep(1000);
            });

            Thread.Sleep(1000);

            FileMessage = "ReadFile 2.";

            Thread.Sleep(1000);

            uiDispatcher.Invoke(() => FileMessage = "ReadFile Invoke 2.", DispatcherPriority.Send);
        }
    }
}
Robert
  • 53
  • 6
  • "The ReadFile method is synchronous and directly on the UI thread" - is not entirely wrong. The method is called from the command `ReadFileCommand` - therefore, in which thread the command will be called, then in such thread the method will be executed. You call a command by clicking on a button, so the command is executed in the UI thread. But in the general case, this is not necessarily the case. – EldHasp Jun 04 '21 at 21:22
  • "So why is it unable to display anything?" What is "Thread"? This is a certain queue of commands executed sequentially. Think about "SEQUENTLY". You have several tasks in one UI thread: one is executing the command, the other is updating the interface. But in general, their commands can only be executed SEQUENTIALLY. For the GUI update task started, the command task must be finished. – EldHasp Jun 04 '21 at 21:29
  • "I even superfluously used Invoke, which did not help" - And that couldn't help. `Dispatcher.Invoke(...)` - Executes a method in the dispatcher QUEUE. And what is he a dispatcher? This is the UI Thread Dispatcher (in your case). You already have this method called in the UI thread, and passing it to the dispatcher queue of the same thread is, at best, pointless. And in some cases, it can lead to self-blocking and the GUI will freeze. – EldHasp Jun 04 '21 at 21:36
  • "So that still leaves me doubting whether the RelayCommand properly works." - Your example doesn't have a using for the RelayCommand, so it's hard to judge. MVVMLight has two different implementations of commands in the Commands and WpfCommands spaces (I don't remember exactly the name, but something similar). In the first the implementation of commands for use anywhere. In the second for use in the WPF GUI. There are differences in them regarding the auto-update from the GUI. – EldHasp Jun 04 '21 at 21:44
  • "MVVVM Light is already dropped by Windows Template Studio, and development stopped december 2018!"- This is a very simple package. Initially, his goal was to propagate the MVVM pattern. Then the support of novice programmers (essentially students). It has fulfilled its function. All that can be achieved for the students is that it delivers. There is nowhere to develop further. And to compete with "adult" packages (ReactivUI, Prism, etc.) was not originally intended. For me, in simple cases, my own implementations are completely enough. Partial example: BaseInpc and RelayCommand classes – EldHasp Jun 04 '21 at 21:52
  • The link did not fit into the previous comment: https://stackoverflow.com/questions/67597202/how-to-fix-this-behavior-in-a-wpf-tabcontrol/67617794#67617794 – EldHasp Jun 04 '21 at 21:55
0

Try a slightly modified implementation:

   private async void ReadFile()
    {
        FileMessage = "ReadFile 1.";

        await Task.Delay(1000);

        await uiDispatcher.BeginInvoke(() => FileMessage = "ReadFile Invoke 1.", DispatcherPriority.Send);

        await Task.Delay(1000);

        // Use Run on a non UI-thread and Dispatcher to enable intermediate updates back on the UI-thread.
        await Task.Run(async () =>
        {
            uiDispatcher.Invoke(() => FileMessage = "Run 1.", DispatcherPriority.Send);

            await Task.Delay(1000);

            uiDispatcher.Invoke(() => FileMessage = "Run 2.", DispatcherPriority.Send);

            await Task.Delay(1000);
        });

        await Task.Delay(1000);

        FileMessage = "ReadFile 2.";

        await Task.Delay(1000);

        await uiDispatcher.BeginInvoke(() => FileMessage = "ReadFile Invoke 2.", DispatcherPriority.Send);
    }
}

In general, this code is absurd.
But I didn’t make any big changes to maintain continuity.

P.S. The code was written here in the post editor.
Sorry - minor mistakes are possible.

EldHasp
  • 6,079
  • 2
  • 9
  • 24