Using Silverlight 5, RIA services 1.0 and Telerik controls I have a dialog with a button that when clicked goes and runs some service code.
The issue is when I double click or triple click it real fast, it keeps calling the service thus getting this error:
System.InvalidOperationException:
System.InvalidOperationException: A SubmitChanges operation is already in progress on this DomainContext.
I was wondering if this is a common error and any work around for it?
Here is the .NET source code that it goes to that causes it to crash:
public virtual SubmitOperation SubmitChanges(Action<SubmitOperation> callback, object userState)
{
if (this.IsSubmitting)
throw new InvalidOperationException(Resource.DomainContext_SubmitAlreadyInProgress);
This is not an error. A submitChange operation is asynchronous so you have to detect that it is completed before doing something else.
One solution could be to block the user from clicking on the button before the operation is completed.
Since you are using a Telerik controls, you can use a busy indicator.
private void btnUserAction_Click(object sender, RoutedEventArgs e)
{
myBusyIndicator.IsBusy = true;
// DO your stuff
var submitOperation = myContext.SubmitChanges();
submitOperation.Completed += (s, e) =>
{
// It is completed, now the user can click on the button again
myBusyIndicator.IsBusy = false;
}
}
EDIT : The busy indicator should be defined in your Xaml, like this :
<Telerik:RadBusyIndicator x:Name="myBusyIndicator">
<Grid x:Name="LayoutRoot" >
<Button Name="btnUserAction" Click="btnUserAction_Click" />
</Grid>
</Telerik:RadBusyIndicator>
I had this same problem. So I created a property on my ViewModel and bound it to my IsEnabled property of my button. I set this to false when the save starts and to true when it is done.
<Button Content="Save" IsEnabled="{Binding FinishedDataTransfer}" ...
bool _finishedDataTransfer = false;
public bool FinishedDataTransfer
{
get { return _finishedDataTransfer; }
set
{
_finishedDataTransfer = value;
RaisePropertyChanged("FinishedDataTransfer");
}
}
The advantage of doing it this way instead of a busy indicator is that the user can still do thing with the page while the save is running.
We had same problem, and had to change from sending a command to the viewmodel to having a bit of code behind for button click ...
/// <summary>
/// Workaround for handling double-click from the Map Results button - trying to prevent running the query twice and adding duplicate layers into the Layer Control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MapResultsButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 1)
this.ViewModel.MapResultsCommand.Execute(null);
}
As you can see, this uses a ClickCount property which is part of the MouseButtonEventArgs
Once the command has reached the viewmodel we can then worry about busy indicators and other ways of disabling the button.
Related
I have a lot of ToggleSwitch and Combobox controls in my application, I am setting them up on application startup, which fires all the events associated with those controls.
These events are supposed to fire only when the user interacts with the controls and not when the value is changed programmatically.
Is there a way to disable all the events and reactivate them afterwards?
I didn't find any efficient solution on other posts or on the Internet.
As some have said, this smells of bad architecture and that you would even want to do this in the first place, but there is a way you can "block" events using an if statement and a simple boolean.
First, you'll want to declare a field in your class.
private bool _blockHandlers;
Once you've done that, you just set the bool to true where you want to start blocking handler, probably in your class constructor if its straight away. As I don't know the name of your class I won't go there.
But lets say you have an event handler
private void SomeHandler
{
if (blockHandlers)
{
return;
}
// It's not blocked, lets continue...
}
This way, you can temporarly block handlers or permenantly block them, with this method you just simple check if they're blocked, if not you can continue with your handler.
Disabling all events isn't possible because there are many event handlers in the framework that you have no direct control over. The fact you want to do this at all suggests that your design is flawed.
You can detect the difference between user interaction and programmatic change with a bit of plumbing and the diligence to use it everywhere that it matters. Suppose you have a ComboBox, and you want to detect when the user triggers SelectionChanged. This can be done with a flag, set only when you make programmatic changes. i.e.
private bool blockHandlers;
// Wrapped in a method for convenience.
public void SetSelectedIndex(int index)
{
blockHandlers = true;
comboBox.SelectedIndex = index;
blockHandlers = false;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (blockHandlers) return;
// Your event handling code...
}
Using this technique requires that you always either use SetSelectedIndex or set/reset blockHandlers around programmatic changes to ensure event handler(s) observe it and do nothing.
I come up with more elegant solution for this problem.
I created the following helper class:
public class WpfEventExecutor
{
bool isLoaded = false;
bool isProcessingEvent = false;
public void SetLoaded()
{
isLoaded = true;
}
public void Execute(Action action)
{
if (!isLoaded) return;
if (isProcessingEvent) return;
isProcessingEvent = true;
action();
isProcessingEvent = false;
}
}
GeneralView.xaml:
<Page x:Class="MyApp.UI.Settings.Views.GeneralView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:local="clr-namespace:MyApp.UI.Settings.Views"
mc:Ignorable="d"
Title="GeneralItem">
<StackPanel>
<ui:ToggleSwitch Name="StartWithWindowsCheckbox" Toggled="StartWithWindowsCheckbox_OnToggled" Header="Start with Windows" />
<Line Height="20" />
<TextBlock>Language</TextBlock>
<ComboBox Margin="0,10,0,0">
<ComboBoxItem Selected="EnglishBoxItem_OnSelected" Name="EnglishBoxItem">English</ComboBoxItem>
<ComboBoxItem Selected="ChineseBoxItem_OnSelected" Name="ChineseBoxItem">简体中文</ComboBoxItem>
</ComboBox>
</StackPanel>
</Page>
GeneralView.cs:
using System.Windows;
using MyApp.Helpers;
using Page = System.Windows.Controls.Page;
namespace MyApp.UI.Settings.Views
{
/// <summary>
/// Interaction logic for GeneralItem.xaml
/// </summary>
public partial class GeneralView : Page
{
readonly WpfEventExecutor eventExecutor = new WpfEventExecutor();
public GeneralView()
{
InitializeComponent();
StartWithWindowsCheckbox.IsOn = LogicHelpers.IsStartWithWindows();
switch (MyApp.Settings.Values.General.Language)
{
case MyApp.Settings.GeneralType.LanguageType.English:
EnglishBoxItem.IsSelected = true;
break;
case MyApp.Settings.GeneralType.LanguageType.Chinese:
ChineseBoxItem.IsSelected = true;
break;
}
// Call this method only after you finished to change the elements programmatically
// After calling to this method, we assume that each time the elements values changed,
// it caused by the user and not via code
eventExecutor.SetLoaded();
}
void StartWithWindowsCheckbox_OnToggled(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
void EnglishBoxItem_OnSelected(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
void ChineseBoxItem_OnSelected(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
}
}
I'm developing a Windows Forms App, a connector between a callCenter and a CRM and I'm clogged with an unhandled exception which I can't understand or either solve.
My App has:
MainForm with some controls and a FlowControll Pannel which may or may not show a collection of SearchForms - OK
SearchForm - basically a DataGridView with clickable cells - depending on the column index, the click will perform different operations - OK
DialForm - loads as consequence of clicking a cell on my SearchForm and allows to i) Cancel (closes the form OK); ii) dial a number -PROBLEM - very frustrating
When I click to dial, the app correctly makes the phoneCall trough my callCenter, but the very next line of code (Dispose() upon the DialForm) generates an unhandled exception of type 'Safe Handle has been closed', reportedly with DangerousAddRef(Boolean& success).
The relevant methods:
///
/// DialForm Method - upon clicking «Dial Number» Button
///
private void dialButton_Click(object sender, EventArgs e)
{
//
// Piece of code to manage closing with DialFormCloseEventArgs
//
DialFormCloseEventArgs args = new DialFormCloseEventArgs();
args.toClose = this;
EventHandler<DialFormCloseEventArgs> eh = father.Search_CloseDialForm;
this.BeginInvoke(eh, new object[] { sender, args });
}
///
/// SearchForm Method - EventHandler to close DialForm and make call
///
public void Search_CloseDialForm(object sender, DialFormCloseEventArgs e)
{
string numberToDial = e.toClose.numberToDial.Text;
e.toClose.Dispose();
this.rePositionMainForm();
using (ConectorCTI.ConectorCTI ctiws = new ConectorCTI.ConectorCTI())
{
ctiws.Timeout = 180000;
// Synchronous Call
//ctiws.Dial(this.userLoginName, numberToDial, "");
// Assynchronous Call
ctiws.DialAsync(this.userLoginName, numberToDial, "");
}
}
I also show my App with textual descriptions so you can actually «see it»
Probably need to wait for the async task to complete before disposing of the object. Something like
using (ConectorCTI.ConectorCTI ctiws = new ConectorCTI.ConectorCTI())
{
var task = ctiws.DialAsync(this.userLoginName, numberToDial, "");
await task;
}
I have Image control with PreviewMouseLeftButtonDown event handled.
The logic is to change image content when single click occured and to activate other visual style when double click occured.
I know about ClickCount property like some answers said (e.g. this) and successfully distinguish between single/double clicks, but problem is that single click occures always, wherether double click follow or not follow the next moment (which is fair enought, anyway). So, with double click both actions processed - for single click and the next moment for double click.
The question: is there any method to prevent single click before occuring right after that double click, other than handling this situation with some kind of timers magic?
Edit:
I found question with good comment, which makes an analogy with windows explorer - how it wait after single click on selected file, and start renaming just ensured that no other click occured after first click.
Delay will definitely exist in purpose to solve this problem, but does it mean that windows explorer using exactly timer, or maybe it have some other option (some property or event that can be awaited) to hold single click in case double click occured?
Finally there were no suggestions received with timer-unrelated solution (and I didn't find any either), so here is simple example how to prevent single click when double click occurred.
Xaml:
<Window x:Class="StackOverflow.DoubleClickExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="100" Width="150"
MouseDown="RootElement_OnMouseDown">
</Window>
Code-behind:
namespace StackOverflow.DoubleClickExample
{
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
public partial class MainWindow : Window
{
[DllImport("user32.dll")]
public static extern uint GetDoubleClickTime();
public MainWindow()
{
this.InitializeComponent();
}
private Guid lastGuid = Guid.Empty;
private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 1)
{
// Create new unique id and save it into field.
var guid = Guid.NewGuid();
this.lastGuid = guid;
// Run task asynchronously for ensuring that there is no another click
// happened in time interval when double-click can occure.
Task.Run(async () =>
{
// Wait system double-click time interval.
await Task.Delay((int)GetDoubleClickTime());
// If no double-click occured in awaited time interval, then
// last saved id (saved when first click occured) will be unchanged.
if (guid == this.lastGuid)
{
// Here is any logic for single-click handling.
Trace.WriteLine("Single-click occured");
}
});
return;
}
// Can be here only when e.ClickCount > 1, so must change last saved unique id.
// After that, asynchronously running task (for single-click) will detect
// that id was changed and so will NOT run single-click logic.
this.lastGuid = Guid.NewGuid();
// Here is any logic for double-click handling.
Trace.WriteLine("Double-click occured");
}
}
}
For testing, make clicks in window area and track messages writing into output window in visual studio (menu View -> Output).
Another way is using CancellationTokenSource and trigger its Cancel method when double-click occured. Just replace lastGuid field and RootElement_OnMouseDown method:
private CancellationTokenSource cancellationTokenSource;
private void RootElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 1)
{
try
{
this.cancellationTokenSource = new CancellationTokenSource();
var token = this.cancellationTokenSource.Token;
// Run task asynchronously for ensuring that there is no another click
// happened in time interval when double-click can occure.
Task.Run(async () =>
{
// Wait system double-click time interval.
await Task.Delay((int)GetDoubleClickTime(), token);
// Here is any logic for single-click handling.
Trace.WriteLine("Single-click occured");
}, token);
}
catch (OperationCanceledException)
{
// This exception always occure when task is cancelled.
// It happening by design, just ignore it.
}
return;
}
// Cancel single-click task.
if (this.cancellationTokenSource != null)
{
this.cancellationTokenSource.Cancel();
}
// Here is any logic for double-click handling.
Trace.WriteLine("Double-click occured");
}
I guess you need to use a timer. For getting the max time that is valid for a double click still to occur you could use following function (tested; output is 500 ms):
[DllImport("user32.dll")]
static extern uint GetDoubleClickTime();
(source: how to get the double-click-time in WPF)
Usually when you have several values you want to bind to one WPF control you use something like ItemsSource and bind it to a List in view model. But I guess that doesn't work for Image control. So you should go with a timer and use the value of the function above for your timer.
I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.
Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.
Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}
From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>
I want to prevent the user clicking two times on a button when it has been already executing and the process is not finished.
I am using compact framework 3.5 and I have problems when the user clicks two times on a button that is already executing or some other button. I want to disable all buttons when the program is executing and enable them again when the process is done.
OS: Windows mobile 6.1
Framework: .NET 3.5 CF
Try adding this.Enabled = false first thing (this being the form in question) in the scope of your Click handler. Be sure to set it back to true when done. You may need to Application.DoEvents() or Update() to display visible progress if this all in the scope of the handler. Probably the preferred way to do any extended processing though would be to spawn a background thread and update your UI from it using Invoke and BeginInvoke.
I found that I needed to do this quite often when building a windows mobile application so made a simple utility class.
public static class FormUtility
{
/// <summary>
/// Lock the form whilst processing
/// </summary>
/// <param name="controlCollection"></param>
/// <param name="enabled"></param>
public static void FormState(Control.ControlCollection controlCollection, bool enabled)
{
foreach (Control c in controlCollection)
{
c.Enabled = enabled;
c.Invalidate();
c.Refresh();
}
}
}
All I need to do was then call one line to lock the form down.
FormUtility.FormState(this.Controls, false);
You should end up with something like
private void btnSave_Click(object sender, EventArgs e)
{
FormUtility.FormState(this.Controls, false);
//Do your work
if (!SaveSuccessful())
//Renable if your validation failed
FormUtility.FormState(this.Controls, true);
}
EDIT : I think what #tcarvin is suggesting is that you do not need to call refresh on every control but simply invalidate the controls and then refresh the container which will cause all the invalidated controls to redraw at once. I haven't tested this but a small change to something like...
public static void FormState(Form form, bool enabled)
{
foreach (Control c in form.Controls)
{
c.Enabled = enabled;
c.Invalidate();
}
form.Refresh();
}
Then use
FormUtility.FormState(this, true);
This is the easiest way, for a button called button1:
void button1_Clicked(object sender, EventArgs e) {
button1.Enabled = false;
try {
// put your code here
} finally {
button1.Enabled = true;
}
}