Label text not updating when bound to property - c#

I have to update the text of a Label. I have bound the Text property of Label to a property and implemented INotifyPropertyChanged event.
My code is as follows:
public partial class MyClass : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string ucText
{
get
{
return _text;
}
set
{
_text = value;
NotifyPropertyChanged("ucText");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MyClass()
{
InitializeComponent();
lblText.DataBindings.Add(new Binding("Text", this, "ucText"));
}
}
In a Button click event in another form, I update the text of the Label as follows:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10000; i++)
{
myClass1.ucText = i.ToString();
}
}
Here myClass1 is object of the UserControl posted above.
In the Button click event, the UI hangs when updating the label and then once the loop completes, shows the final value:
9999
Why is my UI not reactive? I have also tried
lblText.DataBindings.Add(new Binding("Text", this, "ucText", false, DataSourceUpdateMode.OnPropertyChanged));

Both forms are running on the same thread, the UI thread. The following scenario is happening:
Button is clicked
Change text to i
Notify UI
Increment i
Go to 2. if i < 10000
Refresh the UI
As long as the loop isn't done, the UI thread won't redraw, as it's still doing some "heavy" work.
You can of course let a new thread handle the "calculation" and let that thread change the value. To start a new thread use either a backgroundworker or start a new thread with the Thread class.
The binding you are using is in fact working.
Edit: Always remember that all calculation that is directly done on the UI thread will block the UI for the time the calculation needs. Always use other threads to do time intensive calculations.

Related

What is missing in this update UI via dispatcher/databinding

I have a simple WPF window with: Loaded="StartTest"
and
<Grid>
<ListBox ItemsSource="{Binding Logging, IsAsync=True}"></ListBox>
</Grid>
In code behind I have in method StartTest:
LogModel LogModel = new LogModel();
void StartTest(object sender, RoutedEventArgs e)
{
DataContext = LogModel;
for (int i = 1; i<= 10; i++)
{
LogModel.Add("Test");
Thread.Sleep(100);
}
}
And class LogModel is:
public class LogModel : INotifyPropertyChanged
{
public LogModel()
{
Dispatcher = Dispatcher.CurrentDispatcher;
Logging = new ObservableCollection<string>();
}
Dispatcher Dispatcher;
public ObservableCollection<string> Logging { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Dispatcher.BeginInvoke((Action)delegate ()
{
Logging.Add(text);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Logging"));
});
}
}
Of course the problem is that the UI doesn't update in the loop.
What am I missing?
How can I achieve the UI update?
ObservableCollection already raises the PropertyChanged event when it's modified. You don't have to raise the event in the UI thread either.
Your model can be as simple as :
class LogModel
{
public ObservableCollection<string> Logging { get; } = new ObservableCollection<string>();
public void Add(string text)
{
Logging.Add(text);
}
}
All you need to do is set it as the DataContext of your view, eg :
LogModel model = new LogModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
I assume StartTest is a click handler which means it runs on the UI thread. That means it will block the UI thread until the loop finishes. Once the loop finishes the UI will be updated.
If you want the UI to remain responsive during the loop, use Task.Delay instead of Thread.Slepp, eg :
private async void Button_Click(object sender, RoutedEventArgs e)
{
for(int i=0;i<10;i++)
{
await Task.Delay(100);
model.Add("Blah!");
}
}
Update
You don't need to use an ObservableCollection as a data binding source. You could use any object, including an array or List. In this case though you'd have to raise the PropertyChanged event in code :
class LogModel:INotifyPropertyChanged
{
public List<string> Logging { get; } = new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
}
This will tell the view to load all the contents and display them again. This is perfectly fine when you only want to display data loaded eg from the database without modifying them, as it makes mapping entities to ViewModels a lot easier. In this case you only need to update the view when a new ViewModel is attached as a result of a command.
This is not efficient when you need to update the coolection though. ObservableCollection implements the INotifyCollectionChanged interface that raises an event for each change. If you add a new item, only that item will be rendered.
On the other hand you should avoid modifying the collection in tight loops because it will raise multiple events. If you load 50 new items, don't call Add 50 times in a loop. Create a new ObservableCollection, replace the old one and raise the PropertyChanged event, eg :
class LogModel:INotifyPropertyChanged
{
public ObservableCollection<string> Logging { get; set; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
public void BulkLoad(string[] texts)
{
Logging = new ObservableCollection<string>(texts);
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Logging"));
}
}
The explicit implementation is still needed because the Logging property is getting replaced and can't raise any events itself
The reason why the UI is not updated in the loop is a call to Dispatcher.BeginInvoke. This places a new DispatcherOperation in the dispatcher queue. But your loop is already a dispatcher operation, and it continues on the Dispatcher's thread. So all the operations you queue will be executed after the loop's operation is finished.
Maybe you wanted to run the StartTest on a background thread? Then, the UI will update.
By the way, don't block the Dispatcher's thread with Thread.Sleep. It prevents the Dispatcher from doing its things as smoothly as possible.
It is the DoEvents thing, overhere:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
or even the perhaps better https://stackoverflow.com/a/11899439/138078.
Of course the test should be written differently in a way which does not require it.

Updating viewmodel from worker thread throws cross thread issue

Recently, I've been testing with binding of type which implements INotifyPropertyChanged and updating property from worker thread throwing cross thread issue.
Here is sample code :
public class MyViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler hanlder = PropertyChanged;
if(hanlder != null)
hanlder(this, new PropertyChangedEventArgs(propertyName));
}
}
above viewmodel has been bind with label text in windows form and updating label value from worker thread.
Updating label1 text from worker thread causes cross thread issue :
public partial class MainForm : Form
{
private MyViewModel _myViewModel = new MyViewModel();
public MainForm()
{
InitializeComponent();
Btn1.Click += Btn1_Click;
label1.DataBindings.Add("Text", _myViewModel, "Name");
}
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
});
}
}
So far, I can believe is that it is due to updating UI from worker thread. Is there any work around to make it thread safe without changing in button click method i.e. probably making thread safe in viewmodel.
Grab the UI's SynchronizationContext (using SynchronizationContext.Current from the UI thread when the app starts, for example), and store it in some static variable somewhere (i've called it uiSynchronizationContext).
Then on your OnPropertyChanged do something like:
protected virtual void OnPropertyChanged(string propertyName)
{
uiSynchronizationContext.Post(
o => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))
,null
);
}
Or you can use Send instead of Post if you want that operation synchronous (synchronous to the thread that started the send/post operation, it'll always be "synchronous" on the UI thread)
I particulary don't like doing direct databinding when multithreading (I prefer to poll with a timer on the UI thread from some graph object with the changes), but this should solve the problem.
I admiteddly haven't tested it
Did you try with CheckForIllegalCrossThreadCalls ?
Like this :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
CheckForIllegalCrossThreadCalls = false;
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
CheckForIllegalCrossThreadCalls = true;
});
}
I don't say that is the best way but it's working for me when I work with thread
EDIT :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Invoke((MethodInvoker)(() => _myViewModel.Name = "Updating from worker thread!"; ));
});
}

