MVVM updating Label - c#

I know this is a frequently asked question, but I'm trying to solve it at least a week now... Read so many Threads, downloaded millions of different MVVM-Pattern-Examples and so on...
I just want to update a stupid label in my MVVM modelview first approach:
void StartUpProcess_DoWork(object sender, DoWorkEventArgs e)
{
SplashWindow splash = new SplashWindow();
var ViewModel_Splash = new VM_SplashWindow();
splash.DataContext = ViewModel_Splash;
splash.Topmost = true;
splash.Show();
ViewModel_Splash.DoWork();
}
The complete ViewModel:
public class VM_SplashWindow:VM_Base
{
#region Properties
private string _TextMessage;
public string TextMessage
{
get
{
return _TextMessage;
}
set
{
if(_TextMessage != value)
{
_TextMessage = value;
base.OnPropertyChanged("TextMessage");
}
}
}
#endregion
#region Methods
public void DoWork()
{
this.TextMessage = "Initialize";
for(int aa = 0; aa < 1000; aa++)
{
this.TextMessage = "Load Modul: " + aa.ToString();
Thread.Sleep(5);
}
this.TextMessage = "Done";
Thread.Sleep(1000);
}
#endregion
}
A small piece from the base:
public abstract class VM_Base:INotifyPropertyChanged, IDisposable
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
And finally the view:
<Label Height="28" Margin="19,0,17,15" Name="label2" VerticalAlignment="Bottom"
Content="{Binding Path=TextMessage}" Foreground="White" />
If I set a initial value for the TextMessage Property in the constructor of my viewmodel, this initial value will be shown after the splash.Show() command.
Setting the TextMessage Property in the DoWork-Method raises the onPropertyChangedEvent but unfortunately it will not update the label in the window. I don't know what I should do... I'm really looking forward for help. Many thanks in advance!
maybe I should mention that the StartUpProcess_DoWork is running in a own STAThread
kind regards, flo

Apparently, you are performing a lot of work in the GUI thread. And with Thread.Sleep you even suspend the GUI thread. Therefore, it will not be able to update the controls.
The solution is to use a different thread for the DoWork method. This can be easily achieved with a BackgroundWorker. If you provide the GUI dispatcher object to the worker, you can issue GUI changes from there. Although it would be better to use the ProgressChanged-Event for that, if it is possible.

Related

BackgroundWorker not updating UI

Before I get into the details here, I'm still in what I would consider to be the "learning" phase of my C#/WPF journey... so apologies if what I'm asking here is stupidly obvious...
I have a small application (WPF, .NET Framework 4.8) that does the following:
read a list of values
do something for each value in the list
I am trying to do this with a BackgroundWorker so that I can report back to the UI as the list is being processed, preferably with a progress bar.
For the moment, the DoWork method just has some code in there to indicate that it's actually going through the process as expected and so that I could check that all the UI is updating as expected before I put the actual "what I want it to do" in there.
It seems that all the properties are updating as expected, but the UI (i.e. the progress bar) just doesn't move.
And I have checked that the data context in the XAML is set correctly (both in the XAML and in the Code-Behind).
In my XAML I have the following:
<ProgressBar x:Name="ProgressBar"
Width="740"
Height="20"
Background="Transparent"
Foreground="#008DEB"
Grid.Row="3"
Minimum="0"
Maximum="100"
Value="{Binding ProgressBarIndicator}"/>
And in my class containing all my methods/properties etc, I have:
private int _measurementProgress;
public int MeasurementProgress
{
get { return _measurementProgress; }
set
{
_measurementProgress = value;
OnPropertyChanged();
}
}
private int _progressBarIndicator;
public int ProgressBarIndicator
{
get { return _progressBarIndicator; }
set
{
_progressBarIndicator = value;
OnPropertyChanged();
}
}
public void StartMeasurements(string ipAddress)
{
TotalMeasurementsInList = CommandsList.Count;
MeasurementProgress = 0;
measurementWorker.WorkerReportsProgress = true;
measurementWorker.DoWork += worker_DoWork;
measurementWorker.ProgressChanged += worker_ProgressChanged;
measurementWorker.RunWorkerCompleted += worker_RunWorkerCompleted;
measurementWorker.RunWorkerAsync();
}
public void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var command in CommandsList)
{
MessageBox.Show(String.Format("Measuring Sample: {0}",command.SampleName),"Measuring Sample");
measurementWorker.ReportProgress((int)((double)(MeasurementProgress / (double)TotalMeasurementsInList)*100));
Thread.Sleep(command.DelayTime*1000);
}
}
public void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
MeasurementProgress++;
MessageBox.Show(String.Format("Progress is {0}%", e.ProgressPercentage.ToString()));
ProgressBarIndicator = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBarIndicator = 100;
MessageBox.Show("Measurements are completed","Finished");
}
The "OnPropertyChanged()" method is inside my ObservableObject class, and the above class is set to inherit from this ObservableObject class. The ObservableObject class looks like this:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
By placing break-points in strategic places, I can confirm that the value of MeasurementProgress does indeed increment as one would expect, as does the value of ProgressBarIndicator. This was double-confirmed by the MessageBox in the worker_ProgressChanged method as it does indeed display the appropriate percentage value.
Annoyingly, what would appear to me to be exactly the same code is working as expected in another part of the application. The code here is more or less a copy/paste from there... but I just can't see what I'm doing wrong.
Any help or pointers where I could look to try and debug this appreciated.
Many thanks
Colin

c# WinForms DataBinding doesn't update my TextBox.text

I wanna bind a TextBox to a class property, so when this property changes, my TextBox changes automatically too (Windows Forms).
I have a class like this:
class Device : INotifyPropertyChanged
{
private string can_rpm;
public string Can_rpm
{
get { return can_rpm; }
set { can_rpm = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
\\lots of other codes
}
My main form has some code like this (with a textbox called 'tbTest'):
private void Form1_Load(object sender, EventArgs e)
{
Device device= device = new Device();
tbTest.DataBindings.Clear();
tbTest.DataBindings.Add(new Binding("Text",device,"Can_rpm",true,DataSourceUpdateMode.OnPropertyChanged));
\\lots of other stuff
}
My problem: My textBox never updates! A have some other code that updates the 'Can_rpm' property, but nothing shows on my textbox.text. BUT, if I change the empty value of my textbox to something else, my property DOES change too!
So it's working 'one way', but not the other!
I've searched here and googled it, but all I find is examples that does what is already done in my code, but mine doesn't work.
Thanks for helping if you can.
Try with this:
tbTest.DataBindings.Add(nameof(TextBox.Text), device, nameof(Device.Can_rpm));
I've tested the code with your Device class code and this code in the form constructor:
var device = new Device();
this.textBox1.DataBindings.Add(nameof(TextBox.Text), device, nameof(Device.Can_rpm));
device.Can_rpm = "Hello";
After that, my textbox has "Hello" text.
UPDATE
You need update controls always in the thread in which they was created, usually in the main thread. I use a Form extension methods to do that:
public static class FormExtends
{
public static void RunInMyThread(this Form form, Action operation)
{
if (form.InvokeRequired)
{
form.BeginInvoke(operation);
}
else
{
operation();
}
}
}
With the previous extension, you can do (in your Form code) your updates in this way:
this.RunInMyThread(() => device.Can_rpm = "Hello");
Another way to do that:
public class Device : INotifyPropertyChanged
{
private static SynchronizationContext GuiContext = SynchronizationContext.Current;
private string can_rpm;
public string Can_rpm
{
get { return can_rpm; }
set { can_rpm = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
GuiContext.Post(
s => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)),
null);
}
}
}
GuiContext is initialized in the main thread so it runs the code in that thread. If you change your PropertyChanged event to throw in the Post of that context, you don't need take care about where your device properties are changed because the notiy always run in the main thread.

Disabling controls during processing a function

