WinForms switching between forms memory problem [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I currently have 3 forms, a main menu, form 2 and form 3. Is there an optimised way of opening forms (and closing them) as when I switch between them they are taking memory up and I can quickly get to a few GBs usage switching between them over a few minutes.
private void button2_Click(object sender, EventArgs e)
{
this.Hide();
NewForm NewForm = new NewForm();
NewForm.ShowDialog();
this.Close();
this.Dispose();
I've found if comment out this.Hide() my forms aren't actually closing. How can I close the forms when the new one is opened?

Presumably, you're using ShowDialog() to keep the app from exiting when the original, main form is closed?
A different approach would be to use the overload of Application.Run that receives an ApplicationContext.
Then you could write your own "application context" and tell the application to only exit when there are no more forms open. This will allow you to switch from ShowDialog() to Show(). The small change means the old form will be disposed of since code execution continues after Show().
Back in our ApplicationContext class, we can wire up the Idle event and check the count of open forms in Application.OpenForms, letting us know when all forms have been closed and it is time to shut down the application.
So your "program.cs", would change to:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyAppContext(new Form1()));
}
}
public class MyAppContext : ApplicationContext
{
public MyAppContext(Form startingForm)
{
startingForm.Show();
Application.Idle += Application_Idle;
}
private void Application_Idle(object sender, EventArgs e)
{
if (Application.OpenForms.Count == 0)
{
Application.Exit();
}
}
}
Now, whenever you want to switch forms, just make sure to Show() the new form before you Dispose() of the current one:
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.Show(); // make sure you "Show()" the new form BEFORE disposing of the current one below
this.Dispose(); // dispose of the current form
}
With all that in place, the application will shut down automatically once the last form is closed, and the old forms should get disposed of properly.
Remember, though, that the GC manages memory for you...and it might decide that it is going to hold on to that memory to make things load faster in the future. This approach simply addresses the problem that the Dispose() call wasn't being hit since ShowDialog() was stopping execution.

Related

Open Winforms screen after every one minute if the form is closed

A form should open only when there is an event if there is no event it should not display on the screen. So Basically i thought of using a timer to do this. An exe will continously be running and after every minute it checks the db to see if there is data and if there is it shows up on the screen and will only be closed manually with user interaction. After a minute it checks again and displays the form if Data is present in the DB.
I used system.threading.Timer in Program.cs file to open a window after every minute.Below is the code
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.ShowDialog();
}, null, TimeSpan.Zero, 60000);
After certain time I see that this exe is still running in the taskmanager but even though there is data in the DB it stops showing up on the screen. Any help is appreciated.
System.Threading.Timer runs its callback on a threadpool thread. You should never use a threadpool thread for UI work, because:
They don't run a message dispatch loop.
You don't control when the thread gets recycled. UI windows have thread affinity and if their thread exits all the associated windows go poof immediately (you won't even get WM_DESTROY messages).
A normal Application.Run loop on the main thread, with a hidden main window and a UI timer will serve you much better.
I would pass my own custom ApplicationContext to Application.Run() in program.cs.
This will allow you to have NO INTERFACE until your conditions are met. The application will also continue to run (even when you close the Forms) until you explicitly call Application.Exit().
You can keep a reference to your Form at class level. This will help you decide if you need to work with the existing one, or create a new one.
Note that I'm using the System.Windows.Forms.Timer, not the threaded timer.
Something like...
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext());
}
}
public class MyContext : ApplicationContext
{
private EL.CustomMessageBox l = null;
private System.Windows.Forms.Timer timer;
public MyContext()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
bool result = true; // hit the database and get an answer
if (result)
{
if (l == null || l.IsDisposed)
{
// no form has been created yet, or the previous one was closed
// create a new instance
l = new EL.CustomMessageBox();
l.Show();
}
else
{
// if we get in here, then the previous form is still being displayed
// if your form can be minimized, you might need to restore it
// if (l.WindowState == FormWindowState.Minimized)
// {
// restore the window in here?
// }
}
// update the form "l" with some data?
l.xxx = yyy;
}
}
}
I can't help but think that the other answers, massively technically correct as they are, don't actually solve the problem because they probably don't make sense if you aren't aware of how Windows works. Idle_Mind's is closest to what I'd do, though if the forms designer is familiar I'd go for a solution that basically just uses that - as such I present what I would do to solve the task you're faced with:
Have an app with one form (or make this form an autonomous one within another app, but for now maybe do it as a dedicated app for simplicity) - make a new Windows Forms project
Have a Timer (a Windows Forms timer, out of the toolbox, not a System.Threading timer) with an interval of 60000 and Enabled = true
Have a timer Tick event handler on your form (double click the timer in the tray under the form designer to attach an event handler) that queries the DB and finds if there are any messages
If there are new messages, adds them to a listbox or something, and calls this.Show() to show the form
Have an eventhandler attached to the FormClosing event so when the user clicks X, the form hides instead of closes:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
Maybe have the FormClosing event clear the messages listbox. This way if the form opens and the user is on lunch, the messages will build up and build up, then they can read them and clear them by closing the form. Calling Show on an already-visible form does nothing, so the messages will just accumulate into the listbox if more messages come in and the form is already visible
Good quick rule of thumb; never use System.Threading Timer in a Windows Forms app. Use a timer out of the forms designer toolbox instead. Only use a threading timer if you're writing a service or Console app etc. For stability reasons, Windows controls absolutely must be accessed by the thread that originally created the control. Windows forms timer is aware of this and its Tick event can safely access the controls (a form is a control, showing it requires to access it) in a Forms app
You should call Invoke to execute your delegate on the thread that owns the control's underlying window handle.
Something like this should work:
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.Invoke((Action) () =>
{
l.ShowDialog();
});
}, null, TimeSpan.Zero, 60000);
Or even better, use this extension method:
public static void InvokeIfRequired(this Control c, MethodInvoker action)
{
if (c.InvokeRequired)
{
c.Invoke(action);
}
else
{
action();
}
}
And call it like this:
l.InvokeIfRequired(() => { l.ShowDialog(); });
Further information can be found at: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

SynchronizationContext is null and thread ID changes after awaiting anything on UI thread

I'm very confused because I feel like I'm using async/await in a completely typical way here, no different than I've been using it for years, and yet I'm getting the dreaded Control accessed from a thread other than the thread it was created on message all over the place in my application (an old WinForms app I'm trying to breathe new life into).
The project is in .NET Framework 4.8 and I have just converted an old form from using a BackgroundWorker to async/await, and I'm losing the SynchronisationContext after await statements in various places, one example of which is shown below. I replaced the complex chain of async code that actually runs with Task.Delay and the problem still arises. Adding ConfigureAwait(true) or ConfigureAwait(false) to Task.Delay doesn't seem to make any difference, not that I feel I should even need it in this scenario.
Grateful if somebody can point out where I'm going wrong.
private async void ListViewSelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine(SynchronizationContext.Current != null); // true
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); // 1
await Task.Delay(100);
Debug.WriteLine(SynchronizationContext.Current != null); // false - why?
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); // 8
// Touching controls here throws error
}
EDIT 1: Okay, I chased the creation of the form upwards, and I've hit the root of the problem though I still don't understand why it's a problem. The problem can be reduced to this:
// MainForm designer:
this.Load += new System.EventHandler(this.MainFormLoad);
// MainForm code-behind:
private async void MainFormLoad(object sender, EventArgs e) {
var form = new BackupManagerForm();
form.Show(); // BackupManagerForm will exhibit the problem
// ... some async stuff including adding dynamic context menus to MainForm
}
The problem can be removed by removing async from the Load handler in MainForm. Now that I know that I can code around it, but I'm curious as to why that damages the SynchronizationContext so far downstream (despite the fact it seems perfectly able to add the context menus I mentioned above to MainForm for example).
EDIT 2: I just tried to reconstruct the problem in a blank WinForms problem and can't reproduce it. Making the Load handler for MainForm non-async definitely does solves the problem, but actually that's annoying because there's a load of async work I really need to do at that point.
As requested, this is how MainForm is instantiated (there's a lot of code removed for brevity here but hopefully nothing significant):
public static class App {
[STAThread]
public static void Main(string[] args) {
var builder = new ContainerBuilder();
IocConfig.RegisterDependencies(builder);
var container = builder.Build();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));
var bootstrapper = ServiceLocator.Current.GetInstance<IAppBootstrapper>();
bootstrapper.Startup();
}
public class AppBootstrapper : IAppBootstrapper {
private readonly ISettings _settings;
private readonly MainForm _mainForm;
public AppBootstrapper(MainForm mainForm, ISettings settings) {
_mainForm = mainForm;
_settings = settings;
}
public void Startup() {
// Other stuff removed but perhaps these might be relevant?
Application.DoEvents();
_settings.SaveAsync().Wait();
Application.Run(_mainForm);
}
}
EDIT 3: That Application.DoEvents() turns out to be significant. So does instantiating the form with an IoC container. If I either remove the Application.DoEvents() statement or use Application.Run(new MainForm()) the problem disappears. The Application.DoEvents() exists because of a splash screen that is shown before MainForm with splashForm.Show() followed by Application.DoEvents() - when MainForm eventually loads, it hides the splash screen on load.
I've found a good solution to the problem now. This comprehensively solves the problem without any annoying side effects as far as I can tell:
public void Startup() {
var context = SynchronizationContext.Current;
_splashForm.Show();
Application.DoEvents();
...
SynchronizationContext.SetSynchronizationContext(context);
Application.Run(_mainForm);
}
I still don't have a clear understanding of the problem, but it's obviously to do with the fact that the main form is not the first form to create a message loop. What's interesting though is how far down inside the application that manifests as a significant problem. Anyway, I'd be happy to accept an answer from anyone who can explain the phenomenon.

Can't exit application

I was working on a software in Visual Studio, writing code in C#, while I noticed something. I can't figure out how to exit the application.
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void ExitButton_Click(object sender, EventArgs e)
{
Application.Exit(); // This works, when I run the application and click on the button, it will indeed quit.
}
}
}
So as said, when I click on the button, it will indeed quit the application. However if I place Application.Exit(); somewhere else, it won't work. So if I modify Form1() like this, it won't automatically quit the application:
public Form1()
{
InitializeComponent();
Application.Exit();
}
It would be necessary to quit the application instantly if certain conditions are met, for example if some application files are missing.
I than tried to do the following:
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
closeapp();
}
public void closeapp()
{
Application.Exit();
}
private void ExitButton_Click(object sender, EventArgs e)
{
closeapp();
}
}
}
Now if I run the application, it will not exit automatically, however, if I click on the Exit button, it will quit the application.
So it looks like that if it is not called from a event that happens within the form, it will not close the application.
I have searched online (including Google and StackOverflow) with the keyword "Application.Exit();" not working. They recommended that I use "Environment.Exit();". Now in my testing application, this works, but in the real application that I am working on, when I remove all the code (of course while having a backup), it looks like the following and it still does not work.
namespace Censored
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Environment.Exit(1);
}
}
}
What can possibly be the cause of it not working in one application, but working in another one? Is something corrupted?
You can't apply Application.Exit when the application is being initialized. So, why don't you force to exit the application in the FormLoad if your conditions aren't met;
private void Form1_Load(object sender, EventArgs e)
{
Application.Exit();
}
I think the best thing to do here is not Application.Exit because as you said, you want to quit the application because there are some files missing. If there are indeed some files missing, you should really show a message to the user and then quit. Application.Exit will quit the application silently so the user does not know that some files are missing. Bad UX!
One easy way to show an error message is to throw an exception:
throw new FileNotFoundException(
"Some application files are missing! Please add them in before starting this application!");
The reason why Application.Exit does not work here is because the "Application" has not been created at that point in time. In your Main method, you should have this line:
Application.Run(new Form1());
The "Application" will be created after the above method is called. But before Run is called, what else is called? The form's constructor! It then calls InitializeComponent, where you want to exit the application. At this time Run has not been called yet!

Application won't end VideoStreams and Exit

I've got an application what's working with two video streams.
When the form is being closed, it runs this function:
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (FinalVideoDevice.IsRunning) { FinalVideoDevice.Stop(); }
if (streamMJPEG.IsRunning) { streamMJPEG.Stop(); }
Application.Exit();
}
But in reality it doesn't kill the application, only hides the form, but still is seen from TaskManager/Processes.
Any ideas what I might be doing wrong?
Thanks!
Assuming you are in Windows Forms you can call Application.ExitThread();
in general one of the reasons why you still see the process in TaskManager could be that you still have some background / worker threads active.
Roger check this question/answers as well: Application.Exit

C# - Title Bar "X" does not close the program AND form switching

I have a Visual C# project using windowsforms that does not exit when the X is clicked on any form OTHER than the first form. I think this may have something to do with my form switching?
Currently, I have a Template.CS which is exactly what it sounds like. All of my usage forms extend this by:
public partial class Welcome : ADL.template
Then, I switch between forms by invoking this method:
public static void formSwitch(Form in_form, Form out_form)
{
in_form.Hide();
out_form.Show();
}
Called by:
Program.formSwitch(this, new frmUserInput());
What i think is happening here is, the X is closing the Form NOT the application because the starting form is Hidden, not closed. Is there a better way for me to switch between forms?
Thanks!
Well before answering your question, I should point out that Hide doesn't actually close your form, it only (as the name implies) hides it. So as time goes on, you'll keep piling on forms until you either run out of GDI objects or out of memory, either way you'll crash.
You are kind of correct about the reason why your application isn't closing though: even though you close the current form, all your other forms are still loaded so your application won't end.
To fix this, the best way would be to actually close your forms when you don't need them anymore. You won't even have to add any code to close your application then.
Now if you don't want to do that for whatever reason, you can always just call Application.Exit. I strongly discourage you to pursue this "solution" though.
Edit: as for a possible solution, you could change Program.cs to something like:
static class Program
{
static Form NextForm=new frmLogin(); // or whatever your first form is
static public void SetNext(Form next) { NextForm=next; }
static void Main()
{
while(NextForm!=null)
{
Form _next=NextForm;
NextForm=null; // so it closes at the end
Application.Run(NextForm);
}
}
}
And then your formSwitch would become:
public static void formSwitch(Form in_form, Form out_form)
{
Program.SetNext(out_form);
in_form.Close();
}
It looks weird because your workflow is weird for a Windows program. This is more the workflow of a 1970 FORTRAN program running in DOS.
The default Windows Forms application behaviour is: the application is closed when the MAIN window is closed.
The main window is the first one you've created. When you called Hide() on this window, it is rendered invisible (but still exists). So, closing the second window doesn't close the application.
You can edit the code in the ~/Program.cs file as the following:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form f = new Form();
f.Show();
Application.Run();
}
}
This way, the application wont close until you call Application.Exit() exclusivly.

Categories