Disable button until WPF form loads - wpf

I have a WPF form with Next and Prev buttons for navigating to the next and previous months of a custom made calendar. I want to disable these buttons as soon as the user clicks on them and enable them when the mext/prev month data loads. That would prevent the user from clicking on the buttons repeatedly and the events queuing up and getting fired slowly one by one.
So far, I have tried putting the calendar data load stuff in a Dispatcher thread, and maintaining a flag to indicate whether the page is busy or not. Doesn't seem to work, and the events still pile up.
I also tried using a command for the button clicks, have the execute handler load the data, and CanExecute decide whether data can be loaded, based on a IsBusy flag. Doesn't work either.
Any pointers?

I suppose you have an event fired when the month is loaded.
To me the best solution would be to manually disable the buttons on their click event, and enable them back when the month is loaded.
I see no problem on adding some code behind in the view if needed.

What type of Command are you using?
A RelayCommand will re-run the CanExecute method when one of the properties changes, however a DelegateCommand will not.
You need to manually raise the CanExecuteChanged event on a DelegateCommand when your CanExecute parameters change.
void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsBusy")
{
// Might need a Cast here if your command is of type ICommand
LoadDataCommand.RaiseCanExecuteChanged();
}
}

I fixed this by using a BackgroundWorker to load the view and checking BackgroundWorker.IsBusy in the next/prev event handlers.

Related

Allow a user to override a default action via an event (eg use of e.handled or e.cancel = true or false)

I have a WPF Custom control which I have been building as I happen to need a particular functionality and I want to learn how to do this properly. Most of the control has now come together nicely and is generally pretty usable. There is now one feature that I'd like to implement, however, I'm not sure if it's possible in the first place or if it is how I would or should go about implementing it.
So here goes by way of my explanation of what I have and what I would like. Feel free to ask for extra clarification if required.
My control raises a custom event which passes along some custom event args. The event is declared like so in the control.
Public Event RecordControlButtonClicked(sender As Object, e As VtlDataNavigatorEventArgs)
In most cases when this event is raised the end user has simply clicked the button, nothing other than raising the event will take place because the developer using the control will most likely want to decide how they handle the situation at that particular moment and the custom event args provide more than enough info to do that.
There are however a couple of buttons where in all honesty the developer will probably want to happen what I (as the developer of the control) envisage should happen most of the time but on the odd occasion might not and therefore needs the opportunity to cancel out of it. So what I'm wondering is how I might achieve the following:
The end user clicks a button
The control button raises my custom event
The developer using my custom controls decides they wish to ignore what I (the creator of the control) thinks should happen, so they do something like e.handled = true in the code where they handle my custom event.
Somehow that message gets back to the button that raised the event, it seems that it's been 'handled' or 'canceled' and as a result does nothing
My question is how do I create that sort of functionality and is the event that I currently have declared the correct sort of event to handle this?
Well just in case anyone stumbles across this looking for an answer.
It transpires that my basic mistake lay in the way that I'd Created my custom events class. Originally I'd followed several examples that I'd read about and had my custom events inherit from EventArgs;
Public Class MyCustomEventArgs
Inherits EventArgs
End Class
What I should have done was have it inherit from CancelEventArgs;
Public Class MyCustomEventArgs
Inherits System.ComponentModel.CancelEventArgs
End Class
That provides the Property that I was after. Now when the event is actually handled upstreap the developer can add an e.cancel = true and all I need to do back in the originating call to raise the event is listen out for e.cancel ;
RaiseEvent MyCustomEvent(sender, e)
if e.cancel = true
'presumably don't do something
Else
'do what needs to be done
End If

DataGrid fails to commit edits upon view unload

My main viewmodel encapsulates a set of workspace viewmodels only one of which is exposed at any given time via a CurrentWorkspace property on the main view model. The user gets to switch between workspaces through a series of radio buttons so that when a radio button is clicked, the value of the CurrentWorkspace is replaced with a new workspace viewmodel.
Each workspace has a corresponding view (datatemplate) so that as workspaces are switched, old view unloads and a new view loads in keeping with the value of CurrentWorkspace property. Now each view has a datagrid that could still be in edit mode when the user may choose to click a radio button forcing view unload that dismantles its visual tree including the datagrid. The problem is that the last pending row edit is not committed to the underlying view model when this happens.
I tried to remedy the situation by trying to handle DataGrid LostFocus, LostKeyboardFocus, Unload events as well as the UnloadingRow event but none of them seem to offer a handle to the issue. It seems that once the view unload is triggered, these events either do not fire or fire too late for me to invoke a commit.
I would appreciate any help or pointer to where I should look to resolve this issue.
The problem that I noted above was only observed when the user clicked RadioButton triggering view unload. However, there are other buttons in the main view that also trigger workspace unload (such as Quit button) but those surprisingly did not produce the same bug.
On close scrutiny I found, that radio buttons had by design set their attribute Focusable=False. Undoing it solves my problem and the datagrid now commits edits properly when the view unloads. As I noted above, DataGrid/DataGridRow LostFocus events were not firing as expected and now I believe I found the reason.
So I guess DataGrid/DataGridRow rely on their LostFocus events to trigger commits and its important that this event be not suppressed in any manner. I still wish though if wpf controls had an "Unloading" event as a fall back option to handle problems created by design decisions like the one in my case.