I have problem with disabling controls in WPF Application during processing a function. It is simple app sending data via serial port. When port is "listening" (SerialPort.ReadChar();) I want all controls to go gray/disable them.
This way:
private void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
disabling seems to work only inside the function, so nothing actually happens in the window. When I remove enabling at the end of function all controls go gray, but not at the moment of *.IsEnabled = false instructions, but when the functions ends. Is there something I do wrong or everything is OK and this needs to be done in different way?
Welcome to StackOverflow !
Since your code is synchronous it is blocking, hence the behavior you get. There is also the need to consider using the Dispatcher but luckily in your case you haven't encountered such issue.
Suggestions:
use a ViewModel
bind to some properties in it to enable/disable your UI
doing so separates concerns and simplifies your thing in general
Example : a 5 second work that disables the UI (really simple !)
Points of interest in my code:
by putting all controls that must be disabled within a StackPanel and binding its IsEnabled property the model's IsAvailable property I effectively simplify this process
no controls are modified from code-behind
the view (your window) does nothing more than presenting, all your logic is in a model that is not tied to your window and can be reused somewhere else
XAML:
<Window x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainView"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance wpfApplication1:MainViewModel,
d:IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button Command="{Binding DoSomeWork}" Content="Do some long work" />
<StackPanel IsEnabled="{Binding IsAvailable}">
<CheckBox Content="Test control 1" />
<RadioButton Content="Test control 2" />
</StackPanel>
<TextBlock Text="Overall progress:" />
<ProgressBar Height="10" Value="{Binding CurrentProgress}" />
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
// put classes shown below here
}
Your model :
internal class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
// set-up environment
DoSomeWork = new DelegateCommand(DoSomeWorkExecute, DoSomeWorkCanExecute);
IsAvailable = true;
}
public int CurrentProgress
{
get { return _currentProgress; }
set
{
_currentProgress = value;
OnPropertyChanged();
}
}
#region IsAvailable
private bool _isAvailable;
private int _currentProgress;
public bool IsAvailable
{
get { return _isAvailable; }
set
{
_isAvailable = value;
OnPropertyChanged();
}
}
#endregion
#region DoSomeWork
public DelegateCommand DoSomeWork { get; private set; }
private bool DoSomeWorkCanExecute(object arg)
{
return true;
}
private async void DoSomeWorkExecute(object o)
{
await Task.Run(() =>
{
IsAvailable = false;
var steps = 20;
var time = 5000;
var length = time/steps;
for (var i = 0; i < steps; i++)
{
Thread.Sleep(length);
var currentProgress = (int) (((((double) i + 1)*length)/time)*100);
CurrentProgress = currentProgress;
}
IsAvailable = true;
});
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And a trivial command base for DoSomeWork:
internal class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute)
: this(execute, s => true)
{
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
TODO
Get familiar with:
tasks and asynchronous programming
MVVM for separating concerns, I'd suggest Prism, some would consider this is overkill but it has a very good documentation, there are other players such as MVVM Light
Dispatcher as well
You will experience some pain with these concepts the first time, but over time you will find these are the way to go esp. with WPF.
If you are satisfied with my answer, mark it as the answer, otherwise if you need some clarification then add a comment below and either me or someone will try to help further.
Please, read the full answer Aybe provided. It's always good to follow best practices. But when it comes to small quick test projects, I believe that sometimes it might be an overkill.
If you need quick solution to this problem then you could try to use the following approach:
private async void startTransmissionButton_Click(object sender, RoutedEventArgs e)
{
ComboBox.IsEnabled = false;
Button1.IsEnabled = false;
Button2.IsEnabled = false;
Button3.IsEnabled = false;
await
Task.Factory.StartNew(
() =>
{
SerialPort com = new SerialPort("COM1");
com.Open();
c = (char)com.ReadChar();
com.Close();
}
);
ComboBox.IsEnabled = true;
Button1.IsEnabled = true;
Button2.IsEnabled = true;
Button3.IsEnabled = true;
}
Note that assigning value to c variable happens in another thread.
I hope my answer is helpful.

Windows Phone data binding: cross-thread

we have a problem with data binding on windows phone (using xaml). i have created a simple example, which should allow to reproduce the problem.
Here is our model-class:
public class Data : INotifyPropertyChanged
{
private int value = 0;
public int Value
{
get
{
return value;
}
set
{
this.value = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Data()
{
var t = new Thread(new ThreadStart(() =>
{
while (true)
{
Thread.Sleep(2000);
Value += 1;
}
}));
t.IsBackground = true;
t.Start();
}
}
which uses a thread to update the value-property and fire the PropertyChanged-event.
Now i want to bind this value-property to a gui control:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding Path=Value}" />
</Grid>
public MainPage()
{
InitializeComponent();
DataContext = new Data();
}
when the value first changes (and the PropertyChanged-event gets fired) the data binding system tries to copy the value of Data.Value to TextBlock.Text, which results in an invalid cross-thread exception, as this event is not fired on the ui thread.
my question: shouldn't the .NET databinding framework recognize that i'm binding to a ui control and perform the thread switching itself? i know that i can simply use a dispatcher to fire the PropertyChanged-event on the main thread, but i'd like to have my model-class more seperated from the gui component.
is there a better solution to this problem? i am unable to use the DependencyObject approach, because our core project (which contains the model class) should run on Windows Phone AND Android, and Android doesn't support the System.Windows-namespace.
One way to solve this would be to store a reference to the dispatcher on your view model and only use it to execute the property changed event if it is not null. Then you can set the dispatcher property in your VM's constructor.
I do like this:
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyEventChanged(string propertyName)
{
if (PropertyChanged == null) return;
if (Application.OpenForms.Count > 0 && Application.OpenForms[0].InvokeRequired)
Application.OpenForms[0].Invoke(new MethodInvoker(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName))));
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Update ObservableCollection from BackgroundWorker/ProgressChanged Event

I'm writing a simple tool for troubleshooting computers. Basically its just a WPF Window with a ListBox bound to an ObservableCollection<ComputerEntry> where ComputerEntry is a simple class containing the computer host name, and Status. All the tool does is ping each compute name in the list, and if a response is received ComputerEntry.Status is updated to indicate the computer is connected to the network somewhere...
Pinging however can take some time, up to a couple seconds per computer depending on if it has to timeout or not. So I'm running the actual ping in a BackgroundWorker and using the ReportProgress method to update the UI.
Unfortunately the ObservableCollection does not seem raise the PropertyChanged event after the objects are updated. The collection does update with the new information, but the status never changes in the ListBox. Presumably because it does not know that the collection has changed.
[EDIT]
Per fantasticfix, the key here is: "The ObservableCollection fires just when the list gets changed (added, exchanged, removed)." Since I was setting the properties of the object instead of modifying it, the ObservableCollection was not notifying the list of the change -- it didn't know how. After implenting INotifyPropertyChanged everything works fine. Conversly, replacing the object in the list with a new updated instance will also fix the problem.
[/EDIT]
Btw I'm using C# 3.5 and I'm not in a position where I can add additional dependancies like TPL.
So as a simplified example [that won't compile without more work...]:
//Real one does more but hey its an example...
public class ComputerEntry
{
public string ComputerName { get; private set; }
public string Status { get; set; }
public ComputerEntr(string ComputerName)
{
this.ComptuerName = ComputerName;
}
}
//...*In Window Code*...
private ObservableCollection<ComputerEntry> ComputerList { get; set; }
private BackgroundWorker RefreshWorker;
private void Init()
{
RefreshWorker = new BackgroundWorker();
RefreshWorker.WorkerReportsProgress = true;
RefreshWorker.DoWork += new DoWorkEventHandler(RefreshWorker_DoWork);
RefreshWorker.ProgressChanged += new ProgressChangedEventHandler(RefreshWorker_ProgressChanged);
}
private void Refresh()
{
RefreshWorker.RunWorkerAsync(this.ComputerList);
}
private void RefreshWorker_DoWork(object sender, DoWorkEventArgs e)
{
List<ComputerEntry> compList = e as List<ComputerEntry>;
foreach(ComputerEntry o in compList)
{
ComputerEntry updatedValue = new ComputerEntry();
updatedValue.Status = IndicatorHelpers.PingTarget(o.ComputerName);
(sender as BackgroundWorker).ReportProgress(0, value);
}
}
private void RefreshWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ComputerEntry updatedValue = new ComputerEntry();
if(e.UserState != null)
{
updatedValue = (ComputerEntry)e.UserState;
foreach(ComputerEntry o in this.ComputerList)
{
if (o.ComputerName == updatedValue.ComputerName)
{
o.Status = updatedValue.Status;
}
}
}
}
Sorry for the jumble but its rather long with all the support code. Anyways, void Refresh() is called from a DispatcherTimer (which isn't shown), that starts RefreshWorker.RunWorkerAsync(this.ComputerList);.
I've been fighting this for a few days so I'm now to the point where I'm not actually attempting to modify the objects referenced in the ObservableCollection directly anymore. Hence the ugly looping through the ComputerList collection and setting the properties directly.
Any idea whats going on here and how I can fix it?
The observableCollection wont fire when you change properties of items which are inside of the collection (how should it even know that). The ObservableCollection fires just when the list gets changed (added, exchanged, removed).
If you want to detect the changes of the properties of the ComputerEntry the class has to Implement the INotifyPropertyChange interface (if you know MVVM, its like a lightweight MVVM pattern)
public class ComputerEntry : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void RaisePropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String _ComputerName;
public String ComputerName
{
get
{
return _ComputerName;
}
set
{
if (_ComputerName != value)
{
_ComputerName = value;
this.RaisePropertyChanged("ComputerName");
}
}
}
}
Haven't used this in a long time, but don't you need something like INotifyPropertyChanged implemented?

Categories

Resources