This is probably a really basic question, please bear with me, I'm still very new to the world of WPF/C#.
I have a WPF app where I open a new window if a button is clicked.
The window is called Sync and all it does is instantiate a viewmodel class which contains some public properties that are bound to my view.
The viewmodel also instantiates a class containing a lot of business logic, this updates the ViewModel's bound properties, the aim being to update the content of my window.
This sort of works, but only when all of the (sometimes quite lengthy) processing is completed does the window load and the view is populated with the last value of the ViewModel's properties.
I think I'm missing something pretty basic here. How do I get my window to instantly load and then have the view update when any of the properties have changed? Should I be listening for a PropertyChanged event and then updating the view? Where do i do this? Within the view model's setter?
Here's some simplified code:
Calling my window from my main window's View Model
public void SyncAction()
{
Sync syncWindow = new Sync();
syncWindow.Show();
syncWindow.Activate();
}
The window
public partial class Sync : Window
{
public Sync()
{
InitializeComponent();
var viewModel = new SyncViewModel();
}
}
The view model
class SyncViewModel
{
private string _miscStatus = "";
public SyncViewModel()
{
var sync = new SyncLogic();
sync.SyncAll(this);
}
public string MiscStatus
{
get
{
return _miscStatus;
}
set
{
_miscStatus += value;
}
}
}
Some business logic
class SyncLogic
{
private ViewModel.SyncViewModel _syncViewModel;
public void SyncAll(ViewModel.SyncViewModel syncViewModel)
{
_syncViewModel = syncViewModel;
// lock our synctime
var syncTime = DateTools.getNow();
_syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString();
// Do lots of other stuff
_syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}",
DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());
}
}
Bonus question: The way I'm updating the view from within my business logic (by passing in a reference to the viewmodel and updating its properties from there) seems a bit kludgy. I definitely want to keep the business logic separate, but am not sure how I can pass any output back out to the viewmodel. What would be a better way of doing this please?
Why do you care whether the update takes visual effect before or after the code finishes executing? The internal properties are updated immediately; any code that queries the UI will see the new values.
The only time the user will be able to perceive a difference between an update during execution vs after execution is if you have a long-running computation on the UI thread. Don't do that.
Instead, run the computation asynchronously with the UI, so that repaint messages can be processed meanwhile. You can do this using a background thread, but the new easier way with C# 4 and later is async. Because async is implemented using continuation messages to the UI thread, you don't need to synchronize data access or marshal UI access between threads. It just works, and very well. The only thing you need to do is to break your code into small enough chunks, each implemented as an async method, that you don't cause noticeable delay.
What I would do:
Don't do any heavy logic in the ViewModel constructor. Constructor should only initialize object and do nothing else. In your example, constructor should be empty.
public SyncViewModel()
{
}
SyncLogic should not be aware of the ViewModel. Introduce some other class to communicate input arguments and sync results. Let's say SyncArguments and SyncResult.
class SyncLogic
{
public SyncResult SyncAll(SyncArguments syncArgs)
{
var syncResult = new SyncResult();
// Do lots of other stuff
// populate syncResult
return syncResult;
}
}
Introduce a method in the viewmodel that should be called to do the "sync" logic, and make that method async. That way it's very easy to do the heavy stuff in the background and leave the UI thread to do the job it should do, draw the UI.
public async Task Sync()
{
// lock our synctime
var syncTime = DateTools.getNow();
MiscStatus = "Sync starting at " + syncTime.ToString();
var sync = new SyncLogic();
var syncArgs = new SyncArguments();
//populate syncArgs from ViewModel data
//call the SyncAll as new Task so it will be executed as background operation
//and "await" the result
var syncResults = await Task.Factory.StartNew(()=>sync.SyncAll(syncArgs));
//when the Task completes your execution will continue here and you can populate the
//ViewModel with results
MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}",
DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());
}
Make the button click event handler that creates and shows the window async, so you can call Sync method on the ViewModel
private void async Button_click(object sender, EventArgs e)
{
Sync syncWindow = new Sync();
var viewModel = new SyncViewModel();
syncWindow.DataContext = viewModel;
syncWindow.Show();
syncWindow.Activate();
await viewModel.Sync();
}
That will draw the Window without waiting on the Sync method. When the Sync taks completes, viewmodel properties will be populated from the SyncResult and the Bindings will draw them on screen.
Hope you get the idea, sorry if there are some errors in my code, not sure that it all compiles.
First, make sure to set the viewmodel as the view's DataContext:
public partial class Sync : Window
{
public Sync()
{
InitializeComponent();
var viewModel = new SyncViewModel();
DataContext = viewModel;
}
}
Second, you'll have to run the "sync" stuff on a background thread. This is easiest with the async+await keywords in .Net 4.5:
public async void SyncAll(ViewModel.SyncViewModel syncViewModel)
{
_syncViewModel = syncViewModel;
// lock our synctime
var syncTime = DateTools.getNow();
_syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString();
await Task.Factory.StartNew(() => {
// Do lots of other stuff
});
_syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}",
DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());
}
With databinding your Window will automatically updated as long as it notified that properties it bound to has been changed. So what you need is implement INotifyPropertyChanged in the viewmodel and raise property changed event whenever binding source property value changed. For example:
public class SyncViewModel : INotifyPropertyChanged
{
private string _miscStatus = "";
public string MiscStatus
{
get{ return _miscStatus; }
set
{
_miscStatus += value;
OnPropertyChanged("MiscStatus");
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
In case somebody else comes across this issue in WPF, the solution described here is really simple and just worked fine for me. It uses an extension method to force an UIElement to be rendered:
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
Then, simply use as:
private void SomeLongOperation()
{
// long operations...
// UI update
label1.Content = someValue;
label1.Refresh();
// continue long operations
}
}
Quoting the original author:
The Refresh method is the extension method that takes any UI element and then calls that UIElement's Dispatcher's Invoke method. The trick is to call the Invoke method with DispatcherPriority of Render or lower. Since we don't want to do anything, I created an empty delegate. So how come this achieves refresh functionality?
When the DispatcherPriority is set to Render (or lower), the code will then execute all operations that are of that priority or higher. In the example, the code already sets label1.Content to something else, which will result in a render operation. So by calling Dispatcher.Invoke, the code essentially asks the system to execute all operations that are Render or higher priority, thus the control will then render itself (drawing the new content). Afterwards, it will then execute the provided delegate (which is our empty method).
Related
I'm having an issue with updating WPF UI with the RX. Currently I have a class that has an event which is called within its functions. Event is subscribed from the UI thread and updates the UI like below :
SomeClass.cs
public partial class SomeClass
{
public delegate Task ProgressUpdate(string value);
public delegate Task BarUpdate(int value);
public event ProgressUpdate OnProgressUpdateList;
public event BarUpdate OnProgressUpdateBar;
public async Task DoSomething()
{
// execute code
<some code>
// update UI
if (OnProgressUpdateList != null)
{
OnProgressUpdateList(update);
}
}
}
And in MainWindow.xaml
var someClass = new SomeClass();
someClass.OnProgressUpdateList += Export_OnProgressUpdateList;
someClass.OnProgressUpdateBar += Export_OnProgressUpdateBar;
private async Task Export_OnProgressUpdateList(string text)
{
await Dispatcher.InvokeAsync(() =>
{
OutputLog.AppendText(text);
OutputLog.AppendText(Environment.NewLine);
OutputLog.ScrollToEnd();
});
}
This code works except the program processes huge number of files and I'm assuming this is why the UI becomes frozen very quickly (I see the updates being done in the first half a second). I searched for a way around this and I came into a solution to use RX for batching the UI calls. I've searched through several SO posts but I couldn't find an answer on how to correctly implements this (or convert C# events to RX observables) when I call those events from the class and subscribe to this event from outside that class. Can someone help me understand this?
I'm posting an answer to myself as I couldn't get one here and I finally figured it out so for anyone looking for that in the future - here you go:
public partial class SomeClass {
public Subject<string> outputLogSubject = new Subject<string>();
public IObservable<string> OutputLog => outputLogSubject.AsObservable();
//Add string for collection updating UI
outputLogSubject.OnNext(string);
//After finishing the work you can call outputLogSubject.OnCompleted() to stop buffering
outputLogSubject.OnCompleted();
}
It needs to be added in the class that will be calling the executing the work.
Below needs to be added in the UI thread after initialization and BEFORE processing work :
var buffer = someClass.OutputLog.Buffer(TimeSpan.FromMilliseconds(1000), 6);
var chunked = buffer.ObserveOnDispatcher(DispatcherPriority.Background);
var update = chunked.Subscribe(name =>
{
foreach (var item in name)
{
OutputLog.AppendText(item);
}
OutputLog.ScrollToEnd();
});
This allowed me to keep the UI responsive to the point of seeing the output log is real time
I have a class with properties that are bound to my view. To keep my view up-to-date, I implement INotifyPropertyChanged and raise the event everytime some property changes.
Now I got some heavy functions that freeze my application. I want to put them into a background task.
First: here my current approach
(e.g. on button click)
private async void HeavyFunc()
{
foreach (var stuff)
{
count += await Task.Run(() => stuff.Fetch());
}
if (count == 0)
//...
}
stuff class
public async Task<int> Fetch()
{
//network stuff
RaisePropertyChanged("MyProperty");
}
public async void RaisePropertyChanged(string pChangedProperty)
{
await Application.Current.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new ThreadStart(() =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pChangedProperty);
}
);
}
The code above gives an exception ("DependencySource" must be created in the same thread like "DependencyObject").
AFAIK, you generally need to create a new thread and run it (while awaiting it). ´await Task.Run(...);´ should do this job.
Since the PropertyChanged event directly influences the UI, calling it in the UI thread seems to be a good decision. This is why I call Dispatcher.BeginInvoke.
What I don't understand: the exception above is caused when different threads are responsible for the data. But I explicitely calling the event on my UI-thread and the object should be created by the UI-thread too. So why do I get an exception?
My main question is: How do I implement the events for the INotifyPropertyChanged interface generally to avoid or handle most of the async programming problems like above? What should be considered while constructing the functions?
Now I got some heavy functions that freeze my application.
If you're really doing asynchronous "network stuff", then it shouldn't be freezing the app.
My main question is: How do I implement the events for the INotifyPropertyChanged interface generally to avoid or handle most of the async programming problems like above?
The approach that I prefer is to not handle this in the event raising code. Instead, structure the rest of your code so that it respects the UI layer.
In other words, divide your "service" (or "business logic") code from your "UI" code so that it works like this:
// In StuffService class:
public async Task<Result> FetchAsync()
{
//network stuff
return result;
}
// In StuffViewModel class:
public async void ButtonClicked()
{
foreach (var stuff)
{
var result = await Task.Run(() => _stuffService.FetchAsync());
MyProperty = result.MyProperty;
count += result.Count;
}
if (count == 0)
//...
}
public Property MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] string pChangedProperty = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pChangedProperty));
}
This way, there's no manual thread jumping, all properties have the standard ViewModel implementation, the code is simpler and more maintainable, etc.
I did leave in the call to Task.Run, although this should be superfluous if your network calls are truly asynchronous.
I use MVP pattern in my WinForms app. I've problem with implementation Real-Time data drawing Chart (MSChart).
I've some algorithm and presenter class:
public class Algorithm
{
private double parameter1;
public void Execute()
{
for (int i = 0; i < 1000; i++)
{
...
if (i % 10 == 0)
{
parameter1 = parameter1 * 0.95;
}
...
}
}
public class MainWindowPresenter
{
public void RunAlgorithm()
{
Algorithm alg = new Algorithm();
alg.Execute();
}
}
I execute this algorithm in Presenter class. I want to notify View of change parameter1 and pass this change to chart (MSChart) and of course draw in Chart. This is my Form class:
public partial class MainWindow : Form, IMainWindowView
{
private MainWindowPresenter presenter;
...
private void btn_Start_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() => presenter.RunAlgorithm());
}
}
Drawing in Real-Time is no problem - I use Task, but how to notify View and Form ?
It is not entirely clear to me just exactly what you are trying to achieve from your example code, but based on the information provided, something alongs the lines of the following should work:
Change Algorithm to expose its parameter1 value (e.g. by making it a public property).
Your current design has one Algorithm instance per invocation of RunAlgorithm and thus possibly multiple parameter1 values simultaneously, which seems unintended. Make alg a member of the MainWindowPresenter class so that you have one instance that is no longer scoped to the RunAlgorithm method. Add appropriate locking to prevent concurrency issues.
Add events to the Algorithm and MainWindowPresenter classes to notify observers (the form/view) of changes to parameter1. The MainWindowPresenter can forward the events fired by the Algorithm class. Note that these events will run on the thread that your Task is running on. In order to update controls in event handlers attached to these events you will then typically have to Invoke the UI thread.
I am writing a Windows Store App toy application for Windows 8.
It has just one xaml page with a TextBlock. The page has the class MyTimer as DataContext :
this.DataContext = new MyTimer();
MyTimer implements INotifyPropertyChanged and the updating of the property Time is made with a timer:
public MyTimer(){
TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged);
TimeSpan period = new TimeSpan(0, 0, 1);
ThreadPoolTimer.CreatePeriodicTimer(f, period);
}
with
private void NotifyTimeChanged(){
if (this.PropertyChanged != null){
this.PropertyChanged(this, new PropertyChangedEventArgs("Time"));
}
}
the TextBlock has a databinding on Time
<TextBlock Text="{Binding Time}" />
When I run the application i have the following exception:
System.Runtime.InteropServices.COMException was unhandled by user code
With the message
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
The real problem is that I am updating the property of the class MyTimer, not the GUI itself,
I can't figure it out, but I think the solution should use something like this one.
Yes, you're notifying property changes from a thread pool thread rather than the UI thread. You need to marshal the notification back to the UI thread in the timer callback. Now, your view model is separated from your view (a good thing) therefore it doesn't have a direct link to the Dispatcher infrastructure. So what you want to do is hand it the proper SynchronizationContext on which to communicate. To do this you need to capture the current SynchronizationContext during construction or allow it to be passed in explicitly to a constructor which is good for tests or if you're initializing the object off the UI thread to begin with.
The whole shebang would look something like this:
public class MyTimer
{
private SynchronizationContext synchronizationContext;
public MyTimer() : this(SynchronizationContext.Current)
{
}
public MyTimer(SynchronizationContext synchronizationContext)
{
if(this.synchronizationContext == null)
{
throw new ArgumentNullException("No synchronization context was specified and no default synchronization context was found.")
}
TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged);
TimeSpan period = new TimeSpan(0, 0, 1);
ThreadPoolTimer.CreatePeriodicTimer(f, period);
}
private void NotifyTimeChanged()
{
if(this.PropertyChanged != null)
{
this.synchronizationContext.Post(() =>
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Time"));
});
}
}
}
One way to do this is awaiting Task.Delay() in a loop instead of using a timer:
class MyTimer : INotifyPropertyChanged
{
public MyTimer()
{
Start();
}
private async void Start()
{
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(1));
PropertyChanged(this, new PropertyChangedEventArgs("Time"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public DateTime Time { get { return DateTime.Now; } }
}
If you call the constructor on the UI thread, it will invoke the PropertyChanged there too. And the nice thing is that exactly the same code will work for example in WPF too (under .Net 4.5 and C# 5).
how about the code from this blog:
http://metrowindows8.blogspot.in/2011/10/metro-tiles.html
This worked for me.
I had to pass a ThreadPoolTimer object to my delegate function
Sorry I still don't understand how UI works and what is Dispatcher
I have such DispatchingWcfModel:
public interface IWcfModel
{
List<ConsoleData> DataList { get; set; }
event Action<List<ConsoleData>> DataArrived;
}
class DispatchingWcfModel : IWcfModel
{
private readonly IWcfModel _underlying;
private readonly Dispatcher _currentDispatcher;
public DispatchingWcfModel(IWcfModel model)
{
_currentDispatcher = Dispatcher.CurrentDispatcher;
_underlying = model;
_underlying.DataArrived += _underlying_DataArrived;
}
private void _underlying_DataArrived(List<ConsoleData> obj)
{
Action dispatchAction = () =>
{
if (DataArrived != null)
{
DataArrived(obj);
}
};
_currentDispatcher.BeginInvoke(DispatcherPriority.DataBind, dispatchAction);
}
public List<ConsoleData> DataList
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public event Action<List<ConsoleData>> DataArrived;
}
Now I want to add int[] ConnectionStats { get; set; }. Should I introduce separate event for it? What should I write in DispatchingWcfModel? I want to have interface like that:
public interface IWcfModel
{
List<ConsoleData> DataList { get; set; }
int[] ConnectionStats { get; set; }
event Action<List<ConsoleData>> DataArrived;
}
The Dispatcher is WPF's internal message queue for the main UI Thread. It can be used from other threads to run commands on the main UI thread of an application.
This is important because WPF doesn't let you access objects which were created on other threads. For example, if a Button is created on the main UI thread, then you cannot modify this button from another thread, but you can use the Dispatcher from another thread to send a command to the main UI thread to update the button.
This applies to all objects, not just UI Elements. If something like an ObservableCollection is created on one thread, another thread cannot modify it. Because of this, all objects are usually created on the main UI thread.
Dispatcher messages can get processed synchronously or asynchronously, and they can have different priorities . In your code, you're using _currentDispatcher.BeginInvoke(DispatcherPriority.DataBind, dispatchAction);, which means it will begin an asynchronous operation with the main UI thread, and run it at the same priority as DataBinding. You can view more information on DispatcherPriorities here.
If your ConnectionStats collection gets its data asynchronously, then you will want to add some kind of DataArrived method that will take data obtained from a non-UI thread and add it to the collection. You can use the existing DataArrived method if the data is obtained and packaged together, or create your own if the data is obtained separately. If it gets it's data synchronously, you don't need to do anything special.
From the looks of things, _underlying_DataArrived is meant to run on a background thread (meaning it can't alter your collections, which should be created on the main UI thread), while DataArrived is meant to run on the main UI thread.