Home > OS >  WPF MVVM - UI not updating properly from thread even when raising INotifyPropertyChanged
WPF MVVM - UI not updating properly from thread even when raising INotifyPropertyChanged

Time:12-14

I'm reading a file byte-by-byte and trying to update my progress bar accordingly using MVVM. Here's what I did so far.

The File Model is just a single class storing the file path, total number of bytes and number of bytes read so far which I called WorkDone and it's what im using as the current value of the progress bar.

File Model:

public class TestingFile : ObservableObject
    {

        private string _filePath;

        public string FilePath
        {
            get { return _filePath; }
            set
            {
                _filePath = value;
                OnPropertyChanged();
            }
        }

        private byte[] _fileContent;

        public byte[] FileContent
        {
            get { return _fileContent; }
            set 
            {
                _fileContent = value;
                OnPropertyChanged();
            }
        }

     

        private int _workDone;
        // how many bytes have been read
        public int WorkDone
        {
            get { return _workDone; }
            set
            {
                _workDone = value;
                OnPropertyChanged();
            }
        }

        private int _totalLen;
        // total bytes
        public int TotalLen
        {
            get { return _totalLen; }
            set 
            { 
                _totalLen = value;
                OnPropertyChanged();
            }
        }



        public void StartRead()
        {
            FileContent = File.ReadAllBytes(FilePath);
            TotalLen = FileContent.Length;

            ReadTestingFile();

        }


        void ReadTestingFile()
        {
            WorkDone = 0;

            foreach (byte currentByte in _fileContent)
            {
                // do some stuff here for each byte in the file

                  WorkDone; // update progress bar
            }
        }


    }

The view model has a TestingFile object and gets it from a filedialog, after getting the selected file it calls the StartRead method shown below to read the file:

View Model:

   public class FirstViewModel : BaseViewModel
    {
        private TestingFile myTestingFile { get; set; }
        public TestingFile MyTestingFile
        {
            get { return myTestingFile; }
            set
            { 
                myTestingFile  = value; 
            }
        }

        public ICommand GetFileCommand { get; set; }
        public FirstViewModel(NavigationStore navigationStore)
        {
            GetFileCommand = new RelayCommand(GetFileDialog);

            MyTestingFile = new TestingFile();
        }

 
        public void GetFileDialog()
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();

            if (openFileDialog.ShowDialog() == true)
            {
                MyTestingFile.FilePath = openFileDialog.FileName;

                Thread thread = new Thread(new ThreadStart(MyTestingFile.StartRead));
                thread.Start();
            }
        }

    }

Now , in the view I just create a progress bar and bind it's value to WorkDone and Maximum value to TotalLen from the model:

The view:

<ProgressBar HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                             Margin="200, 20, 200, 0"
                             Value="{Binding MyTestingFile.WorkDone, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
                             Maximum="{Binding MyTestingFile.TotalLen}"
                             Height="25"/>

The problem is that, even though I'm creating a separate thread for reading the file, the progress bar is not moving as the file is being read, it goes directly to 100 after the whole file is read, what am I doing wrong?

CodePudding user response:

The error is not in the implementation, but in your expectations.
You expect the value of the WorkDone property to gradually change. This value is incremented in a loop in the ReadTestingFile method. The problem is that this cycle takes place almost instantly - in units or even fractions of a millisecond.
Therefore, you do not even have time to see intermediate values, but only see the final result.

For testing purposes, add a small delay to the method:

        void ReadTestingFile()
        {
            WorkDone = 0;

            foreach (byte currentByte in _fileContent)
            {
                // do some stuff here for each byte in the file

                  WorkDone; // update progress bar

               Thread.Sleep(10); // Testing MUST not be on the UI thread!
            }
        }

P.S. I also advise you to use asynchronous methods and Task instead of Thread.

            if (openFileDialog.ShowDialog() == true)
            {
                MyTestingFile.FilePath = openFileDialog.FileName;

                Task.Run(MyTestingFile.StartRead);
            }

SUPPLEMENT:

but what about larger files ...?

If you need it for large files, then you need to reduce the refresh rate.
An example of such an implementation:

void ReadTestingFile()
{
    int _workDone = WorkDone = 0;
    byte[] _fileContent = FileContent;
    Task.Run(async () =>
    {
        do
        {
            await Task.Delay(20);
        } while ((WorkDone = _workDone) != _fileContent.Length);
    });
    foreach (byte currentByte in _fileContent)
    {
        // do some stuff here for each byte in the file

        _workDone  ; // update progress bar
    }
}

P.S. But I'm warning you right now.
For greater security, it is better to cancel the task using CancellationToken. In my example, I did using conditional checking to simplify the code for this example.

  • Related