Tsubasa
Tsubasa

Reputation: 1429

How to hide the console window while starting Tkinter applications but reopening it when the GUI button is pressed to run the python script?

import Tkinter as tk
import os
from hhh import hello

def runshell(): 
    root.destroy()
    hello()


root=tk.Tk() 
nvar=tk.StringVar(root) 
en=tk.Entry(textvariable=nvar) 
en.pack() 

btn=tk.Button(text="Shell", command=runshell) 
btn.pack() 

root.mainloop()

Here is the above code of Tkinter GUI.

import time
import sys
import ctypes
ctypes.windll.kernel32.SetConsoleTitleA("HELLO WORLD")

def hello():
    def printf(s):
        for c in s:
            sys.stdout.write('%s' % c)
            sys.stdout.flush()
            time.sleep(0.15)

    printf('Hello, World!')

The above code is named as "hhh.py" which I've imported as module in the first code and is needed to be run in a CUI. I am on windows platform. Now how can I hide the console window that pops up while starting Tkinter apps and at the same time could reopen it by pressing the button to see the output of the "hhh.py" ? Please help... !!!

Upvotes: 1

Views: 4332

Answers (1)

Eryk Sun
Eryk Sun

Reputation: 34290

Hiding an existing console window isn't a good idea in general. It's a shared resource, and if your application dies with the window hidden, it's basically rendering useless every other application that's attached to the console.

You can run your script via pythonw.exe, which doesn't automatically allocate or attach to a console. Then allocate your own console on demand, switch to full-screen mode (if supported), set the window title, and rebind sys.std* to the console device files "CONIN$" and "CONOUT$". You have sole ownership of this window, so you're entitled to hide it.

For example:

import os
import sys
import time
import ctypes
import platform

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

from ctypes import wintypes

user32 = ctypes.WinDLL('user32', use_last_error=True)
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

_windows_version = tuple(map(int, platform.version().split('.')))

kernel32.GetConsoleWindow.restype = wintypes.HWND
user32.SendMessageW.argtypes = (wintypes.HWND, wintypes.UINT,
    wintypes.WPARAM, wintypes.LPARAM)
user32.ShowWindow.argtypes = (wintypes.HWND, ctypes.c_int)

SW_HIDE = 0
SW_MAXIMIZE = 3
SW_SHOW = 5

WM_SYSKEYDOWN = 0x0104
VK_RETURN = 0x0D

def toggle_fullscreen(hwnd=None):
    if _windows_version < (10, 0, 14393):
        return
    if hwnd is None:
        hwnd = kernel32.GetConsoleWindow()
    lparm = (user32.MapVirtualKeyW(VK_RETURN, 0) << 16) | 0x20000001
    user32.SendMessageW(hwnd, WM_SYSKEYDOWN, VK_RETURN, lparm)

def printf(s):
    for c in s:
        sys.stdout.write('%s' % c)
        sys.stdout.flush()
        time.sleep(0.15)

def input(s):
    sys.stdout.write(s)
    sys.stdout.flush()
    return sys.stdin.readline().rstrip('\n')

def hello():
    kernel32.SetConsoleTitleW(u"Hello, World!")
    printf('Hello, World!')
    input('\nPress enter to continue...')

class App(object):
    allocated_console = None

    def __init__(self):
        if self.allocated_console is None:
            # one-time set up for all instances
            allocated = bool(kernel32.AllocConsole())
            App.allocated_console = allocated
            if allocated:
                hwnd = kernel32.GetConsoleWindow()
                user32.ShowWindow(hwnd, SW_HIDE)
                toggle_fullscreen(hwnd)
        self.root = root = tk.Tk()
        nvar = tk.StringVar(root) 
        en = tk.Entry(textvariable=nvar) 
        en.pack() 
        btn = tk.Button(text="Shell", command=self.runshell) 
        btn.pack()

    def mainloop(self):
        self.root.mainloop()

    def runshell(self):
        hwnd = kernel32.GetConsoleWindow()
        user32.ShowWindow(hwnd, SW_SHOW)
        try:
            old_title = ctypes.create_unicode_buffer(512)
            n = kernel32.GetConsoleTitleW(old_title, 512)
            if n > 512:
                old_title = ctypes.create_unicode_buffer(n)
                kernel32.GetConsoleTitleW(old_title, n)
            old_stdin = sys.stdin
            old_stderr = sys.stderr
            old_stdout = sys.stdout
            try:
                with open('CONIN$', 'r') as sys.stdin,\
                     open('CONOUT$', 'w') as sys.stdout,\
                     open('CONOUT$', 'w', buffering=1) as sys.stderr:
                    self.root.destroy()
                    hello()
            finally:
                kernel32.SetConsoleTitleW(old_title)
                sys.stderr = old_stderr
                sys.stdout = old_stdout
                sys.stdin = old_stdin
        finally:
            if self.allocated_console:
                user32.ShowWindow(hwnd, SW_HIDE)

if __name__ == '__main__':
    for i in range(3):
        app = App()
        app.mainloop()

pythonw.exe is typically associated with the .pyw file extension. You can also configure tools such as py2exe to create a non-console executable.


I had to write an input function since raw_input writes its prompt to the stderr FILE stream. I'd rather avoid rebinding C standard I/O from Python.

It toggles full-screen mode for an allocated console in Windows 10 by sending the key combination Alt+Enter to the console window using a WM_SYSKEYDOW message. Full-screen mode isn't supported in Windows Vista up to Windows 8. In that case you could maximize the window and resize the screen buffer.

Note that I'm only hiding the allocated console window. Avoid calling FreeConsole. The C runtime's conio API (e.g. kbhit, getch) caches a handle to "CONIN$", but it provides no dynamically exported and supported way to reset this cached handle. These CRT functions weren't designed to support cycling over multiple consoles. The assumption is that a process is attached to at most one console for its lifetime. At least in Windows 10 this cached handle also prevents the unused console host process from destroying its window and exiting until your process exits.

If the user closes the console while the application is attached, the console will kill the application. This cannot be prevented. At best you can set a control handler to be notified that process is about to be killed.

Another approach to check whether you can hide the console window would be to call GetConsoleProcessList to get the list of attached processes. You're entitled to hide the window if your process is the only one. If there are two processes attached, it seems reasonable to hide the window if the other one is the py[w].exe launcher that Python 3 installs. Checking the latter requires opening a handle to the process via OpenProcess to get the image name via GetModuleBaseName.

Upvotes: 1

Related Questions