Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closing a window using Application.MainLoop.Invoke #2827

Closed
Nictheboy opened this issue Aug 22, 2023 · 4 comments
Closed

Closing a window using Application.MainLoop.Invoke #2827

Nictheboy opened this issue Aug 22, 2023 · 4 comments

Comments

@Nictheboy
Copy link

I have a program that sends request to a server and get a respond. When a request is sent, I open a "Waiting for server reply" window using Application.MainLoop.Invoke(() =>Application.Run(someWindow));. And when a respond is arrived, I close the window using Application.MainLoop.Invoke(() =>someWindow.RequestStop());.

This works pretty well, but when I'm trying to open another window using Application.Run(theWindowThatIWantToRun) in a function that handles UI events (thus I believe I won't need to use Application.MainLoop.Invoke in this circumstance), the window is not opened. Step to step debug shows that at this point someWindow.RequestStop() is excuted, and only this is excuted.

The problem can be simplified as the following program:


using Terminal.Gui;

BugDemo window = new();
Application.Run<BugDemo>();
Application.Shutdown();

class BugDemo : Window
{
    SomeOtherWindow someOtherWindow = new();
    TheWindowThatIWantToRun theWindowThatIWantToRun = new();

    public BugDemo() : base("MyApp")
    {
        Button button = new("Button") { X = 1, Y = 1 };
        button.Clicked += () => DoSomething();
        Add(button);
    }

    async private void DoSomething()
    {
        Application.MainLoop.Invoke(() =>
            Application.Run(someOtherWindow)
        );
        await Task.Delay(1000);
        Application.MainLoop.Invoke(() =>
            someOtherWindow.RequestStop()
        );
        Application.Run(theWindowThatIWantToRun);
    }

    class SomeOtherWindow : Window
    {
        public SomeOtherWindow() : base("Some Other Window") { X = Pos.Center(); Width = 40; Height = 10; }
    }

    class TheWindowThatIWantToRun : Window
    {
        public TheWindowThatIWantToRun() : base("The Window That I Want To Run") { X = Pos.Center(); Width = 40; Height = 10; }
    }
}

I expect this program to open SomeOtherWindow for one second, close it, and open theWindowThatIWantToRun then. However, theWindowThatIWantToRun is closed immediately and sometimes I can't even see it.

  • OS: Windows 10
  • Version 22H2
@tznind
Copy link
Collaborator

tznind commented Aug 23, 2023

Hey, thanks for reaching out and for providing a great repro of what you want. I have adjusted your demo app and can get the correct behaviour that you want with the following:

  • Move the Application.Run into the invoke delegate
  • Use Application.RequestStop() instead of Window.RequestStop()
async private void DoSomething()
    {
        Application.MainLoop.Invoke(() =>
            Application.Run(someOtherWindow)
        );
        await Task.Delay(1000);
        Application.MainLoop.Invoke(() =>
+            {
-                someOtherWindow.RequestStop()
+                Application.RequestStop();
+                Application.Run(theWindowThatIWantToRun);
+            }
        );
-                Application.Run(theWindowThatIWantToRun);
    }

working

@tznind
Copy link
Collaborator

tznind commented Aug 23, 2023

Note that if the user hits Ctrl+Q while the first window is open then the RequestStop() will hit your main form which will cause problems. So you can instead use:

// if loading screen is still running
if(Application.Top == someOtherWindow)
{
    Application.RequestStop();
    Application.Run(theWindowThatIWantToRun);
}

Also it is probably best not to reuse a Window instance e.g. in the demo app someOtherWindow is repeatedly opened and closed. I'm not sure what issues you might encounter running the same instance twice but I think better to just create a new instance each time you show it if possible.

@Nictheboy
Copy link
Author

The problem is solved, it works really well. Thanks for your patience and time!

@BDisp
Copy link
Collaborator

BDisp commented Aug 24, 2023

You are creating two instances here:
BugDemo window = new(); this will never be used. Can be removed.
Application.Run<BugDemo>(); this one will create a new instance and run. Is this one that is used.

Also when you click on a empty on the screen the theWindowThatIWantToRun will disappear, because only one non modal toplevel can be show. To avoid this you can use someOtherWindow and theWindowThatIWantToRun as subviews of BugDemo by adding and removing. Another solution where you still can use the Application.Run(someOtherWindow) and Application.Run(theWindowThatIWantToRun) is using the BugDemo with IsMdiContainer = true. I think this is the better choose for multi non modal toplevels showing all theme and always visible on the screen.

Bellow is a sample that will be needed the PR #2778 to run it. It can be repeated by click the button over and over after closing the theWindowThatIWantToRun window.

using Terminal.Gui;

Application.Run<BugDemo>();
Application.Shutdown();

class BugDemo : Window
{
    SomeOtherWindow someOtherWindow;
    TheWindowThatIWantToRun theWindowThatIWantToRun;
    Button button;
    static bool isDoSomething;

    public BugDemo () : base ("MyApp")
    {
	IsMdiContainer = true;
	button = new ("Button") { X = 1, Y = 1 };
	button.Clicked += () => DoSomething ();
	Add (button);
	KeyPress += (e) => {
		if (e.KeyEvent.Key == Application.QuitKey && isDoSomething) {
			e.Handled = true;
		}
	};
	}

	async private void DoSomething ()
	{
		isDoSomething = true;
		button.Enabled = false;
		someOtherWindow = new ();
		theWindowThatIWantToRun = new ();
		theWindowThatIWantToRun.Closed += (_) => button.Enabled = true;
		Application.MainLoop.Invoke (() =>
			Application.Run (someOtherWindow)
		);
		await Task.Run (async () => {
			await Task.Delay (1000);
			Application.MainLoop.Invoke (() =>
				someOtherWindow.RequestStop ()
			);
		})
		.ContinueWith (_ => Application.MainLoop.Invoke (() => {
			isDoSomething = false;
			Application.Run (theWindowThatIWantToRun);
		})
		);
	}

	class SomeOtherWindow : Window {
		public SomeOtherWindow () : base ("Some Other Window")
		{
			X = Pos.Center (); Width = 40; Height = 10;
			KeyPress += (e) => {
				if (e.KeyEvent.Key == Application.QuitKey && isDoSomething) {
					e.Handled = true;
				}
			};
		}
	}

	class TheWindowThatIWantToRun : Window {
		public TheWindowThatIWantToRun () : base ("The Window That I Want To Run")
		{
			X = Pos.Center (); Width = 40; Height = 10;
			KeyPress += (e) => {
				if (e.KeyEvent.Key == Application.QuitKey && isDoSomething) {
					e.Handled = true;
				}
			};
		}
	}
}
mdi-top.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants