Disable a button - c#

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;
}
}

Related

How to suspend execution and wait for a key-press in a win application?

I am implementing a debugger using c#(working on VS2012 -- .Net 4.5), it should work as below: (This is a vbscript debugger which is using msscript.ocx control)
On the line with breakpoint it should wait for the {F5} key and upon having the {F5} key it should move to next code line.
Now the problem is that in the debug method(This method is called upon hitting breakpoint) keeps on moving in the loop checking for the static variable set as true ( The key-press event on the control sets this static variable as true).
The application goes un-responsive and i have to stop it.
Here goes the code for it:
Following code is implemented at the KeyPress event of a TextBox:
Whenever it receive a {F5} key it sets true in a static variable.
static bool dVar;
private void fctb_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.F5)
{
//Setting the static variable true when the control recieves a {F5} key
dVar = true;
}
}
Now Upon hitting the breakpoint following function is called
public void DebugIT()
{
dVar=false
//Waits for the {F5} key press by checking for the static variable
//The Application goes Un-Responsive on this Loop and stops accepting keys
while (dVar!=true)
{
System.Threading.Thread.Sleep(1000);
}
}
}
The issue here is that when it enter the while loop it stops accepting the key-presses and goes unresponsive.
Need a way that suspends the code execution till the time it receives a desired key-press.
Or
Can we have a separate thread which checks for the {F5} key press and does not make the application un-Responsive.
Can anyone please help?
Here is an example of how you can do this.
If you want this exact code to work create a new form and drag-drop two buttons and a textbox on it.
public partial class Form1 : Form
{
ManualResetEvent man = new ManualResetEvent(false);
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
button2.Click += button2_Click;
}
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Enabled = false;//Do some work before waiting
await WaitForF5(); //wait for a button click or a key press event or what ever you want
textBox1.Enabled = true; //Continue
}
private Task WaitForF5()
{
return Task.Factory.StartNew(() =>
{
man.WaitOne();
man.Reset();
}
);
}
private void button2_Click(object sender, EventArgs e)
{
man.Set();
}
}
In the above example when you click button1 the textbox is disabled and when you press the second button is gets enabled again.
And this is done without blocking the UI
You need to add DoEvents into your while loop. See the example from MSDN

Better way to programmatically Lock/Disable multiple UI controls on Ribbon bar