Wpf ItemSource Doesn't Update Visual Control in custom control

I have a custom control with itemsSource binding:
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
Results.Clear();
foreach (var check in newValue)
{
Results.Add(check as Check);
}
}
protected ObservableCollection<Check> results = new ObservableCollection<Check>();
public ObservableCollection<Check> Results
{
get { return results; }
set { results = value; }
}
Implemented in the main view:
<control:ResultCtrl x:Name="resultCtrl" ItemsSource="{Binding Path=Results, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"></control:ResultCtrl>
Check class:
public class Check : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected string checkStateString;
public string CheckStateString
{
get { return checkStateString; }
set
{
if (value != checkStateString)
{
checkStateString = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("CheckStateString"));
}
}
}
I call a class who calculate checks in the Main View show method:
Thread t = new Thread(new ThreadStart(
delegate
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action<ResultCtrl>(AddIn.Services.Results.Comprobaciones), resultCtrl);
}
));
t.Start();
In AddIn.Services.Results.Comprobaciones I do:
resultCtrl.ItemsSource = new ObservableCollection<Check>(AddIn.Document.Results);
for every check. Every time I do that I see how ItemsSource change, but Visual only update when the AddIn.Services.Results.Comprobaciones end. I tried to do UpdateLayout() and Items.Refresh() but nothing work.
Any ideas?
This code:
Thread t = new Thread(new ThreadStart(/* ... */);
t.Start();
creates a thread, that is completely useless, because everything it does is a blocking call to UI thread's Dispatcher. In other words, 99.999...% of time it runs on UI thread. You could easily write this:
AddIn.Services.Results.Comprobaciones();
with the same result.
You have to rewrite your code for having any benefits from multi-threading. I have no idea, how does your Comprobaciones method look like, but, obviously, you should call Dispatcher.Invoke only when you need to update something in UI.
Also note, that in most cases you shouldn't create Thread instances directly. Consider using TPL instead (possibly, via async/await, if you're targeting .NET 4.5).

Update the MessageBoxText runtime using System.Windows.MessageBox?

