I'm working on an application on Windows Phone, and I am using silverlight.
I have some bugs where the user can quickly press a button twice, which will effectively do 2 WCF calls since the action is called 2 times.
The obvious solution is simply to disable the button until the call completes but I'm wondering if there's a more global solution where I wouldn't have to implement this for every action. My application uses about 50 WCF methods so it would be tedious to implement this for every single action/every screens.
There's also the situation where users can click the phone back button while a call is running and start clicking on other buttons etc...
Anyone know a clean solution for this?
Simple solution: use a boolean variable that will be set to true after first click and to false when result from server has arrived back. In click handler just check the value of this variable, if it is true do not call the service again.
Are you using a design pattern like MVVM in your application? If not, you probably should.
In this case you could create a bool property called IsIdle or something like it. Then just set <Button IsEnabled="{Binding IsIdle}".
Now whenever you start doing an asynchronous call, set IsIdle to false. This will disable the buttons. When loading is finished set it back to true, so all the buttons are enabled again.
This might not be very elegant, but I think it would prevent any input without creeping all over your views and code.
You could prevent input by temporarily overlaying a transparent rectangle over your screen with IsHitTestable = true. You could write a small utility method to push that rectangle on any of your screens and remove it when you want to restore input.
You could also use that screen to eventually show something like a busy indicator when the network is slow.
Related
From a production application, we notice that our WPF buttons fire the ICommand.Execute method twice on fast double click.
Now, on every Command, the application is covered with a full-screen spinner animation, preventing any further interaction with the application.
This github repo contains a minimalistic repro of the issue. Note that:
when the Button's Command fires, the "IsBusy" flag is set to true
as a consequence, the BusyIndicator overlay will be shown
as a consequence, the Button cannot be pressed again until after 300ms
However, especially on slow computers, when fast double-clicking (really fast, like gaming fast that is), it is possible to fire the command twice without the BusyIndicator blocking the second call (this can be seen if the output shows 2 'click' lines right after one another).
This is unexpected behavior to me, as the IsBusy flag is set to true right away on the UI thread.
How come a second click is able to pass through?
I would expect the IsBusy Binding to show the overlay on the UI thread, blocking any further interaction?
The github sample also contains 2 workarounds:
using the ICommand.CanExecute to block the Execute handler
using the PreviewMouseDown to prevent double clicks
I'm trying to understand what the issue is.
What work-around would you prefer?
Diagnosis
This is only my guess and not a solid and confirmed info, but it seems that when you click the mouse button, the hit-testing is done immediately, but all the mouse related events are only scheduled to be raised (using the Dispatcher I presume). The important thing is that the control that is clicked is determined at the time the click occurred, and not after the previous click has been completely handled (including all UI changes that potentially follow).
So in your case, even if the first click results in showing the BusyIndicator covering (and thus blocking) the Button, if you manage to click for the second time before the BusyIndicator is actually shown (and that does not happen immediately), the click event on the Button will be scheduled to be raised (which will happen after the BusyIndicator is shown), causing the command to be executed again even though at that point the BusyIndicator will possibly be blocking the Button.
Solution
If your goal is to prevent command execution while the previous one is still executing the obvious choice is to make the Command.CanExecute result depend on the state of the IsBusy flag. Moreover, I wouldn't even call it a workaround, but a proper design.
What you're facing here is a clear-cut example of why you shouldn't make your business logic rely on UI. Firstly, because rendering strongly depends on the machine's processing power, and secondly because covering a button with another control by far does not guarantee the button cannot be "clicked" (using for example UI Automation framework).
I have a page that programmatically shows a popup in a backing bean method. The popup asks the user a Yes/No question, and the subsequent logical path followed is determined by their response. However, whether the popup is shown or not in the first place is conditional. Also, there is additional logic in the original method, outside of the popup logic, that must be completed.
The issue with this is that ADF seems to spin off the popup into another thread, and keeps executing the logic in the original method simultaneously, while waiting for the user's response. However, the desired effect is that the program halts until the user has answered the question in the popup.
I haven't been able to figure out an elegant way to make this to happen. Ideally, I think the solution is to encapsulate the logic that occurs after the popup is shown (or not shown) in the original method, and call it from the popup's action listener if the popup is shown (otherwise call it from the original method). However, the logic to be encapsulated requires the use of some local variables that were set before the popup was shown. There's no way to get these values to the popup's action listener method in order to pass them to the encapsulated logic (aside from creating global static variables in the bean, which seems like a poor solution).
Another idea I had was to bump up the "show/don't show popup" logic to the task flow. However, it seems that doing this for every single popup would make the task flow really complicated.
Is there a better way to do this? It must be a common issue, and it seems that I'm going about it all wrong.
ETA: I have tried setting the popup's ContentDelivery property to "immediate", and the af:dialog component within the popup so Modal is "true". Neither has produced the desired behavior.
This can't be achieved with a server-side web framework.
For ADF, you will have different events for each lifecycle:
1 - popup opening :: popupFetchListener event
2 - click on OK, Cancel buttons ::DialogListener event
3 - hit Esc button :: popupCancelledEvent
You can share data between these events either on pageFlowScope, or viewScope.
But if you use ADF BC, you may be better off using transient attributes on View Objects.
I've seen I have a problem with several users that use to double-click in buttons.
I have several buttons bound to commands that launch many actions.
For example there are two windows that communicate between them through a mediator so when I click "close the other window", the bound command sends a "CloseTheOtherWindowMessage". The problem is that when a user makes double click it tries to close the window a second time and, as expected, it crashes.
I've tried to set the window BusyIndicator as IsBusy when I press the button but my finger is quicker than MVVM and it still let me double-click before it starts showing the BusyIndicator.
I've found many examples of how to only admit double click in MVVM using interaction.Behaviors but I want just the opposite. Is there any example or other good and general solution for this problem?
Why is it "as expected" when it crashes? A crash should never be "as expected".
Your finger shouldn't be "quicker than MVVM". The Dispatcher thread always acts deterministically and sequentially. Do you use a multi-threaded approach?
In the command's Execute method or handler, raise its CanExecuteChanged event, and the binding engine will immediately call CanExecute(...). Make it so that this method will return false the second time. Maybe use a timer, or, better yet, you can logically determine by your view model state alone that the action is not possible right now (i.e. because IsOtherStuffAvailable is currently false).
I am making an asynchronous call to a web service. Since it might take a few seconds there is a status Label used to let the user know what's going on. But even though the call is async the first call seems to block for a few seconds and the status label takes too long to get updated. In WinForms I could force the label to refresh (using Update() I think) but not in WPF. Any super easy ways to get this working?
Thanks,
Gerry
You could move the entire call logic into a QueueWorkUserItem or BackgroundWorker block. That way the first proxy initialization would not block the UIThread (before the async. Begin/End pattern kicks in). Assuming that you are using databinding the object exposing the property bound to the Label implemented INotifyPropertyChanged everything should happen automagically.
I'd (wildly) guess that the blocking is due to the creation/initialization of the service proxy classes. If so, you could try to create the proxy earlier, or call your asynchronous web service in another thread.
The general answer to your question about refreshing controls... I have always relied on data binding to do this. That won't help though if the main UI thread is stuck doing something. And if the UI thread is stuck, I don't know that there's any way to get it to draw.
There isn't a way to tell the label to refresh that will actually work in your case. If the UI is being blocked, it won't refresh. Basically, when you actually get to the point where you update the label's text, it will show in WPF. The only possible exception that I can think to that would be if you are using a non-WPF control but even then it should work.
My suggestion would be to update the label before you perform the first action (even before variables are initialized, since this might be where the issue actually is). Here is a pseudocode example of what I mean (just in case I wasn't clear):
private void KickOffProcess()
{
label1.Text = "Processing ..."; //This is where you need to move the label update code
AsyncCall();
}
I'm improving standart WPF TabControl. I want to add undocking functionality to it:
user drags the page just outside the TabControl and this page undocks in the window.
I want two events in this control - PageDragStart (raises when the page dragged outside) and PageDragEnd (raises when the page dropped outside)
I've got no problem with the first event.
But the second... OnDrop doesn't call, because the item dropped outside the tabcontol container. How can I know that it was dropped?
P.S. I want a universal control (so, undocking functionality shouldn't be connected and hardcoded with the window tabcontrol is placed or something like this)
Why use DoDragDrop at all? As I was reading your description, using Mouse.Capture by itself seemed the obvious solution:
Handle OnMouseLeftButtonDown on the tab and start capture
Handle OnMouseMove on the tab and update the cursor based on hit testing
Handle OnMouseLeftButtonUp on the tab, and stop the capture and make the appropriate change
The reasons you might ever consider DoDragDrop over simple mouse capture are:
Integration with Windows' OLE drag and drop so you can drag and drop between applications and technologies
Modal nature of DoDragDrop call (which actually seems to be more of a disadvantage to me)
Automated hit testing of targets
Standardized "drop operation" API to allow unrelated applications to handle copy vs move, etc.
You apparently don't need the OLE integration or multi-application support and you want to customize the hit testing, so it seems that DoDragDrop has no advantages over directly handling the mouse capture.
I solved the problem - in rather brutal and unsafe way. But for it's gonna work as the temporary solution.
Well, when I'm raising PageDragStart event, I call Mouse.Capture(this, CaptureMode.SubTree);
When the page is dropped somewhere - DoDragDrop throws different exceptions (COMException, NullReference (I couldn't find which object is null) and some others I don't remember).
I catch exception and call PageDragEnd event (if the property IsPageDraggingOut set to true).
As far as you can see this solution is really dirty and bad. But it works.
So, any other ideas (or some ideas how to work with Mouse.Capture properly)?