in silverlight and MVVM , I can fire the busy indicator for example
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
RaisePropertyChanged("IsBusy");
}
}
ObervableCollection<OperationBase> _pendingOperations = new ObervableCollection<OperationBase>();
public ViewModelBase()
{
_pendingOperations.CollectionChanged +=(s,e)=>
{
if( _pendingOperations.Count > 0)
IsBusy = true // has operation going on
else
IsBusy = false; //no operation
}
}
void LoadData()
{
LoadOperation op = Context.Load(YourQuery, lo=>
{
_pendingOperations.Remove(lo); // lo is the operation, remove it
if (lo.HasError)
{
lo.MarkErrorAsHandled();
MessageBox.Show(lo.Error.Message);
}
});
_pendingOperations.Add(op);// add operation to the collection
}
void SaveData()
{
SubmitOperation so = this.context.SubmitChanges(s =>
{
_pendingOperations.Remove(s);
if (s.HasError)
{
s.MarkErrorAsHandled();
MessageBox.Show(s.Error.Message);
}
}, null);
_pendingOperations.Add(so);// add operation to the collection }
}
...
}
I want to do the same in MVC , any idea how to achieve that for example on search , create or any long process I need to show busy indicator and close it at the end , I know there's no property changed , am wonder if ther's any way
Assumeing you mean MVC -> ASP.NET MVC or something HTML/Javascript based with some sort of web server component.
In general you would have an animated gif (A spinning wheel for example) and show hide them while you are waiting for a long running operation.
In Javascript you can take advantage of Promises or use generic callback functions.
//pseudo code
loadData(function(data){
// data came back async
// do something with data
$('#loader').hide();
});
$('#loader').show();
or with promises
//method should return promise
var promise = loadData();
$('#loader').show();
promise.done(function(data){
// do something with data
$('#loader').hide();
});
you should of course do handle the error case too but same principles apply....
Related
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.
In my windows phone 8.1 application I have a singleton service DataService which should once in a while be downloading some data. Meanwhile on UI I should be displaying the amount of data received. DataService.StartGettingData() gets called when user logs into the application:
void StartGettingData()
{
if (getDataTaskCancellationTokenSource != null)
getDataTaskCancellationTokenSource.Cancel();
getDataTaskCancellationTokenSource = new CancellationTokenSource();
var token = getDataTaskCancellationTokenSource.Token;
Task.Factory.StartNew(async () => await ExecuteCycleAsync(token), token);
}
async Task ExecuteCycleAsync(CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
await LoadDataAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(timeTillNextDownload, cancellationToken);
}
}
This task will be cancelled when user logs out with the help of
if (getDataTaskCancellationTokenSource != null)
getDataTaskCancellationTokenSource.Cancel();
The property containing the result of download looks like this:
List<DataType> Data = new List<DataType>();
public IEnumerable<DataType> Data
{
get { return Data; }
set
{
Data = value.ToList();
OnDataUpdated();
}
}
void OnDataUpdated()
{
var handler = DataUpdated;
if (handler != null)
handler(this, EventArgs.Empty);
}
This part seemed to be working until I had to display the amount of data on the screen.
My MainViewModel gets instance of DataService injected with Ninject.
readonly IDataService DataService;
public MainViewModel(IDataService dataService)
{
DataService = dataService;
DataService.DataUpdated += DataService_DataUpdated;
UpdateDataCount();
}
void DataService_DataUpdated(object sender, EventArgs e)
{
UpdateDataCount();
}
void UpdateDataCount()
{
DataCount = DataService.Data.Count();
}
In xaml I've got TextBlock binded to DataCount property of MainViewModel
int DataCount;
public int DataCount
{
get { return DataCount; }
set
{
DataCount = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And here is where problem appears: OnPropertyChanged fails with
"The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" which get's caught in DataService.LoadDataAsync().
I understand the runtime is trying to tell me I am accessing UI element from non ui thread. But am I? I thought OnPropertyChanged is the magic place which disconnects UI from the rest of background tasks.
Of course, the problem can be solved implementing OnPropertyChanged this way:
public CoreDispatcher Dispatcher { get; set; }
protected async void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
handler(this, new PropertyChangedEventArgs(propertyName));
});
}
}
But should it really be implemented this way? Or am I missing something in DataService.ExecuteCycleAsync()?
Not trying to dig any deeper, I believe your problem is this:
Task.Factory.StartNew(async () => await ExecuteCycleAsync(token), token);
Change it to simply this, see if it works as expected:
ExecuteCycleAsync(token);
Otherwise, the code inside ExecuteCycleAsync starts execution on a thread without synchronization context, which can lead to all different kinds of problems, depending on what's inside LoadDataAsync.
Note that calling ExecuteCycleAsync(token) like this is still a fire-and-forget call which may not be observing any exceptions (more here). You may want to store the Task object it returns, to be able to observe it later.
I have designed MMVM pattern in C#. My GUI has different button. Each button is for particular command. These commands are derived from CommandsBase Class. Each command runs on seperate thread by calling CommandExecute. There are several commands like CommandRunMode1, CommandRunMode2, commandDiagnosys etc. Now new requirement has been arised to abort command. I am trying to write CommandAbort Class. And the problem is how to abort already executing command when ABORT button is pressed on GUI (i.e. stop other thread in the halfway from CommandAbort class thread).
enter code here
#region COMMAND_Base
public abstract class CommandsBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected delegate void NoArgsDelegate();
protected delegate void OneArgDelegate(string arg1);
protected delegate void TwoArgDelegate(string arg1, bool arg2);
protected ViewModelBase ParentViewModel;
private StatusIndicator _isEnableState;
string _uiText;
bool _uiEnable;
RelayCommand _command;
protected Dispatcher _dispatcher;
private readonly int _responseDelay = 2000; // milliseconds
#region PROPERTIES
public CommandText CommandText { get; set; } // Ui Text
public CommandStatusIndicator CommandStatus { get; set; } // background color
public StatusIndicator IsEnableState
{
get { return _isEnableState; }
set
{
_isEnableState = value;
OnChanged("Status");
}
}
public string UiText
{
get { return _uiText; }
set
{
_uiText = value;
OnChanged("UiText");
}
}
public bool UiEnabled
{
get
{
return _uiEnable;
}
set
{
_uiEnable = value;
OnChanged("UiEnabled");
}
}
public ICommand Command
{
get
{
if (_command == null)
{
_command = new RelayCommand(param => this.CommandExecute(), param => this.CommandCanExecute);
}
return _command;
}
}
public int NumberOfAttempts;
public int ResponseDelay
{
get
{
return _responseDelay;
}
}
#endregion
protected CommandsBase()
{
}
protected void UpdateUi(string text)
{
UiText = text;
}
protected void UpdateUi(bool enabled)
{
UiEnabled = enabled;
}
protected void UpdateUi(string text, bool enabled)
{
UiText = text;
UiEnabled = enabled;
}
#region COMMAND_EXECUTION
public virtual void CommandExecute()
{
NoArgsDelegate commandExecution = new NoArgsDelegate(CommandExecuteInAThread);
commandExecution.BeginInvoke(null, null);
}
protected abstract void CommandExecuteInAThread();
public abstract bool CommandCanExecute { get; }
#endregion
public virtual void OnChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
region COMMAND_RunMode1
public class CommandRunMode1 : CommandsBase
{
public CommandRunMode1(string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Mode1
ApplicationLayer.TransmitString("START1");
while (Display.CurrentScreent != DisplayController.CurrentScreen.ST1SCREEN);
ApplicationLayer.TransmitString("MODE1ENTER");
while (Display.CurrentScreent != DisplayController.CurrentScreen.MD1SCREEN);
ApplicationLayer.TransmitString("PROCESSCREEN");
while (Display.CurrentScreent != DisplayController.CurrentScreen.PROCESSCREEN);
}
#endregion
}
endregion
region COMMAND_RunMode2
public class CommandRunMode2 : CommandsBase
{
public CommandRunMode2(string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Mode2
ApplicationLayer.TransmitString("START2");
while (Display.CurrentScreent != DisplayController.CurrentScreen.ST2SCREEN);
ApplicationLayer.TransmitString("MODE2ENTER");
while (Display.CurrentScreent != DisplayController.CurrentScreen.MD2SCREEN);
ApplicationLayer.TransmitString("PROCESSCREEN");
while (Display.CurrentScreent != DisplayController.CurrentScreen.PROCESSCREEN);
}
#endregion
}
endregion
region COMMAND_Abort
public class CommandAbort : CommandsBase
{
public CommandAbort (string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Abort currently running command
ApplicationLayer.TransmitString("ABRT");
}
#endregion
}
endregion
Cancellation of a thread should always be cooperative.
What do i mean by that?
The code running in your thread should periodically check to see if it should continue. This may be a boolean somewhere. If cancellation is required, simply cleanup the resources you are using and return.
volatile bool IsCancelled = false;
void DoWork()
{
while(!IsCancelled)
{
//do work
}
}
When is this flag set?
Perhaps you have a Cancel button. Pushing this would trigger an event handler. This event handler sets the flag. The next time your threaded code checks the flag, it will cancel the operation. This is why it is called cooperative cancellation. 2 threads work together to make it happen.
Im afraid you have an architectural challenge to overcome
In MVVM, commands are the pipeline in which user interaction is communicated to the view model. The View model should react to this command by 'doing to the work' by calling the appropriate methods on other classes ( your model/foundation layer/business objects...use whichever words you prefer). You will need an implementation of DelegateCommand or RelayCommand.
User clicks Button
Button executes command
Command invokes method on view model
View model calls your domain classes to do the work
Why structure it this way? Now you can have your cancellation flag in the view model. This is one of the things a view model is for - storing state!
If you are able to, I recommend using .Net's Task Parallel Library. It supports cooperative cancellation for free!
To sum up. I strongly recommend you move the serial code out of the command classes. It is possible to make it work with your design but this is not good practice :(
I am trying to run something like a background thread and notify a ViewModel of the status of this background thread by raising an event. The ViewModel in turn raises the OnPropertyChanged event. Unfortunately the corresponding view will not update.
The part of the background thread where I notify the ViewModel can be seen here:
private void RunThread() {
while(true) {
suspendEvent.WaitOne(Timeout.Infinite);
if (shutDownEvent.WaitOne(0)) {
break;
}
if (pauseEvent.WaitOne(0)) {
pauseEvent.Reset();
}
Notify("Cleaning started!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
Clean();
Notify("Cleaning finished!");
pauseEvent.WaitOne(TimeSpan.FromSeconds(5));
}
}
private void Notify( String status ) {
NotifyOfCleanerStatusHandler handler = NotifyOfcleanerStatus;
if (handler != null) {
handler(status);
}
}
The part of the ViewModel where I receive the event can be seen here:
public void SetCleanerStatus( String status ) {
CleanerStatus = status;
}
And finally, the property that I bind my view to is seen here:
public String CleanerStatus {
get {
return cleanerStatus;
}
set {
if (value != null) {
cleanerStatus = value;
OnPropertyChanged("CleanerStatus");
}
}
}
The intriguing thing is: The View will show one of the above statuses, either "Cleaning started!" or "Cleaning finished!". One would think that they should alternate in an interval of 5 seconds. This is not the case.
If I remove the first waithandle call (pauseEvent.WaitOne(TimeSpan.FromSeconds(5));) the message in the view is "Cleaning finished" and stays that way. If i keep the line, the message is "Cleaning started!" and stays that way. Debugging shows that the line OnPropertyChanged(..) is reached.
What am I doing wrong?
Simplify your RunThread method:
private ManualResetEvent suspendEvent = new ManualResetEvent(false);
private bool shutdown;
private void RunThread()
{
while (!shutdown)
{
suspendEvent.WaitOne();
if (shutdown)
{
break;
}
Notify("Cleaning started!");
Thread.Sleep(TimeSpan.FromSeconds(5));
Notify("Cleaning finished!");
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
When updating the UI it might also be necessary to invoke the PropertyChanged handler in the UI thread:
public string CleanerStatus
{
get { return cleanerStatus; }
set
{
cleanerStatus = value;
App.Current.Dispatcher.BeginInvoke(
new Action(() => OnPropertyChanged("CleanerStatus")));
}
}
I have an application in witch the user can start tasks, heavy tasks. And I want to manage the progression of these tasks in one user interface Grid (each row is a task, with a progression bar) the user can show this grid by clicking a button (using the main thread). The problem that I have is a Cross Thread Operation. I know why: whenever the task progression changed (with thread1), the algorithm try to update the grid datasource (with the main thread). But I don't know how to fix it.
The DataSource property of my grid is set to a BindingList<BackgroundOperation>.
The definition of my task (BackgroundOperation)
public class BackgroundOperation
{
public int progression;
public int Progression
{
get { return progression;}
set
{
progression = value;
OnPropertyChanged("Progression");
}
}
public event EventHandler OnRun;
public event EventHandler<ProgressChangedEventArgs> OnProgressChanged;
public event PropertyChangedEventHandler PropertyChanged;
public void Run()
{
var task = new Task(() =>
{
if (OnRun != null)
OnRun(this, null);
});
task.Start();
}
public void ReportProgress(int progression)
{
Progression = progression;
if (OnProgressChanged != null)
OnProgressChanged(this, new ProgressChangedEventArgs { Progression = progression });
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
You need to run the OnProgressChanged (which BTW should be called just ProgressChanged) on the UI thread. You can do that by saving the current SynchronizationContext when the class is created and then Post()ing the delegate there:
public class BackgroundOperation
{
private readonly SynchronizationContext m_synchronizationContext;
public BackgroundOperation()
{
m_synchronizationContext = SynchronizationContext.Current;
}
…
public void ReportProgress(int progression)
{
Progression = progression;
var handler = OnProgressChanged;
if (handler != null)
m_synchronizationContext.Post(
_ => handler(
this,
new ProgressChangedEventArgs { Progression = progression }),
null);
}
}