In my windows form application I call, from the main MDIContainer form, a class in which I invoke the open of the child form.
In Main Form :
private void btnTemplate_ItemClick(object sender, ItemClickEventArgs e)
{
beiProgressBar.EditValue = "Form opening in progress...";
repositoryItemMarqueeProgressBar1.ShowTitle = true;
beiProgressBar.Visibility = BarItemVisibility.Always;
bwTemplate.RunWorkerAsync();
}
private void bwTemplate_DoWork(object sender, DoWorkEventArgs e)
{
FrmTemplate frm;
frm = new FrmTemplate();
Callback.SetFormTemplate(this, ref frm);
}
In the Callback class I show the child form :
public delegate void SetFormTemplateCallback(FrmMain pFormMain, ref FrmTemplate pForm);
public static void SetFormTemplate(FrmMain pFormMain, ref FrmTemplate pForm)
{
if (pFormMain.InvokeRequired)
{
SetFormTemplateCallback d = new SetFormTemplateCallback(SetFormTemplate);
pFormMain.Invoke(d, new object[] { pFormMain, pForm });
}
else
{
pForm.MdiParent = pFormMain;
pForm.InitForm();
pForm.Show();
}
}
This operation randomly hangs my application.
I also tried with BeginInvoke but the problem is still present.
I noticed that often the application freeze when it is minimized.
It is difficult also debug this error.
Anyone has ever reached a similar behavior ?
Related
I am developing a Client/server app.
In the Client app, I have a Main Form that is an MDI parent.
The Main Form has a Load event that creates a new instance of a Child Form and makes it visible.
This event also establishes Main as the MdiParent of Child.
The Child form is meant to be a sign-in screen.
From the child form, I create a reference to the parent, to be able to call methods from the parent.
However, upon executing the MdiParent.RequestConnection method, the GUI becomes stuck.
So I tried to execute the method from a Thread, but it is not accepting my declaration, even if I'm seemingly following the correct syntax.
I don't see what am I doing wrong. Please help
Main form
public partial class frmMainForm: Form
{
public frmMainForm()
{
InitializeComponent();
}
Thread runningClient;
public MyTcpClient client= new MyTcpClient ();
frmChildForm frmSignIn;
bool clientConnected;
private void frmMainForm_Load(object sender, EventArgs e)
{
clientConnected= false;
panelSidebar.Hide();
if(frmSignIn == null)
{
frmSignIn= new frmChildForm();
frmSignIn.MdiParent = this;
frmSignIn.Show();
}
}
public void TurnOnPanels()
{
panelSidebar.Visible = true;
panelSidebar.BringToFront();
}
public void RequestConnection(string username)
{
string serverRsp = client.Connect(username);
if(serverRsp.Equals("SUCCESS"))
{
MessageBox.Show("Signed In", "Welcome", MessageBoxButtons.OK, MessageBoxIcon.Information);
clientConnected = true;
frmSignIn.Close();
}
}
}
And my child form
public partial class frmChildForm : Form
{
frmMainForm frmParent;
Thread clientRunning;
public frmChildForm()
{
InitializeComponent();
frmParent= (frmMainForm)this.MdiParent;
}
private void frmSignIn_FormClosing(object sender, FormClosingEventArgs e)
{
frmParent= (frmMainForm)this.MdiParent;
frmParent.TurnOnPanels();
}
private void btnSignIn_Click(object sender, EventArgs e)
{
if (txtSignInUsername.Text.Equals(""))
{
MessageBox.Show("No empty fields.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
//This is where it fails
clientRunning= new Thread(new ParameterizedThreadStart(frmParent.RequestConnection);
//"No Overload for RequestConnection matches delegate ParameterizedThreadStart"
//If I try to include the parameter inside that call, I get a "waiting for method //name" syntax error message instead.
clientRunning.Start(txtSignInUsername.Text.ToUpper());
}
}
private void frmSignIn_Load(object sender, EventArgs e)
{
frmParent = (frmMainForm)this.MdiParent;
}
}
I also tried to do it from the main form by creating a thread inside RequestConnection, where it was supposed to execute client.Connect, but I got the same error.
Couple of things you need to fix
public void RequestConnection(object username) // changed parameter type
{
if (username == null)
throw new ArgumentNullException(nameof(username));
if (!(username is string))
throw new InvalidCastException("Expect string");// give proper message
string serverRsp = client.Connect(username.ToString());
if (serverRsp.Equals("SUCCESS"))
{
MessageBox.Show("Signed In", "Welcome", MessageBoxButtons.OK, MessageBoxIcon.Information);
clientConnected = true;
//this is require to solve cross-thread operation
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(delegate ()
{
frmSignIn.Close();
}));
else
frmSignIn.Close();
}
}
you need to get MdiParent in Form Load Event and remove from Constructor.
Your child form load event/ or use Parent changed Event
private void FrmLogin_Load(object sender, EventArgs e)
{
frmParent = (MainForm)this.MdiParent;
}
The ParametrizedThreadStart delegate actually takes an object as parameter.
So you must provide it a method that takes an object as parameter, not as string. In your method you will receive the object, check that it is a string then convert it to a string.
I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.
I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:
Here's the 'main' program:
class Program
{
static MyForm form;
static void Main(string[] args)
{
form = new MyForm();
form.Show();
doWork();
form.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
while (form.Value < 100000)
{
form.ChangeVal();
Thread.Sleep(1);
}
return;
}
}
...And here's the Form. I'm not including the auto-generated stuff from VS.
public partial class MyForm : Form
{
private int val;
public int Value
{
get { return val; }
set { val = value; }
}
public Thread GUIupdater;
public MyForm()
{
InitializeComponent();
this.Refresh();
}
private void MyForm_Load(object sender, EventArgs e)
{
GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
GUIupdater.Start();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
}
public void ChangeVal()
{
val++;
}
private void changeLabel(string s)
{
label.Text = s;
label.Refresh();
}
private delegate void labelChanger(string s);
private void GUIupdaterThread()
{
while (true)
{
Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
Thread.Sleep(100); //100 ms
}
}
private void killThreadOnClose(object sender, FormClosingEventArgs e)
{
GUIupdater.Abort();
}
}
So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!
Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here
Edits:
When I add Console.WriteLine Statements, I discovered that the GUIupdaterThread (the thing that's meant to update the GUI) loop always 'breaks' on the Invoke statement, never reaching 'Thread.Sleep'. I changed it to 'BeginInvoke', which causes the loop to function properly, but this hasn't changed the fact that the GUI doesn't update.
CLARIFICATIONS:
About my 'actual' project:
The main thread here in 'Program' simulates my software, which is a plugin implementing an interface. My decision to alter val / value in that thread, not in the thread created by the window, was deliberate.
I'm constrained to using .NET 4.0 . any more recent features can't help me
Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.
There are several approaches how to update controls from other threads.
I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).
I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.
Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:
This form has a single label on it.
public partial class ProgressForm : Form
{
private int currentValue = 0;
public ProgressForm()
{
InitializeComponent();
}
public void ChangeValue(int newValue)
{
currentValue = newValue;
lblValue.Text = string.Format("Current value: {0}", currentValue);
lblValue.Refresh(); //Call Refresh to make the label update itself
}
}
static class Program
{
private static ProgressForm progressForm = null;
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
progressForm = new ProgressForm();
progressForm.Show();
doWork();
progressForm.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
int i = 0;
while (i < 100000)
{
progressForm.ChangeValue(i);
Thread.Sleep(1);
i++;
}
return;
}
}
You may use the following instead as you are trying to access UI control other than main thread (from which it is created).
while ( true )
{
Invoke ( ( Action ) (() =>
{
label.Text = val.ToString();
label.Refresh()
Application.DoEvents();
}));
Thread.Sleep( 100 );
}
I recommend you to use "backgroundworker".
First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.
private void btnDoIt_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Foo();
}
int total = 0;
private void Foo()
{
for (int i = 0; i <= 100000; i++)
{
total += i;
this.Text = i.ToString();
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run next process
}
I have an application in C# which creates a form and stack it in front of another app's window.
I do this by using SetParent. However, the (new) parent window freezes.
How can I solve that? Is this a matter of threading?
This is working:
private void Test(object sender, EventArgs e)
{
FormCover cov = new FormCover();
IntPtr hwnd = Win32Utils.FindWindowByCaptionStart(IntPtr.Zero, TrackerName, null);
Win32Utils.SetParent(cov.Handle, hwnd);
cov.SetDesktopLocation(0, 0);
cov.Show();
}
But this (with a timer elapsed event) is not:
public partial class Form1 : Form
{
FormCover cover;
void tmrCheck_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ShowCover();
}
private void ShowCover()
{
cover = new FormCover();
IntPtr hwnd = Win32Utils.FindWindowByCaptionStart(IntPtr.Zero, TrackerName, null);
cover.CoverInitialize(hwnd);
cover.Activate();
}
}
//------
public partial class FormCover : Form
{
public delegate void IntPtrDlg(IntPtr param);
public FormCover()
{
InitializeComponent();
}
internal void CoverInitialize(IntPtr hwdnParent)
{
if (this.InvokeRequired)
{
this.Invoke(new IntPtrDlg(CoverInitialize), new object[] { hwdnParent });
}
else
{
Win32Utils.SetParent(this.Handle, hwdnParent);
this.SetDesktopLocation(0, 0);
}
}
internal void CoverActivate(IntPtr handleFormulario)
{
if (!Visible)
this.Show();
}
internal void CoverFinalize()
{
Hide();
Win32ParentUtils.SetParent(Handle, new IntPtr());
}
}
What is the difference between these two samples? The first one is working fine, the second one is freezing the aprent window.
As I just stated, you'll need to create a message pump for your form.
Try
Thread thread = new Thread( () =>
{
var formCover = new FormCover();
Application.Run(formCover);
});
thread.ApartmentState = ApartmentState.STA;
thread.Start();
Then you should be able to set the parent of your form.
See here for further reference.
OK guys, I have this Class that shows a "Loading..." Splash Screen. It works great when I call it on Initialize() but not on Form_Load. Instead of showing at the beginning of Form_Load, it shows after all tables are filled and then just hangs there (no lock).
class innerLoad
{
//Delegate for cross thread call to close
private delegate void CloseDelegate();
//The type of form to be displayed as the splash screen.
private static frmLoading splashForm;
static public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashForm != null)
return;
Thread thread = new Thread(new ThreadStart(innerLoad.ShowForm));
thread.IsBackground = true;
//Thread.Sleep(100);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
//volatile static public bool isOpen = false;
static private void ShowForm()
{
splashForm = new frmLoading();
splashForm.ShowDialog();
splashForm.Dispose();
}
static public void CloseForm()
{
try
{
if (splashForm == null)
return;
splashForm.Invoke(new CloseDelegate(innerLoad.CloseFormInternal));
}
catch
{
}
}
static private void CloseFormInternal()
{
splashForm.Close();
splashForm = null;
}
}
And here is the Form_Load Code:
private void frmPayGen_Load(object sender, EventArgs e)
{
//th1 = new Thread(LoadingForm);
//th1.Start();
//Thread.Sleep(500);
innerLoad.ShowSplashScreen();
fill();
innerLoad.CloseForm();
//Thread.Sleep(500);
}
I appreciate your help and I love this site... helps me a lot :D
If you set a breakpoint at the start of your Form Load event, and use F11 to step through, you eventually see this exception:
Exceptions in a Form Load event are basically ignored. If an exception is thrown, nothing after the line where the exception was thrown runs, but the Windows Form doesn't crash either. Taking away this line of code should make things work as you wish.
Since I added a splash screen my main form will sometimes (about 1 every 20 times) disappear like it's minimized (it will be invisible but it will be still on the task bar and if I click it it reappears). Here is my code:
static class Program
{
private static SplashScreen splashScreen = null;
private static ManualResetEvent splashScreenWaiter = null;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowSplashAsync();
BuilderForm2 builderForm2 = new BuilderForm2();
builderForm2.Shown += new EventHandler(builderForm2_Shown);
Application.Run(builderForm2);
}
private static void HideSplash()
{
if (splashScreenWaiter != null)
{
splashScreenWaiter.WaitOne();
splashScreen.Invoke(new Action(splashScreen.Close));
splashScreenWaiter = null;
splashScreen = null;
}
}
private static void builderForm2_Shown(object sender, EventArgs e)
{
HideSplash();
}
private static void ShowSplashAsync()
{
splashScreenWaiter = new ManualResetEvent(false);
Thread splashThread = new Thread(ShowSplash);
splashThread.IsBackground = true;
splashThread.SetApartmentState(ApartmentState.STA);
splashThread.Start(splashScreenWaiter);
}
private static void ShowSplash(object resetEvent)
{
splashScreen = new SplashScreen((ManualResetEvent)resetEvent);
Application.Run(splashScreen);
}
}
And this is SplashScreen code:
public partial class SplashScreen : Form
{
private ManualResetEvent ResetEvent;
bool handleCreated = false;
bool formShown = false;
public SplashScreen(ManualResetEvent resetEvent)
{
ResetEvent = resetEvent;
HandleCreated += new EventHandler(SplashScreen_HandleCreated);
InitializeComponent();
}
private void SetResetEventIfReady()
{
if(handleCreated && formShown) ResetEvent.Set();
}
private void SplashScreen_Shown(object sender, EventArgs e)
{
formShown = true;
SetResetEventIfReady();
}
void SplashScreen_HandleCreated(object sender, EventArgs e)
{
handleCreated = true;
SetResetEventIfReady();
}
}
Nothing jumps out. There is however a very serious race condition in your code. It is related to the SystemEvents class. That class provides important notifications to controls so they can respond to the user changing the Windows theme. That class needs a hidden notification window to receive messages about the changes the user made.
This goes very wrong if your program's first window is created on a worker thread instead of the UI thread. That makes the SystemEvents class create that notification window on the wrong thread (not your worker thread btw). And the events it raises will be called from that thread. Getting the event on that wrong thread creates havoc, controls are not thread-safe. The most typical outcome is that you'll have odd painting problems or the form deadlocks when you lock the workstation. I can imagine what you see going wrong could be explained by this as well.
The .NET framework already has excellent and time-tested support for splash screens. I recommend you use it instead of spinning your own. Check this answer for the code.
If you want to keep your own then you can work around the race problem by pasting this line of code into your Main method, before the ShowSplashAsync call:
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };