I'm using the MVVM pattern in my first WPF app and have a problem with something quite basic I assume.
When the user hits the "save" button on my view, a command gets executed that calls the private void Save() in my ViewModel.
The problem is that the code in "Save()" takes some time to execute, so I'd like to hide the "Save" button in the UI view before executing the large chunk of code.
The problem is that the view doesn't update untill all code is executed in the viewmodel.
How can I force the view to redraw and process the PropertyChanged events before executing the Save() code?
Additionally, I would like a reuseable way, so that I can easily do the same thing in other pages as well.. Anyone else made something like this already? A "Loading..." message?
If it takes a long time, consider using a separate thread, for example by using a BackgroundWorker, so that the UI thread can stay responsive (i.e. update the UI) while the operation is performed.
In your Save method, you would
change the UI (i.e. modify some INotifyPropertyChanged or DependencyProperty IsBusySaving boolean which is bound to your UI, hides the Save button and maybe shows some progress bar with IsIndeterminate = True) and
start a BackgroundWorker.
In the DoWork event handler of your BackgroundWorker, you do the lengthy saving operation.
In the RunWorkerCompleted event handler, which is executed in the UI thread, you set IsBusySaving to false and maybe change other stuff in the UI to show that you are finished.
Code example (untested):
BackgroundWorker bwSave;
DependencyProperty IsBusySavingProperty = ...;
private MyViewModel() {
bwSave = new BackgroundWorker();
bwSave.DoWork += (sender, args) => {
// do your lengthy save stuff here -- this happens in a separate thread
}
bwSave.RunWorkerCompleted += (sender, args) => {
IsBusySaving = false;
if (args.Error != null) // if an exception occurred during DoWork,
MessageBox.Show(args.Error.ToString()); // do your error handling here
}
}
private void Save() {
if (IsBusySaving) {
throw new Exception("Save in progress -- this should be prevented by the UI");
}
IsBusySaving = true;
bwSave.RunWorkerAsync();
}
You're using MVVM pattern, so your Save Button's Command is set to an instance of the RoutedCommand object which is added to the Window's CommandBindings collection either declaratively or imperatively.
Assuming that you do it declaratively. Something like
<Window.CommandBindings>
<CommandBinding
Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}"
CanExecute="Save_CanExecute"
Executed="Save"
/>
</Window.CommandBindings>
For the handler of Executed routed event, your Save() method, on entry, you set a variable to false, on return you set it back to true. Something like.
void Save(object sender, ExecutedRoutedEventArgs e)
{
_canExecute = false;
// do work
_canExecute = true;
}
For the handler of the CanExecute routed event, the Save_CanExecute() method, you use the variable as one of the condition.
void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _canExecute && _others;
}
I hope I am clear. :)
You could always do something like this:
public class SaveDemo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _canSave;
public bool CanSave
{
get { return _canSave; }
set
{
if (_canSave != value)
{
_canSave = value;
OnChange("CanSave");
}
}
}
public void Save()
{
_canSave = false;
// Do the lengthy operation
_canSave = true;
}
private void OnChange(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
Then you could bind the IsEnabled property of the button to the CanSave property, and it will automatically be enabled/disabled. An alternative method, and one I would go with would be to use the Command CanExecute to sort this, but the idea is similar enough for you to work with.
You can accomplish this by the following code..
Thread workerThread = null;
void Save(object sender, ExecutedRoutedEventArgs e)
{
workerThread = new Thread(new ThreadStart(doWork));
SaveButton.isEnable = false;
workerThread.start();
}
do all your lengthy process in dowork() method
in some other method...
workerThread.join();
SaveButtton.isEnable = true;
This will cause to run save lengthy process in another thread and will not block your UI, if you want to show an animation while user click on save button then show some progress bar like iPhone etc... give me feedback i'll try to help you even more.
Late answer, but I figured it'd be good to input a bit as well.
Instead of creating your own new thread, it would probably be better to leave it up to the threadpool to run the save. It doesn't force it to run instantly like creating your own thread, but it does allow you to save threading resources.
The way to do that is:
ThreadPool.QueueUserWorkItem(Save);
The problem with using this approach, as well, is that you're required to have your "Save()" method take in an object that will act as a state. I was having a similar problem to yours and decided to go this route because the place that I'm working is very Resource-Needy.
Related
I have a simple WPF button and a textbox in my WPF application(not using MVC or binding at all). I like to be able to do the following upon clicking the button:
1) Clear the textbox
2) Create the result
3) assign the result to the textbox
I used Textbox.Clear, TextBox.Text= String.Empty, delegates and a dispatcher approach like
private void button_Click(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(new Action (ClearReportTxtBox), DispatcherPriority.Send);
System.Threading.Thread.Sleep(5000);
runTest();
}
private void ClearReportTxtBox()
{
Report_textBox.Text = string.Empty;
}
None of them working correctly for me. The dispatcher method is somehow working but not as I wish. It seems that the Clear task will be queued and when the all actions in the button click handler are finished, it will come into play and delete the textbox, but this causes that the generated report and already assigned to the textbox (created by runtest in the code above) will be deleted as well. Hence it is too late delete action and eliminate the whole result.
Currently it seems to me that by clicking on the button the UIthread blocks and takes the control. The Dispatcher will queue the delete action as next action after finishing the button click.
Is it possible to force delete at the beginning and then do the rest? What I like to reach is that I pause the button activity and do delete at first action and then continue with the rest of actions in the button handler.
Am I doing something wrong?
Thank you for your help in advance.
The "Dispatcher.BeginInvoke" is kinda weird for what you want to do
All the UI update has to be done on main thread. Since the "Button_click" event is executing on main thread, the delegate you push into Dispatcher can only be executed AFTER the button_click handle is completed. That's why the execution sequence becomes
1. The GUI freeze because Thread.Sleep
2. RunTest
3. ClearReportTextBox
Guess you can try sth like the following instead.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ClearReportTxtBox();
Task.Factory.StartNew(RunTest);
}
private void ClearReportTxtBox()
{
MyTextBox.Text = string.Empty;
}
private void RunTest()
{
System.Threading.Thread.Sleep(5000);
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.Invoke(priority, ()=> MyTextBox.Text = "123");
}
else
{
MyTextBox.Text = "123";
}
}
I am using MVVM/PRISM/MEF for my WPF application. It has one DataGrid with multiple records, and when one row is double clicked a separate view is added to region with multiple controls on it, the initialization of controls takes about 10 seconds for new screen, so thats why I want to show RadBusyIndicator during that time.
Following in the XAML
<!-- This is Main View -->
<!-- Module: MainModule, ViewModel: MainViewViewModel -->
<telerik:RadBusyIndicator IsBusy="{Binding IsBusy}" BusyContent="{Binding BusyContent}">
<!-- All PRISM regions are here -->
</telerik:RadBusyIndicator>
Its view model is
class MainViewViewModel : ViewModelBase
{
ImportingConstructor]
public MainViewViewModel(IEventAggregator eventAggregator, IRegionManager regionManager, IServiceLocator serviceLocator)
:base(eventAggregator, regionManager, serviceLocator)
{
eventAggregator.GetEvent<BusyStateChangedEvent>().Subscribe(OnBusyStateChanged,ThreadOption.BackgroundThread);
}
#region BusyStateChanged
private void OnBusyStateChanged(bool newState)
{
IsBusy = newState;
}
#endregion
}
And in other view when DataGrid row is double clicked ViewModelBase function is called, as follows
public class ViewModelBase
{
private NavigationItem global_navItem = null;
public virtual void OnNavigationItemChanged(NavigationItem item)
{
changeNav = true;
global_navItem = item;
//Firing event to change the state
EventAggregator.GetEvent<BusyStateChangedEvent>().Publish(true);
//Using BackgroundWorker, but its not showing any Busy Indicator as well
var bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Setting busy indicator to false
EventAggregator.GetEvent<BusyStateChangedEvent>().Publish(false);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
//DisplayView function is taking too long
if (global_navItem != null) this.DisplayView(global_navItem);
}
}
public void DisplayView(NavigationItem item)
{
try
{
//This call is taking long as it initializes the View
MyCustomeUserControl view = this.ServiceLocator.GetInstance<MyCustomeUserControl>(item.viewName);
view.Region = this.Region;
}catch(Exception e)
{
}
}
Events are being fired correctly and view is displayed correctly, but my problem is that Busy indicator is not shown at all, when I double click on DataGrid row the GUI become unresponsive, and after some time the new view appears. I am in doubt that this is problem of GUI thread being busy, but what can I do to avoid this, I have used BackgroudWorker already?
EDIT
1- I am raising PropertyChanged event for IsBusy Property. and I have already tried all options for Thread in event subscription. i.e. Thread.BackgroundThread, Thread.UIThread and Thread.PublisherThread. but no change.
2- I have tested Thread.Sleep rather that DisplayView in bw_DoWork, and its showing RadBusyIndicator properly, so it means that GUI controls are being initialized in GUI thread, no matter I have created a BackgroundWorker for it.
Would the indicator appear if you use Thread.Sleep(5000) instead of this.DisplayView(global_navItem)?
I assume showing the view will use the UI thread and this will block the UI no matter you use a BackgroundWorker or not.
Edit:
As it seems like your UI loading operation blocks the UI thread and so your BusyIndicator, you can try to host one of them in a different thread. An approach is explained in this article.
Finally I have found a solution. For reference following post can be seen. I have implemented a child chrome-less window with RadBusyIndicator using the approach discussed in this post.
Creating multiple UI Threads in WPF
I am writing a WP7 app which restores its state on the main UI thread in its page's OnNavigatedTo() override handler. In this handler, it sets the page's listbox ItemsSource property to the deserialized ObservableCollection of data items. The deserialization is from isolated storage and is quick enough that it doesn't hang the thread.
However, when the page comes up, the listbox is empty. Upon setting a breakpoint and inspecting the state of the page, the Items property is correctly populated and non-empty.
If I delay the settings of the ItemsSource property like so:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int delayMs = 100; // Why 100 ?
runDelayedDispatch(Deployment.Current.Dispatcher,
delayMs, delegate()
{
deserializeFromStorageAndSetItemsSource();
});
}
...
// Does a BeginInvoke() after the specified delay.
public static void runDelayedDispatch(Dispatcher dispatcher,
int delayInMilliseconds, Action action)
{
Action dispatcherAction = delegate()
{
dispatcher.BeginInvoke(action);
};
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) => Thread.Sleep(delayInMilliseconds);
worker.RunWorkerCompleted += (s, e) => dispatcherAction.Invoke();
worker.RunWorkerAsync();
}
Then everything works fine.
What am I doing wrong? Should I be reading from isolated storage on a different handler or later in the application lifecycle?
Articles about app lifecycle have not shed any light on this :(
http://msdn.microsoft.com/en-us/library/system.windows.controls.page.onnavigatedto(v=vs.95).aspx
http://msdn.microsoft.com/en-us/library/cc838245(v=vs.95).aspx
http://windowsphonegeek.com/articles/WP7-Application-Lifecycle-and-Tombstoning
http://visualstudiomagazine.com/articles/2011/06/01/pcmob_app-lifecycle.aspx
Thanks!
It sounds like maybe your data context isn't set correctly, your binding isn't set correctly, or your INotifyPropertyChanged isn't firing.
PS: I think you should reword your question to get rid of your attempt to bypass the problem with a delay - that is giving you answers along lines you don't want and I'm pretty sure it isn't needed at all. Instead, put the ALL the RELEVANT code for the list and page in your question so we can see what you're doing.
All you have to do is to refresh the UI using INotifyPropertyChanged
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int delayMs = 100; // Why 100 ?
runDelayedDispatch(Deployment.Current.Dispatcher,
delayMs, delegate()
{
deserializeFromStorageAndSetItemsSource();
});
NotifyPropertyChange("UI");
}
public static void runDelayedDispatch(Dispatcher dispatcher,
int delayInMilliseconds, Action action)
{
Action dispatcherAction = delegate()
{
dispatcher.BeginInvoke(action);
};
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) => Thread.Sleep(delayInMilliseconds);
worker.RunWorkerCompleted += (s, e) => dispatcherAction.Invoke();
worker.RunWorkerAsync();
NotifyPropertyChanged("UI");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I found it, thanks all! I went back to the basics and tested this scenario from a bare bones list box and it worked in the OnNavigatedTo() without any problem.
The culprit was that I was hooking into receiving notification when the DataContext had changed in order to bind the width of the listbox items to the listbox width, but I could easily accomplish this by hooking into the LayoutUpdated event.
Here was the culprit code that I removed:
http://www.codeproject.com/Articles/38559/Silverlight-DataContext-Changed-Event
Thanks guys and sorry for wasting your time :|
I am working on creating a WPF solution which uses MVVM pattern to load searched items in a search control asynchronously. The search control which is a WPF usercontrol is created with a textbox to enter search text and search button and a hidden listbox which would be visible when it loads the searched items list in it. This user control is in turn embedded into another WPF view which has a treeview of certain items. This view has a view model in which the logic to load the searched items of the tree view would be loaded in the search control. All the while, this has been happening synchronously without the use of any Dispatcher call. But, after a change request, I would like to make this happen asynchronously in a different thread using Dispatcher.
Could anyone please let me know how to get handle of the Dispatcher of the Search control in the view model class so as to call BeginInvoke on it using MVVM pattern wherein my View model is not aware of the view? Any clue would be highly appreciated.
public ObservableCollection<Details> CatalogSearchResults { get; private set; }
private void ExecuteSearchCommand(object parameter)
{
CatalogSearchResults.Clear();
if (string.IsNullOrEmpty(parameter.ToString())) return;
searchtext = (string)parameter;
searchtext.Trim();
SetSearchResults();
}
private void SetSearchResults()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += LoadResults;
bw.RunWorkerCompleted += this.LoadResultsCompleted;
bw.RunWorkerAsync();
}
private void LoadResults(object sender, DoWorkEventArgs args)
{
IsSearchInProgress = true;
foreach (var category in _rootCategory.Recurse(FindChildren))
{
if (category.CommentDetails != null)
{
//limitation - there is no direct way to add range to observable collection.
//Using linq query would result in two loops rather than one.
foreach (var node in category.Details)
{
if (node.Name.IndexOf(searchtext, StringComparison.CurrentCultureIgnoreCase) >= 0
|| node.PrecannedText.IndexOf(searchtext, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate { CatalogSearchResults.Add(node); });
Thread.Sleep(100);
}
}
}
}
IsSearchInProgress = false;
}
In the xaml, I am biding the Items property of the Search control to the CatalogSearchResults:
<ctrl:SearchControl x:Name="Ctrl" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" ToolTip="Search" Command="{Binding SearchCommand}" Grid.ColumnSpan="3"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}"
Items ="{Binding CatalogSearchResults}" > </ctrl:SearchControl>
Thanks,
Sowmya
Here's a simple implementation showing how to use BackgroundWorker to update objects on the UI thread while DoWork is running - in this example, there's a ListBox in the UI that's bound to FilteredItems, and ItemsSource is a property of the UserControl of type IEnumerable:
FilteredItems = new ObservableCollection<object>();
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerAsync();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker) sender;
var result = ItemsSource
.OfType<object>()
.Where(x => x.ToString().Contains(_FilterText));
foreach (object o in result)
{
// Pass each object found to bw_ProgressChanged in the UserState argument.
// This updates the UI as each item is found.
bw.ReportProgress(0, o);
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// FilteredItems is bound to the UI, but it's OK to update it here because
// the ProgressChanged event handler runs on the UI thread.
FilteredItems.Add(e.UserState);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
}
Note that calling ReportProgress every time you find an item is pretty inefficient, as you're marshalling every item found across threads with an Invoke call. Depending on how long the filtering is actually taking, it may be better to accumulate a bunch of results and pass a List<object> to bw_ReportProgress instead of just a single object.
It depends on a lot of factors (and your description is a bit confusing), but I've given a lengthy answer here that may shed some light on the matter. Basically, using the dispatcher alone will not automatically make the code multi-threaded; you'll need some real multi-threading mechanism like BackgroundWorker or the Task Parallel Library. Depending on how you have things set up and on exactly what you do in the other thread, you may indeed need to invoke some actions on the dispatcher thread - however BackgroundWorker does this automatically in most cases so I'd go with that for simple things. The Task Parallel Library also has special handling for the dispatcher, you should find more info on that on MSDN or any TPL tutorial.
The best advice I'd give if you didn't deal heavily with multi-threading until now is to gather as much information as possible on it, because, as it has been said countless times until now, multi-threading is hard! :)
Modify as necessary. 'Items' is just an observableCollection of strings exposed from the VM
private void SetSearchResults()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += LoadResults;
bw.RunWorkerCompleted += this.LoadResultsCompleted;
bw.RunWorkerAsync();
}
private void LoadResultsCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
private void LoadResults(object sender, DoWorkEventArgs args)
{
List<string> results = GetResults();
foreach (string result in results)
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Normal, (ThreadStart)delegate { Items.Add(result); } //Dont worry about access to modified closure in this case
Thread.Sleep(100);
}
}
In XAML
<ListBox ItemsSource={Binding Items}/>
All views in the application have the same dispatcher, you can access it with Application.Current.Dispatcher.
But anyway, you don't need the dispatcher to perform operations on a worker thread. You only need it to perform actions on the UI, because UI elements can only be accessed from the UI thread. But even then, you usually don't need to explicitly manipulate the dispatcher. You can update a property of your ViewModel from the worker thread, controls bound to this property will be updated alright, because the PropertyChanged event is automatically marshalled to the UI dispatcher.
What doesn't work is modifying an bound ObservableCollection<T> from a worker thread: you need to do it from the UI thread using Dispatcher.Invoke. You can also use a specialized ObservableCollection<T> that raises event on the UI thread.
I have figured out how to close Window from my ViewModel.
Now I need to solve window close problem from the other side.
I need to trigger Save() method in my ViewModel when the user clicks window's close button.
I was thinking about binding Command property to Window's close event, but my ViewModel is related to the user control and in that case Close command will not be executed.
I was looking at this question as well Disposing WPF User Controls , but I'm afraid that putting Save method call in Dispose method is a little bit too late.
Is there any way to solve this problem?
Thank You very much!
If you always need to save on closing, why not call the Save() method from the Close() method in the ViewModel instead of from the View? Then just make sure that the window only closes from the ViewModel and not from the View itself. Something like (untested, but you get the idea)
public class SaveOnCloseViewModel
{
public event Action RequestClose;
...
public void Close()
{
Save();
RequestClose();
}
}
public class SaveOnCloseView
{
private SaveOnCloseViewModel _vm;
public SaveOnCloseView(SaveOnCloseViewModel vm)
{
_vm = vm;
_vm.RequestClose += this.PerformClose;
this.OnClosing += Window_Closing;
}
private bool _isClosing = false;
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!_isClosing)
{
e.Cancel = true;
_vm.Close();
}
}
private void PerformClose()
{
_isClosing = true;
this.Close();
}
}
I think the best solution for this would be to register the ViewModel at an event of your control which is also registered at the closed event of you window. This way you can also register other ViewModels at the windowClosed Event in the future to raise other save() Methods in other ViewModels