GUI of a WPF application is freezing

The Whole GUI is loaded. After clicked on certain button, the GUI is not responsive: any buttons are no response. If I switch to other app and switch back, the GUI is OK immediately.
Behind the clicked button, it is a handle sending requests to back end. I debugged app, and data has always been returned.
Since the app is huge and I'm not familiar with it, I cannot extract a simple model of sending requests and processing data.I wonder possible causes, since I have no idea now.
Best regards,
-----Add More-----
The back end request sending is in a threadpool thread; when getting data, no UI controls are updated directly. Only presenters (view model) are updated, which are binding to UI control.
I think as sa-ddam213 have suggested and I too believe that you are executing a code block in background Thread or Task.
I think problem is that
- On button click, start execution in background.
- You have kept a flag for checking if background process is running for CanExecute() for the Button's Command.
- UI checks CanExecute() for the Button's Command for a while then does not as mentioned in another question here - Is Josh Smith's implementation of the RelayCommand flawed?.
- Process returns in background.
- UI does not knows the background process is completed and as it has stopped checking the CanExecute() for the Button's Command, UI will not come to know itself.. (you need to tell it)
- Therefore UI is not responding but when user will click somewhere in the application or do an in-out as you said, it will back again.
Solution
- By using InvalidateRequerySuggested method you can invoke the command requery on UI thread so UI will recheck the CanExecute() for each Control and/or Button Command.
// Forcing the CommandManager to raise the RequerySuggested event
CommandManager.InvalidateRequerySuggested();
The InvalidateRequerySuggested method forces the CommandManager to raise the RequerySuggested event. The RequerySuggested event informs a command source to query the command it is associated with to determine whether or not the command can execute.
For more refer:
- How does CommandManager.RequerySuggested work?
- MSDN - CommandManager
- MSDN - CommandManager.InvalidateRequerySuggested Method
Its just a wild guess but try dropping the priority of the logic inside your click event
Example
private void Button_Click(object sender, RoutedEventArgs e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate
{
// all your code here.
});
}

Event to know if Panel lost focus in Winforms application?

I have a simple Form with 4 panels in it. Each of those panels are docked in the parent, to ensure only one is visible at a given time. Now, for Panel2, when it is moving from front to back, I would like to work on that event. I am making panels visible by calling panel.BringToFront()
I have tried Leave event but that doesn't work. For Form, the event is Deactivate, what's the event for Panel?
I'm thinking LostFocus is what you're looking for.
Edit
As another strategy, you know that calling panel.BringToFront will queue an update in your UI. Wherever you are calling panel.BringToFront, perhaps you could just call one of your own methods, or trigger one of your own events. This way, you know when the event will be triggered, and exactly what will trigger it.
The reason I thought of this is that I doubt your Panel will ever actually have the focus itself - rather, one of its child controls will likely have the focus. By doing you own event trigger, you don't have to rely on something as volatile as focus. Plus, even if the Panel did have the focus, it's always possible that it could lose focus in other ways than your own panel switching.
Edit #2
Here's an attempt at a quick implementation of my previous ramblings. I'll be making the assumption that this code be placed somewhere in the same class as all your Panel instances (i.e. in your Form class).
// This will be the custom event to which you can subscribe
// in order to detect a switch in panels.
public event EventHandler PanelSwapEvent;
// This reference the currently visible panel - should be set
// to the default panel in the form's constructor, if possible.
private Panel currentPanel;
// This actually switches the panels, to minimize code duplication.
private void switchToPanel(Panel p)
{
Panel lastPanel = currentPanel;
currentPanel = p;
// Move the panels, and invoke the event.
p.BringToFront();
if(PanelSwapEvent != null)
PanelSwapEvent(lastPanel, new EventArgs());
}
// Here's the actual event handler (replaces your
// pnlServiceInfo_LostFocus handler).
private void PanelSwapHandler(object sender, EventArgs e)
{
// whatever you want to do when panels are swapped
}
In this example, the sender of the event handler is the panel that lost "focus". Using it is as simple as saying switchToPanel(pnl_whatever) to indicate that you would like to switch from the current panel to the panel named pnl_whatever.

Databinding falls behind event notification - discussion

Found an interesting problem that I first found in WinForms, and found again in Silverlight, and more than likely WPF as well when it comes to databinding.
I have a tab control with several tabs. As users click across the tabs, each time should be valid before allowing the user to switch from the tab.
For example, user is in a text box which is updated. Binding of text boxes is not flushed until the control loses focus. Loss of focus occurs when the cursor is moved from the control, and focus is given to another control.
In this scenario, the user tabs into a control (let's use text box for this example), and updates the text box. At this point the databinding has not flushed the control, and hence the VM has not yet seen the change. The user then uses their mouse to click the next tab of the control.
At this point things get interesting. I used the PreviewSelectionChanged (Telerik RadTabControl), as I want to check things out before the jump to the next tab occurs, and it also gives me the ability to cancel the event.
However, when I look at the VM, in this event, it still does not have the updated data. I see the VM is clean, and go ahead and allow the jump to the next tab.
As soon as this event is over however, the databindings flush, and the VM gets updated. what now? The events are out of sync! When the mouse was used to click the next tab, the textbox should have lost focus, flushed it's bindings, before the Preview of the Tab click! It's to late to jump back and say oops we didn't catch that in time!
I think I found an interesting work around to this issue - but I'm not 100% sure it will work 100% of the time. I cancel the current event, but then I use the Dispatcher and create a delegate pointing to another method with the same signature as the current event. The Dispatcher will add this message to the message pump, which by this time will now (hopefully?) be behind the messages of the VM updating...
My two questions are:
1) I'm assuming that the textbox control either didn't flush when the mouse left the control, or the process that was fired was too slow and hence the preview message was on the pump before the databinding - either way I see this to be a major issue.
2) Is the workaround a good solution?
Ok, first to answer question 1:
Just because the mouse left the textbox area, doesn't mean that the textbox lost focus. It only loses focus once something else gets focus. For example, if you moved the mouse out of the textbox and click on some other control on your page (it can be anything from a scroll viewer to another textbox, etc.) then your textbox will lose focus.
Now, based on that, the events do not happen in the wrong order. What happens is; your click event on the other tab triggers both the textbox to lose focus (and the data binding to take place) and the move to the next frame, and based on that, you basically get a race condition where the moving to the next tab happens before the databinding takes place.
About question 2:
What you can do is, set the UpdateSourceTrigger to Explicit, you will however be forced to then have some kind of text_changed event and manually update the binding.
You can read more about that here. It might not be the most complete explanation but is a good place to start.
On the other hand, you can associate some events to the textbox and force the textbox to lose focus on those events (e.g. mouse out).
Just an idea: Why not do everything in the VM's PropertyChanged event?
protected override void OnThisViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == "WhateverProperty") {
//Do your magic here for whatever you want to set
}
}
Have your TabItems bound to a collection that will control is being disabled or not.
<sdk:TabControl>
<sdk:TabItem IsEnabled="{Binding SomeProperty, Converter={AmIDisabledOrWhatConverter}}" />
</sdk:TabControl>
That way, everything is triggered whenever a property is chaned in the vm. No more timing issues since everything is on the vm.
Just my two cents.
There's a design defect here, and you're trying to work around the defect instead of fixing it. You shouldn't have to figure out how to cancel the Click event on the tab. The tab shouldn't be processing Click events in the first place.
Generally speaking, if it's not legal for the user to click on a control, the control shouldn't be enabled. The tab should be disabled until the state of the view model is valid.
Your view model should be exposing a command for navigating to the next tab, and the tab should be bound to the command. The command's CanExecute method should only return true when the state of the view model on the current tab is valid.
This doesn't fix your other problem, which is that Silverlight doesn't support UpdateSourceTrigger="PropertyChanged" out of the box. But that's a solved problem (here is one example).
Note that if you implement commands to handle this wizard-like navigation in your application, you can, down the road, change the view to use something other than a tab control (e.g. to use navigation buttons like an actual wizard, or something like Telerik's PanelBar) without having to screw around with event handlers.
Change your bindings to include UpdateSourceTrigger="PropertyChanged".
This will ensure that your data sources are updated on every key stroke, not just LostFocus.
MyOwnTextBox()
{
this.TextChanged += (s, e) => UpdateText();
}
private void UpdateText()
{
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null && be.ParentBinding.Mode == BindingModes.TwoWay)
{
be.UpdateSource();
}
}
I am using this class it updates my binding on the fly, however there is issue with empty string and null values, if your destination property is nullable string then this will update empty string in your destination property. You can get workaround by using some sort of string converter which will use null instead of empty string in case of nullable strings.

Resources