I have been wondering, what would be the better way to implement Locking/Disabling a bunch of Ribbon UI controls by just a click?
Currently, my approach is kinda simple and basic:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
if (tbtnLock.Checked)
{
control1.Enabled = false;
control2.Enabled = false;
control3.Enabled = false;
//...
//controlN.Enabled = false;
}
else
{
control1.Enabled = true;
control2.Enabled = true;
control3.Enabled = true;
//...
//controlN.Enabled = true;
}
}
I think it's okay if we only have just a few controls but once we add more and more controls onto the Ribbon bar, I don't think it'd be a good coding practice to do things like above.
Is there any cleaner and neater approach to this? Am I able to get the collection of all the controls on the Ribbon bar? Hopefully someone can give me some pointers here? Thanks very much.
EDIT:
Revised code below:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
toggleUILockState();
}
private void toggleUILockState()
{
if (group1.Items != null)
{
foreach (RibbonControl c in group1.Items)
{
if (c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
}
}
I think it looks a lot better than the previous version. Thanks everyone for the help.
Well certainly the first step to improve the code would be to remove the if statement and assigned the enabled state of the control directly with the checked state of the tbtnLock control like...
control1.Enabled = !tbtnLock.Checked;
control2.Enabled = !tbtnLock.Checked;
that would cut your code if half straight away. You may want to assign that to a bool first incase you want to do additional processing on it later (maybe some other object helps determining lock state for example)
bool isEnabled = !tbtnLock.Checked;
control1.Enabled = isEnabled;
control2.Enabled = isEnabled;
Further than that I would need to know what "ribbon" control you are using. Do you have a link?
But as you have hinted, I would want to look at trying to find a colleciton of controls, loop through them, check if the control is not the tbtnLock control and disable/enable as needed.
Also, I would recommend move all this code to a function outside of the event handle, in case you need to call this method from other code. Something like...
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
UpdateRibbonState();
}
private void UpdateRibbonState(){
//Code goes here
}
EDIT: Making assumtion that a "group" (as described in comments) has a collection of controls...
foreach(Control c in group.Controls)
{
if(c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
I am not familiar with any built-in .Net ribbon controls and as there is no link to a 3rd party set) I am making a best guess at the properties available for a "group"
Seems like you could put all your UI controls into one list and then iterate it to enable/disable them all at once. Something like (untested/pseudocode):
List<RibbonBase> listMyControls = new List<RibbonBase>()
{
control1, control2, control3, ... , controlN
};
foreach (var control in listMyControls)
{
control.Enabled = !tbtnLock.Checked;
}
Here is some code to extend this to an entire tab.
/// <summary>
/// Enable or Disable all buttons in all groups of the RibbonTab to match toggleButtonActive
/// toggleButtonActive remains enabled
/// </summary>
/// <param name="enabled"></param>
private void SetUILockState(bool enabled)
{
foreach (RibbonGroup group in myRibbonTab.Groups)
{
if (group.Items != null)
{
foreach (RibbonControl c in group.Items)
{
if (c.Name != "toggleButtonActive")
{
c.Enabled = enabled;
}
}
}
}
// TODO handle right click menus as well
}

WindowsFormsApplicationBase SplashScreen makes login form ignore keypresses until I click on it - how to debug?

My WinForms app has a simple modal login form, invoked at startup via ShowDialog(). When I run from inside Visual Studio, everything works fine. I can just type in my User ID, hit the Enter key, and get logged in.
But when I run a release build directly, everything looks normal (the login form is active, there's a blinking cursor in the User ID MaskedEditBox), but all keypresses are ignored until I click somewhere on the login form. Very annoying if you are used to doing everything from the keyboard.
I've tried to trace through the event handlers, and to set the focus directly with code, to no avail.
Any suggestions how to debug this (outside of Visual Studio), or failing that - a possible workaround?
Edit
Here's the calling code, in my Main Form:
private void OfeMainForm_Shown(object sender, EventArgs e)
{
OperatorLogon();
}
private void OperatorLogon()
{
// Modal dialogs should be in a "using" block for proper disposal
using (var logonForm = new C21CfrLogOnForm())
{
var dr = logonForm.ShowDialog(this);
if (dr == DialogResult.OK)
SaveOperatorId(logonForm.OperatorId);
else
Application.Exit();
}
}
Edit 2
Didn't think this was relevant, but I'm using Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase for it's splash screen and SingleInstanceController support.
I just commented out the splash screen code, and the problem has disappeared. So that's opened up a whole new line of inquiry...
Edit 3
Changed title to reflect better understanding of the problem
UI focus/redraw/etc. issues usually are rather straightforward to debug by using remote-debugging. I.e. use a second PC (virtual is just enough) where your application runs.
See this MSDN article for details.
Run this in your form code behind. It will tell you which control has focus by giving you the type and name of the control. Run it in form_shown because its the last event in the form load process.
private void Form1_Shown(object sender, EventArgs e)
{
Control control = FindFocusedControl(this);
MessageBox.Show("The focused control " + control.Name + " is of type " + control.GetType());
}
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
If the answer isn't obvious after that, tell us what you get.
I've found a hack...er...I mean...workaround, that fixes the problem. The solution was buried in one of the comments of this answer (thanks, P. Brian Mackey, for providing the link to the related question!)
The workaround is to minimize the main window while the splash screen is displayed, then set it's WindowState back to Normal before showing the login form.
In the code below, see the lines commented with "HACK".
public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
this.IsSingleInstance = true;
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that
/// initializes the splash screen.
/// </summary>
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new SplashScreen();
}
/// <summary>
/// When overridden in a derived class, allows a designer to emit code that configures
/// the splash screen and main form.
/// </summary>
protected override void OnCreateMainForm()
{
// SplashScreen will close after MainForm_Load completed
this.MainForm = new OfeMainForm();
// HACK - gets around problem with logon form not having focus on startup
// See also OfeMainForm_Shown in OfeMainForm.cs
this.MainForm.WindowState = FormWindowState.Minimized;
}
}
public partial class OfeMainForm : Form
{
// ...
private void OfeMainForm_Shown(object sender, EventArgs e)
{
// HACK - gets around problem with logon form not having focus on startup
// See also OnCreateMainForm in Program.cs
this.WindowState = FormWindowState.Normal;
OperatorLogon();
}
// ...
}
This is working for now, but I'm wondering if I should explicitly open the Logon form from the SingleInstanceController, rather than from my main form.

How to create a control and manage its events in one method?

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....
You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}
You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Ignoring queued mouse events

I have an application written in C# targeting .NET Compact Framework 3.5, running on Windows CE. From time to time, operations lasting for a second or so are being performed on the UI thread. I currently set the Cursor.Current property to indicate that the application is busy, but this does not prevent mouse events from eager users to queue up. This sometimes leads to unintended clicks.
What is the best way to ignore queued mouse messages on the .NET Compact Framework platform? Sadly, the code has to run on the UI thread.
Disabling the controls won't help you, as I've found from my POS application that the users can sneak in another click in about 50ms, especially when using a touch screen that is not calibrated.
One of the problems this creates is when producing an invoice, you can't have a duplicate click produce another invoice, just because there's a 50ms delay before clearing the current invoice.
In cases like this, I use a pattern similar to this:
public static void ClearMouseClickQueue()
{
Message message;
while (PeekMessage(out message,IntPtr.Zero, (uint) MessageCodes.WM_MOUSEFIRST,(uint) MessageCodes.WM_MOUSELAST,1) != 0)
{
}
}
private object approvalLockObject = new object();
private void btnApproveTransaction_Click(object sender, EventArgs e)
{
ApproveTransactionAndLockForm();
}
private void ApproveTransactionAndLockForm()
{
lock (approvalLockObject)
{
if (ApprovalLockCount == 0)
{
ApprovalLockCount++;
ApproveTransaction();
}
else
{
CloseAndRetry();
}
}
}
private void ApproveTransaction()
{
ClearMouseClickQueue();
this.Enabled = false;
Logger.LogInfo("Before approve transaction");
MouseHelper.SetCursorToWaitCursor();
... validate invoice and print
}
In case you need to reenable the screen, do this:
this.Enabled = true;
ApprovalLockCount = 0;
DialogResult = DialogResult.None;
I believe that the best solution is to prevent the events from happening. You can do that by disabling all the controls and re-enabling them, when the lengthy operation finishes.

Categories