I'm working in C# with WinForms in a large application with multiple forms.
At several points I have another form coming up as a progress screen. Because I can't freeze my UI thread, I have to start the new form in a new thread. I'm using progressform.ShowDialog() to start the form, but because it's in a new thread, it is possible to Click or Alt + Tab back to the main form. I want to disable this.
My thought is that I can put an EventHandler on the mainForm.GotFocus event and redirect focus to progressForm if it is shown. However, the GotFocus event isn't being triggered when you switch applications or move between the progressForm and mainForm. I'm guessing that it's because some element in mainForm has focus, not the form itself.
If anyone knows of a better way to do this (I'm not committed to the EventHandler approach) or working code for the EventHandler approach, it would solve my problem.
Edit
As per the comment, I tried using the Activated event.
// in InitializeForm()
this.Activated += FocusHandler;
// outside of InitializeForm()
void FocusHandler(object sender, EventArgs e)
{
if (ProgressForm != null)
{
ProgressForm.Focus();
}
}
But it still allowed me to click back to the main form and click buttons.
I've tried some ways and found this which does work as you want, the whole idea is to filter some message from your main UI when your progress form is shown:
public partial class Form1 : Form
{
[DllImport("user32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
public Form1()
{
InitializeComponent();
}
ChildUI child = new ChildUI();
bool progressShown;
IntPtr childHandle;
//I suppose clicking on the button1 on the main ui form will show a progress form.
private void button1_Click(object sender, EventArgs e)
{
if(!progressShown)
new Thread(() => { progressShown = true; childHandle = child.Handle; child.ShowDialog(); progressShown = false; }).Start();
}
protected override void WndProc(ref Message m)
{
if (progressShown&&(m.Msg == 0x84||m.Msg == 0xA1||m.Msg == 0xA4||m.Msg == 0xA3||m.Msg == 0x6))
//0x84: WM_NCHITTEST
//0xA1: WM_NCLBUTTONDOWN
//0xA4: WM_NCRBUTTONDOWN
//0xA3 WM_NCLBUTTONDBLCLK //suppress maximizing ...
//0x6: WM_ACTIVATE //suppress focusing by tab...
{
SetForegroundWindow(childHandle);//Bring your progress form to the front
return;//filter out the messages
}
base.WndProc(ref m);
}
}
if you want to show your progress form normally (not a Dialog), using Application.Run(), showing form normally (using Show()) without processing some while loop will terminate the form almost right after showing it:
private void button1_Click(object sender, EventArgs e)
{
//progressShown = true;
//child.Show();
if (!progressShown)
{
new Thread(() => {
progressShown = true;
if (child == null) child = new ChildUI();
childHandle = child.Handle;
Application.Run(child);
child = null;
progressShown = false;
}).Start();
}
}
I've tested and it works like a charm.
Related
I have an application where the users can add or delete products in a dashboard, because I connect it to azure sql the queries sometimes take time, so I need to block the ui in the main form, I tried disabling the currently open form. but that make the ui look bad.. I also tried form.ShowDialog() which made the main ui struck.. how can I block the main UI till the query is completed?
A sample "Please Wait" form with.
Here there is two form
1. Main Form with a Button
2. Form Designed to show as "Please wait" message. The Form contains two control
a. Label
b. Progress Bar - the property style for the progress bar is made as Marquee
PleaseWait.cs
public partial class PleaseWait : Form
{
public Action Worker { get; set; }
public PleaseWait(Action worker)
{
InitializeComponent();
if(worker == null)
throw new ArgumentNullException();
Worker = worker;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Task.Factory.StartNew(Worker).ContinueWith(a=>
{
this.Close();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
MainForm.cs . In button1_click you will initialize the PleaseWait Form as ShowDialog
private void button1_Click(object sender, EventArgs e)
{
using (PleaseWait pw = new PleaseWait(MakeAzureSQLQuery))
{
pw.ShowDialog();
}
}
private void MakeAzureSQLQuery()
{
//Making the Query Function
//You can also use Background Thread for Querying
for (int nCount = 0; nCount < 500; nCount++)
Thread.Sleep(50);
}
I don't want to click on a button or the form, I just want to know if user is pressing the left mouse button while the cursor is in the form.
I've tried this:
private void PlayForm_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.LButton)
{
...
}
}
but it doesn't work.
I also tried PlayForm_Click() but it works only when the click is on the 'canvas' if there's something else on top it won't work
If you just want to know if the left mouse button is down while executing some other code in the Form you can look at the static property Control.MouseButtons, which returns a value from the MouseButtons enumeration .E.g.:
if ((Control.MouseButtons & MouseButtons.Left) != 0)
you could use the mouse enter/leave to set a boolean that the mouse cursor is over the form, then you could Use the Mouse.
...
bool mouseOverMe;
public MainWindow()
{
InitializeComponent();
mouseOverMe = false;
}
private void Window_MouseEnter(object sender, MouseEventArgs e)
{
mouseOverMe = true;
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
mouseOverMe = false;
}
void doSomething()
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
if (mouseOverMe)
MessageBox.Show("Im a mouse down in the window");
}
...
something sorta like this.
As far as I've understood, you want a handler for a click on whatever is in the form. I'd suggest you could iterate trough all the controls in the form on Form_Load event and just set a common handler for the MouseClick (or KeyPressed or whichever event you want according to your current need) for all controls present in the .Controls collection in the moment the form is loaded and you should register the same handler for the form itself (a.k.a. this.MouseClick). This will be a bit of an overkill if you'd later want to register a MouseClick handler for a particular control, but you can always compare the sender object and get the data from there. Example code is not present for now, since I'm typing from my phone. Will update later.
The main problem is that the form doesn't get any messages when a message is sent directly to a child control.
One way around this is to register an application-wide message filter. Note that the following implementation is rather inefficient (and quite ugly), but it should show you the basic idea:
void Main()
{
var form = new Form();
form.Load += (s, _) => Application.AddMessageFilter(new MyFilter((Form)s));
var pnl = new Panel();
pnl.Controls.Add(new Button());
form.Controls.Add(pnl);
Application.Run(form);
}
public class MyFilter : IMessageFilter
{
Form form;
public MyFilter(Form form)
{
this.form = form;
this.form.Disposed += (_, __) => Application.RemoveMessageFilter(this);
}
public bool PreFilterMessage(ref Message msg)
{
const int WM_LMOUSEDOWN = 0x0201;
if (msg.Msg == WM_LMOUSEDOWN && msg.HWnd != IntPtr.Zero
&& Control.FromHandle(msg.HWnd).TopLevelControl == form)
{
Console.WriteLine("Hi!");
}
return false;
}
}
As my English is not well, I explain my question simple and paste a code snippet here to describe the problem.
The problem is a multiple threading issue in our winForm application. I simple the logic as following code sample.
In the test code, there are 1 mainForm Form1 and a button named "Start" in the mainForm. When user click the button, two forms form2 and form3 will be shown from 2 background threads. After form2 was closed, the Form1 will be triggered to close. But form3 is shown here, so I need user to close form3 by himself. So I handled form.Closing event and use Application.DoEvents() to let user close form3. It looks work in mind. But actually, the form3 can accept user's actions but form3 will not be closed as expected.
Please explain why form3 cannot be closed here and how to modify the code to make user's close operation work.
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CloseFormFromMainThread
{
public partial class Form1 : Form
{
private Form2 _form2;
private Form2 _form3;
private SynchronizationContext _synchronizationContext;
public Form1()
{
InitializeComponent();
Closing += Form1Closing;
}
void Form1Closing(object sender, CancelEventArgs e)
{
while (_form3 != null)
{
Application.DoEvents();
Thread.Sleep(100);
}
}
private void ButtonStartClick(object sender, EventArgs e)
{
var thread = new Thread(StartForm3);
thread.Start();
var thread2 = new Thread(StartForm2);
thread2.Start();
}
private void StartForm3()
{
Thread.Sleep(200);
var action = new Action(() =>
{
_form3 = new Form2();
_form3.Text = "form 3";
_form3.ShowDialog();
_form3 = null;
});
ExecuteActionInUiThread(action);
}
private void Form1Load(object sender, EventArgs e)
{
_synchronizationContext = SynchronizationContext.Current;
}
private void StartForm2()
{
Thread.Sleep(500);
var action = new Action(() =>
{
_form2 = new Form2();
_form2.Text = "form 2";
_form2.ShowDialog();
Close();
});
ExecuteActionInUiThread(action);
}
private void ExecuteActionInUiThread(Action action)
{
var sendOrPostCallback = new SendOrPostCallback(o => action());
_synchronizationContext.Send(sendOrPostCallback, null);
}
}
}
First suggestion: Do not use Application.DoEvents(). Ever. Whenever you think you need it, you have a conceptual problem in your code flow that you should fix first. I guess your code is just creating a deadlock because it waits for the OnClosing callback to return before it can process more events (like the closing of another form).
As I checked the source code of Form.Close(), for modal dialog, there only raised FormClosing event, no Closed event raised. No signal was sent to let form3.ShowDialog to go ahead. So I think only use main thread and Application.DoEvents cannot make code go ahead. Then it is a soft deadlock in the main thread.
Currently, I use a another thread to do check (_form3 != null) and let main thread to execute the _form3.ShowDialog() logic. Following is my code about Form1Closing.
private bool _isFormClosing;
void Form1Closing(object sender, CancelEventArgs e)
{
if (_form3 == null) return;
e.Cancel = true;
if (!_isFormClosing)
{
_isFormClosing = true;
Task.Factory.StartNew((() =>
{
while (_form3 != null)
{
Thread.Sleep(50);
}
ExecuteActionInUiThread(Close);
}));
}
}
I have developed a windows form application in C#.Net. There are multiple forms in the application. Main form say frmA has a push button say btnA. On clicking btnA, new form say frmB is opened and frmA goes behind frmB. frmB also has a push button say btnB and position (location) of btnB on frmB is exactly same as that of btnA on frmA. On clicking btnB, some different actions take place.
Now my problem is some of application users double click on btnA. I get two single clicks back to back. On first single click on btnA, frmB is opened with btnB. Second single click is immediately executed on btnB and in effect users don't even get to see frmB.
My constraint is I cannot change locations of buttons on either forms. Is there anyway I can handle this problem?
Set btnB.Enabled to false and use the following code. This will delay the possibility to click the button for half a second.
public partial class frmB : Form
{
private Timer buttonTimer = new Timer();
public frmB()
{
InitializeComponent();
buttonTimer.Tick += new EventHandler(buttonTimer_Tick);
buttonTimer.Interval = 500;
}
private void frmB_Shown(object sender, EventArgs e)
{
buttonTimer.Start();
}
void buttonTimer_Tick(object sender, EventArgs e)
{
btnB.Enabled = true;
buttonTimer.Stop();
}
}
Just try to fire new form into different position.
As i've understood user is firing frmB on first click
and pressing btnB on "second click"
It's not double click.
It's two different clicks
try something like that:
Form2 form=new Form2();
form.StartPosition = FormStartPosition.Manual;
form.Location=new Point(0, 10);
form.Show();
I would do some trick, What about changing the cursor position. Once user click on btnA cursor point will be shifted a little so the second click will not hit the btnB.
this.Cursor = new Cursor(Cursor.Current.Handle);
Cursor.Position = new Point(Cursor.Position.X - 50, Cursor.Position.Y - 50);
Cursor.Clip = new Rectangle(this.Location, this.Size);
disable it on the first click enable it when the user open again the forma or it come from forma to form b
this.button.enable=false;
immediately after pressing the button.
and write again in the constructor of forma
this.button.enable=true;
I agree that this UI is not friendly but if you absolutely cannot change the position of the buttons or the open position of the second form then you need to stop the second click causing the closure of frmB. Easiest way would be to build in a delay upon opening formB e.g.
public partial class FormB : Form
{
public FormB()
{
InitializeComponent();
Thread.Sleep(2000);
}
}
You can use IMessageFilter to detect WM_LBUTTONDOWN messages and suppress them if they occur within a certain time threshold:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Application.AddMessageFilter(new DoubleClickSuppressser());
}
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.StartPosition = FormStartPosition.Manual;
f2.Location = this.Location;
f2.Show();
}
private void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
Console.WriteLine("listBox1 DoubleClick");
}
}
public class DoubleClickSuppressser : IMessageFilter
{
private int difference;
private DateTime Last_LBUTTONDOWN = DateTime.MinValue;
private const int WM_LBUTTONDOWN = 0x201;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
if (Control.FromHandle(m.HWnd) is Button)
{
if (!Last_LBUTTONDOWN.Equals(DateTime.MinValue))
{
difference = (int)DateTime.Now.Subtract(Last_LBUTTONDOWN).TotalMilliseconds;
Last_LBUTTONDOWN = DateTime.Now;
if (difference < System.Windows.Forms.SystemInformation.DoubleClickTime)
{
return true;
}
}
Last_LBUTTONDOWN = DateTime.Now;
}
break;
}
return false;
}
}
Note that I've specifically disabled Double Clicking only for Buttons with the Control.FromHandle() check. This will allow double clicks to work on other controls such as ListBoxes, etc...
In your own class, inherit the base control and use the .SetStyle() method to disable the double-click. The below code isn't for a Button, but it should work the same:
public class MOBIcon : PictureBox
{
public MOBIcon() : base()
{
this.SetStyle(ControlStyles.StandardDoubleClick, false);
}
}
The problem with enabling and disabling the button is that if the code you have in the click event runs so fast that the button gets enabled before the second click of the double click action (some users don't understand that it only takes 1 click) so here is my solution.
private const int waittime = 2;
private DateTime clickTime = DateTime.Now;
private void cmd_TimeStamp_Click(object sender, EventArgs e)
{
if ((DateTime.Now - clickTime).Seconds < waittime)
return;
else
clickTime = DateTime.Now;
try
{
cmd_TimeStamp.Enabled = false;
//Do some stuff in here
}
catch (Exception ex)
{
//Show error if any
MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
cmd_TimeStamp.Enabled = true;
}
}
I have been trying to write a small app with its own option windows. When I try to launch the window I can never seem to set focus on the new form. This is not a mdi form, but merely a new form that I create when a user selects an option from the menu. It should be noted that Form.Show is return false, which means that the new form is never receiving focus.
I have tried multiple methods for loading the form and all have failed:
From Calling Form:
ServerForm SF = new ServerForm(ref DataLoader, false);
SF.Show();
SF.Focus();
// Fails
Inside the form itself:
this.Show();
this.BringToFront();
this.Activate();
this.TopMost = true;
// Fails
Setting Form to selectable:
this.SetStyle(System.Windows.Forms.ControlStyles.Selectable, true);
...
ServerForm SF = new ServerForm(ref DataLoader, false);
SF.Show();
SF.Focus();
// Fails
Using Old API:
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr handle, int nCmdShow);
ServerForm SF = new ServerForm(ref DataLoader, false);
ShowWindow(SF.Handle, 3);
SF.Show();
SF.Focus();
// Fails
Passing in Parent
ServerForm SF = new ServerForm(ref DataLoader, false);
SF.Show(this);
SF.Focus();
// Fails
In all of these cases the form will show up, but the form that spawned still will have focus over the new form. This happens even when I disable the old form before I create the new form.
Any suggestions?
It's because Form.canFocus() is false when the form loads. Use Form.Activate() on Form.Shown event. That's all.
private void ServerForm_Shown(object sender, EventArgs e)
{
this.Activate();
}
I solved with this (thanks to #Joel Coehoorn):
form.WindowState = FormWindowState.Minimized;
form.Shown += delegate(Object sender, EventArgs e) {
((Form)sender).WindowState = FormWindowState.Normal;
};
form.ShowDialog();
Set TopMost form property to true. Then
in program.cs:
var formLogin = new frmLogin();
formLogin.ShowDialog();
if (formLogin.DialogResult == DialogResult.Yes)
{
Application.Run(new frmMain());
}
in formLogin:
[DllImport("user32")]
public static extern int SetForegroundWindow(IntPtr hwnd);
...
private void frmLogin_Shown(object sender, EventArgs e)
{
SetForegroundWindow(this.Handle);
}
private void frmLogin_Deactivate(object sender, EventArgs e)
{
TopMost = false;
}
Remember that there is only a single user interface thread allowed in a winforms app.
Are you manipulating anything on the parent form after your call to Form.Show()? This
may cause the parent form to be focused again.
Remove everything you have used to try to focus, activate the form and rely just on the call to Form.Show(). This should be enough to load the form, and focus upon it. If anything, in your menu item handler. Comment out everything after your call to Show() and see if that works. Work backwards to see what caused your parent form to be refocused.
This seems to work. First I create the new form:
private void changeDefaultServerToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Enabled = false;
ServerForm SF = new ServerForm(ref DataLoader, true);
}
Then in the constructor for the new form I do the following:
this.BringToFront();
this.CenterToParent();
this.TopMost = true;
this.ShowDialog();
Apparently there is some sort of behind the scene difference between Form.Show and Form.ShowDialog. Not quites sure what it is, I can only think it has to do with setting the active parent somehow. Adding code after the call to construct the function does not seem to give back focus to the parent form. Which it shouldn't.
Have you tried setting the correct parent window in Form.Show()?
E.g.:
using(ServerForm SF = new ServerForm(ref DataLoader, false)) // if ServerForm is IDisposable
{
SF.Show(this);
}
Edit:
There's something going on that isn't in your question. Is your owning window a TopMost window?
Try to call ShowDialog(this). It helped in my case when I faced the same problem.