I'm using System.Windows.MessageBox to show the message to user, But I want to update the text on that Messagebox, after showing it.
The example in my case, I want to show the MessageBox with the content that can change at runtime as below:
"The system will be restarted in 5 seconds"
"The system will be restarted in 4 seconds"
"The system will be restarted in 3 seconds"
"The system will be restarted in 2 seconds"
"The system will be restarted in 1 second"
"The system will be restarted in 0 second"
Someone can show me how to do it?
Many thanks,
T&T
I think it's easier to use another window instead of MessageBox. Then you turn off features that you don't want (resizing, close button), make it modal, set up timer event handling, and so on.
Someone can show me how to do it
You can't do it with standard messagebox i.e. System.Windows.MessageBox.
Alternative:
Though what you can do is to define a custom message box(a windows form) with a label on it that you update via event asynchronously. And you use that to display user the count down.
This can be possible with MessageBox from Extended WPF Toolkit.
It has Text dependency property, which can be data bound, but, unfortunately, MessageBox initialization is hidden, and solution contains more than single line:
First of all, we need our MessageBox ancestor, because we're going to call protected InitializeMessageBox method (to apply standard message box settings). Then, we need to make our Show overload, which will apply binding to Text:
class MyMessageBox : Xceed.Wpf.Toolkit.MessageBox
{
public static MessageBoxResult Show(object dataContext)
{
var messageBox = new MyMessageBox();
messageBox.InitializeMessageBox(null, null, "Hello", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel);
messageBox.SetBinding(MyMessageBox.TextProperty, new Binding
{
Path = new PropertyPath("Text"),
Source = dataContext
});
messageBox.ShowDialog();
return messageBox.MessageBoxResult;
}
}
Next, we need a data context:
sealed class MyDataContext : INotifyPropertyChanged
{
public string Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private string text;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And a code, which will change the text in message box:
public partial class MainWindow : Window
{
private readonly MyDataContext dataContext;
private readonly DispatcherTimer timer;
private int secondsElapsed;
public MainWindow()
{
InitializeComponent();
dataContext = new MyDataContext();
timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.ApplicationIdle,
TimerElapsed, Dispatcher.CurrentDispatcher);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
secondsElapsed = 0;
timer.Start();
MyMessageBox.Show(dataContext);
timer.Stop();
}
private void TimerElapsed(object sender, EventArgs e)
{
dataContext.Text = string.Format("Elapsed {0} seconds.", secondsElapsed++);
}
}
The benefit of this approach is that you don't need to write yet another message box.

Bind a textbox to a property class

I have these :
A Form with a Label on it
A class "Business"
A class "TimerHelper"
I'd like when I update the property MyTime, update the textbox too
My "Business" class look like this :
public class MyBusinessClass : INotifyPropertyChanged
{
public void MakeSound(object sender, ElapsedEventArgs e)
{
// I change MyTime here
}
public event PropertyChangedEventHandler PropertyChanged;
private int myTime;
public int MyTime
{
get { return myTime; }
set
{
myTime= value;
InvokePropertyChanged(new PropertyChangedEventArgs("MyTime"));
}
}
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
The "TimerHelper" look like this :
public class TimerHelper
{
private Timer _timer;
public void Run()
{
_timer = new Timer(1000);
_timer.Enabled = true;
_timer.Elapsed += new ElapsedEventHandler(MyBusinessClass.MakeSound);
}
}
In the forms, I tried this :
myTextBox.DataBindings.Add("Text", new MyBusinessClass(), "MyTime");
but I get an exception in the method "InvokePropertyChanged"
I tried this :
Invoke((MethodInvoker)delegate { myTextBox.Text = new MyBusinessClass().MyTime; });
but the textBox is never updated
I found the answer there :
A generic asynchronous INotifyPropertyChanged helper
I think your problem is you are trying to update the text box through databibding from other thread.
I mean, you're using System.Timers.Timer class which invokes Elapsed event in a threadpool.
Databinding fails updating the control because you're changing your business class in that threadpool.
You should change of thread context in the Elapsed event handler and update your business model in the main thread (where text box was created).
public class TimerHelper
{
private Timer _timer;
public void Run()
{
_timer = new Timer(1000);
_timer.Enabled = true;
_timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
}
}
void OnTimerElapsed (object sender, ElapsedEventArgs e)
{
if (myTextBox.InvokeRequired)
{
myTextBox.Invoke(MyBusinessClass.MakeSound);
}
}
}
Although I do not see where you are setting the value of MyTime (I assume it is in MakeSound?), you are setting the value of MyTime on a thread other than the UI thread. You are probably using the System.Timers.Timer class, which does not execute the event handler on the UI thread. If you want to use System.Timers.Timer and make changes to the UI in its ElapsedEventHandler delegate, you need to make use of the SynchronizingObject property on the timer. See this MSDN article for more information: Comparing the Timer Classes

Categories