
Знакомо, правда? Да, да — это «рабочий стол» Windows 3.1, которая вышла в 1992 году. И даже если вы не из того поколения, у которого сейчас свело олдскулы, вы, я думаю, все равно хоть раз в жизни видели эту ОС (хотя бы на картинке) и не остались к ней равнодушны.
В этой статье мы напишем простенький игрушечный оконный псевдо-менеджер в стиле Windows 3.x. Использовать для этого мы будем Python и стандартную библиотеку Tkinter. Выглядеть он будет так:
Целью статьи является не создание визуальной копии 3.x, а упрощенная реализация главной фичи Windows, которая и дала ей название — окошек. Стилизованных под 3.x, разумеется.
Ну что же, поехали!
Как это будет устроено
Структура нашего проекта будет выглядеть так:
├───main.py │ ├───assets │ └───sources ├───program │ calc.py │ notepad.py │ terminal.py │ └───window manager.py window.py
У нас будет точка входа (main.py), которая запускает всю программу, базовый класс Window (sources/window.py), который будет отвечать за окна нашей псевдо-windows, и WindowManager (sources/manager.py), который этими окнами будет управлять.
В придачу у нас идет еще 3 демки, но они необязательны и непосредственно с менеджером никак не связаны (в смысле зависимостей). Вы можете написать и свои приложения — главное, чтобы они были на tkinter, а их главный класс наследовалcя от tk.Frame
Примечание
Если идея понравится общественности, то в отдельной статье я покажу, как можно запихнуть pygame в фрейм tkinter
Пара слов о стилизации и функциональности
При создании интерфейса я старался ориентироваться на оригинал (см. картинку). В нашем проекте также, как в оригинале, используется синий цвет заголовка, рамка вокруг окна с уголками для изменения размера, используются такие же кнопки в заголовке. Но у нас нет полного сворачивания окон (кнопка свернуть только возвращает окно к исходному размеру), отсутствует Program Manager, группы и много чего ещё. Но базовый функционал все-таки присутствует.
Итак, начнем.
Класс Window
Перед тем как приступить к написанию основного класса окна создадим следующий вспомогательный класс:
from enum import IntFlag class WindowFlags(IntFlag): """ Bitmap flags for window options. 'WN' means 'Window Not', so 'WN_DRAGABLE' means 'Window Not Dragable', 'WN_RESIZABLE' means 'Window Not Resizable', and so on. """ WN_CONTROLS = 1 WN_DRAGABLE = 2 WN_RESIZABLE = 4
С помощью него мы будем определять и задавать некоторые параметры нашего окна:
-
WN_CONTROLS — выключает кнопки свернуть/развернуть
-
WN_DRAGABLE — отключает перетаскивание окна за заголовок (но за углы пока еще можно, это баг)))
-
WN_RESIZABLE — отключает изменение размеров окна
Возможно, вы уже догадались, что эти параметры задаются битовыми флагами. Так гораздо удобнее их сочетать и использовать — не нужно хранить кучу bool-переменных. Позже вы увидите, как мы будем их использовать (если, конечно, будете внимательно читать код)
Таблица флагов
|
Флаги |
Двоичное |
Десятичное |
|---|---|---|
|
WN_CONTROLS |
001 |
1 |
|
WN_DRAGABLE |
010 |
2 |
|
WN_RESIZABLE |
100 |
4 |
Теперь перейдем к окну. Импортируем необходимы библиотеки и инициализируем класс:
import tkinter as tk from PIL import Image, ImageTk # WindowFlags here class Window: """ A class representing a draggable, resizable window in the style of Windows 3.1. The window includes a title bar with minimize, maximize and close buttons, resizable corners, and a content area that can contain child windows. """ def __init__(self, parent, title, content, size, flags): """Initialize a new window. Args: parent: The parent widget (either the main frame or another window's content area) title: The title of the window content: The content widget to be placed inside the window size: A tuple of (x, y, width, height) for the window's initial position and size flags: Bitmap flags for window options """ self.parent = parent self.title = title self.content = content self.size = size self.flags = flags # Initialize window state variables self.is_resizing = False self.is_maximized = False # Store the previous size and position for restoration when un-maximizing self.resize_offset_x = 0 self.resize_offset_y = 0 # Create a list to store child windows references self.childs = [] self.create_window() if self.content: self.set_content(self.content)
Наше окно будет состоять из:
-
Заголовка с кнопками (
self.title)
-
Рамки вокруг окна с выделенными углами для перетаскивания (в init не присутствует, мы будем рисовать ее отдельно)

-
Основного содержимого (
self.content)
-
И дочерних окон (необязательно) (
self.childs)
Остальное, думаю, в объяснениях не нуждается. Идём дальше:
Код
def create_window(self): """ Create the window's visual elements and set up event bindings """ # Unpack the size tuple x, y, width, height = self.size # Create the main window frame self.window_frame = tk.LabelFrame( self.parent, relief="flat", padx=2, pady=2, background="#878A8D", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000" ) self.window_frame.place(x=x, y=y, width=width, height=height) # Add resizable corners if the window is resizable if not self.flags & WindowFlags.WN_RESIZABLE: self.corner_A = tk.LabelFrame( self.window_frame, width=31, height=31, bg="#878A8D", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000", relief="flat" ) self.corner_A.place(x=-5, y=-5) self.corner_B = tk.LabelFrame( self.window_frame, width=31, height=31, bg="#878A8D", highlightthickness=1, bd=1, highlightcolor="#000000", highlightbackground="#000000", relief="flat" ) self.corner_B.place(relx=1.0, x=5, y=-5, anchor='ne') self.corner_C = tk.LabelFrame( self.window_frame, width=31, height=31, bg="#878A8D", highlightthickness=1, bd=1, highlightcolor="#000000", highlightbackground="#000000", relief="flat" ) self.corner_C.place(relx=1.0, x=5, rely=1.0, y=5, anchor='se') self.corner_D = tk.LabelFrame( self.window_frame, width=31, height=31, bg="#878A8D", highlightthickness=1, bd=1, highlightcolor="#000000", highlightbackground="#000000", relief="flat" ) self.corner_D.place(rely=1.0, x=-5, y=5, anchor='sw') # Create the title bar with a close button self.title_bar = tk.Frame( self.window_frame, relief="raised", borderwidth=1, background="#000000", highlightthickness=0, highlightcolor="#FCFCFC", highlightbackground="#FCFCFC" ) self.title_bar.pack(fill="x") self.close_icon = ImageTk.PhotoImage( Image.open("./assets/close_button.bmp")) self.close_button = tk.Button( self.title_bar, command=self.show_context_menu, image=self.close_icon, width=20, height=20, borderwidth=1, relief="flat", anchor="center", bg="#C0C7C8", activebackground="#C0C7C8", fg="#000000", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000" ) self.close_button.pack(side="left") self.title_label = tk.Label( self.title_bar, text=self.title, font=("Arial", 10, "bold"), anchor="center", background="#000076", foreground="#FCFCFC" ) self.title_label.pack(side="left", fill="both", expand=True) # Add maximize and minimize buttons if the window has controls if not self.flags & WindowFlags.WN_CONTROLS: self.maximize_icon = ImageTk.PhotoImage( Image.open("./assets/maximize_button.bmp")) self.maximize_button = tk.Button( self.title_bar, command=self.maximize_window, image=self.maximize_icon, width=20, height=20, borderwidth=1, relief="raised", anchor="center", bg="#C0C7C8", activebackground="#C0C7C8", fg="#000000", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000" ) self.maximize_button.pack(side="right") self.shrink_icon = ImageTk.PhotoImage( Image.open("./assets/minimize_button.bmp")) self.shrink_button = tk.Button( self.title_bar, command=self.minimize_window, image=self.shrink_icon, width=20, height=20, borderwidth=1, relief="raised", anchor="center", bg="#C0C7C8", activebackground="#C0C7C8", fg="#000000", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000" ) self.shrink_button.pack(side="right") # Create the content area of the window self.window_content = tk.Frame( self.window_frame, bg="#ffffff", highlightthickness=1, highlightcolor="#000000", highlightbackground="#000000" ) self.window_content.pack(fill="both", expand=True) # Bind drag events if the window is draggable if not self.flags & WindowFlags.WN_DRAGABLE: self.window_frame.bind("<ButtonPress-1>", self.start_drag) self.window_frame.bind("<ButtonRelease-1>", self.stop_drag) self.window_frame.bind("<B1-Motion>", self.drag) self.title_label.bind("<ButtonPress-1>", self.start_drag) self.title_label.bind("<ButtonRelease-1>", self.stop_drag) self.title_label.bind("<B1-Motion>", self.drag) # Bind resize events if the window is resizable if not self.flags & WindowFlags.WN_RESIZABLE: self.corner_A.bind("<ButtonPress-1>", self.start_resize) self.corner_A.bind("<ButtonRelease-1>", self.stop_resize) self.corner_A.bind("<B1-Motion>", self.resize) self.corner_B.bind("<ButtonPress-1>", self.start_resize) self.corner_B.bind("<ButtonRelease-1>", self.stop_resize) self.corner_B.bind("<B1-Motion>", self.resize) self.corner_C.bind("<ButtonPress-1>", self.start_resize) self.corner_C.bind("<ButtonRelease-1>", self.stop_resize) self.corner_C.bind("<B1-Motion>", self.resize) self.corner_D.bind("<ButtonPress-1>", self.start_resize) self.corner_D.bind("<ButtonRelease-1>", self.stop_resize) self.corner_D.bind("<B1-Motion>", self.resize) self.window_frame.bind("<ButtonPress-1>", self.start_resize) self.window_frame.bind("<ButtonRelease-1>", self.stop_resize) self.window_frame.bind("<B1-Motion>", self.resize) # Track window size and position changes self.window_frame.bind( "<Configure>", self.track_window_size_and_position) self.title_label.bind( "<Configure>", self.track_window_size_and_position) self.context_menu = tk.Menu(self.window_frame, tearoff=0, bg="#C0C0C0", fg="black", activebackground="#808080", activeforeground="white", font=("MS Sans Serif", 8)) self.context_menu.add_command(label="Close", command=self.close_window) self.context_menu.add_command( label="Minimize", command=self.minimize_window) self.context_menu.add_command( label="Maximize", command=self.maximize_window)
Это самая большая часть класса. Оно и понятно — здесь мы создаем рамку вокруг окна, углы (если размер можно изменять), панель меню с кнопками и привязываем различные события. В принципе всё понятно, глубоко вникать не будем, пойдём дальше.
Когда я писал статью, я думал, расписывать ли подробно всю геометрию нашего «менеджера окон», и решил, что не стоит. Во-первых, это очень трудоёмко и объёмно, статья бы получилась большой и скучной. Во-вторых, это никому особо не интересно. Если вы новичок в tkinter вас это только запутает, а если вы уже работали с ним, то вы без особого труда разберётесь, как всё это делается.
А дальше у нас:
Метод show_context_menu. Он отвечает вот за это:

def show_context_menu(self): """Show the context menu at the mouse position.""" # Get the position of the close button x = self.close_button.winfo_rootx() y = self.close_button.winfo_rooty() + self.close_button.winfo_height() # Post the context menu at the position of the close button self.context_menu.tk_popup(x, y)
Затем close_window. Он уничтожает окно при нажатии на кнопку Close
def close_window(self): """ Destroy the window and remove it from the display """ self.window_frame.destroy()
Далее у нас идет свертывание (minimize) и развертывание (maximaize) окна. Пока это реализовано так: кнопка «Свернуть» сворачивает окно к исходному состоянию, в котором оно было создано, но не сворачивает полностью, кнопка «Развернуть» разворачивает окно на весь экран.
def maximize_window(self): """ Maximize the window to fill the entire parent widget Stores the previous size and position for restoration when un-maximizing """ # If the window is already maximized, do nothing if self.is_maximized: return # Store the current size and position for restoration x = self.window_frame.winfo_x() y = self.window_frame.winfo_y() width = self.window_frame.winfo_width() height = self.window_frame.winfo_height() self.previous_size_and_position = (x, y, width, height) # Get parent dimensions parent_width = self.parent.winfo_width() parent_height = self.parent.winfo_height() # Maximize the window to fill the parent widget self.window_frame.place( x=-6, y=-6, width=parent_width + 12, height=parent_height + 13) self.is_maximized = True # Update all maximized child windows to fit new size for child in self.childs: if child.is_maximized: content_width = self.window_content.winfo_width() content_height = self.window_content.winfo_height() child.window_frame.place( x=-6, y=-6, width=content_width + 12, height=content_height + 13) def minimize_window(self): """ Restore the window to its previous size and position if maximized """ # If the window is not minimized, do nothing if self.is_maximized: # Restore to previous size and position x, y, width, height = self.previous_size_and_position self.window_frame.place(x=x, y=y, width=width, height=height) self.is_maximized = False # Update all child windows that are maximized for child in self.childs: if child.is_maximized: child.maximize_window()
Следующим пунктом у нас идет перемещение окна:
def start_drag(self, event): """ Begin window dragging operation """ # Bring the window to the front self.lift(event) # Store initial mouse position and window position self.drag_start_x = event.x_root self.drag_start_y = event.y_root self.window_start_x = self.window_frame.winfo_x() self.window_start_y = self.window_frame.winfo_y() # Prevent conflict with resize operations self.is_dragging = True def stop_drag(self, event: tk.Event): """ End window dragging operation """ self.is_dragging = False self.drag_start_x = None self.drag_start_y = None def drag(self, event: tk.Event): """ Handle window movement during drag operation """ # Prevent dragging if the window is not being dragged if not hasattr(self, 'is_dragging') or not self.is_dragging or self.is_maximized: return # Calculate the displacement from the start position deltax = event.x_root - self.drag_start_x deltay = event.y_root - self.drag_start_y # Update window position based on initial position plus displacement new_x = self.window_start_x + deltax new_y = self.window_start_y + deltay self.window_frame.place(x=new_x, y=new_y)
Получилось корявенько (особенно if not hasattr(self, 'is_dragging') or not self.is_dragging or self.is_maximized), ну да ладно. Особо хочу заметить, что start_drag отвечает еще и за фокус на окне: когда вы нажимаете на заголовок, срабатывает событие drag и self.lift перемещает окно вверх.
Теперь пришел черед ресайзинга:
Код
def start_resize(self, event): """ Begin window resizing operation """ self.lift(event) # Prevent conflict with drag operations if hasattr(self, 'is_dragging'): self.is_dragging = False corner = event.widget # Store initial window geometry self.resize_start_x = event.x_root self.resize_start_y = event.y_root self.window_start_width = self.window_frame.winfo_width() self.window_start_height = self.window_frame.winfo_height() self.window_start_x = self.window_frame.winfo_x() self.window_start_y = self.window_frame.winfo_y() # Determine which corner was clicked and set the cursor accordingly if corner == self.corner_A: cursor = "top_left_corner" handle_x = self.window_frame.winfo_width() - 1 handle_y = self.window_frame.winfo_height() - 1 elif corner == self.corner_B: cursor = "top_right_corner" handle_x = 0 handle_y = self.window_frame.winfo_height() - 1 elif corner == self.corner_C: cursor = "bottom_right_corner" handle_x = 0 handle_y = 0 elif corner == self.corner_D: cursor = "bottom_left_corner" handle_x = self.window_frame.winfo_width() - 1 handle_y = 0 else: cursor = "bottom_left_corner" handle_x = self.window_frame.winfo_width() - 1 handle_y = 0 # Store the handle position relative to the window and the root window handle_pos_x_root = handle_x + self.window_frame.winfo_rootx() handle_pos_y_root = handle_y + self.window_frame.winfo_rooty() # Store the handle position relative to the window self.handle_pos_x = handle_x self.handle_pos_y = handle_y # Store the handle position relative to the root window self.handle_pos_x_root = handle_pos_x_root self.handle_pos_y_root = handle_pos_y_root # Set the cursor and start resizing self.cursor = cursor self.window_frame.config(cursor=cursor) self.is_resizing = True # Store the initial mouse position for resizing self.resize_offset_x = event.x self.resize_offset_y = event.y def stop_resize(self, event: tk.Event): """ End window resizing operation Args: event: The mouse event that triggered the end of resizing """ if hasattr(self, 'is_resizing') and self.is_resizing: # Only stop resizing if we are already resizing (to prevent stopping a resize that never started) # This can happen if you release the mouse button while not resizing (which can happen accidentally while dragging fast) # If we don't prevent that we will end up with an inconsistent state that will make the window flicker and behave erratically while dragging or resizing self.window_frame.config(cursor="") self.is_resizing = False def resize(self, event: tk.Event): """Handle window resizing during resize operation. Args: event: The mouse motion event Updates window dimensions while maintaining minimum size constraints. """ self.lift(event) if not self.is_resizing: return # Calculate the displacement from the start position deltax = event.x_root - self.resize_start_x deltay = event.y_root - self.resize_start_y # Calculate new dimensions based on which corner is being dragged if self.handle_pos_x == 0: # Left corners new_width = max(200, self.window_start_width + deltax) new_x = self.window_start_x else: # Right corners new_width = max(200, self.window_start_width - deltax) new_x = self.window_start_x + deltax if self.handle_pos_y == 0: # Bottom corners new_height = max(150, self.window_start_height + deltay) new_y = self.window_start_y else: # Top corners new_height = max(150, self.window_start_height - deltay) new_y = self.window_start_y + deltay # Update window geometry in a single operation self.window_frame.place( x=new_x, y=new_y, width=new_width, height=new_height)
Эта самая сложная часть логики класса. Если коротко, то это работает так:
-
При нажатии на угол, мы определяем угол (A, B, C, D)
-
Затем фиксируем начальные координаты и размеры
-
После этого для каждого угла задаем «якорную точку». Например для левого верхнего угла (A) это
handle_x = ширина окна - 1 -
Затем рассчитываем дельту и определяем новую ширину и позицию.
Условно это можно представить в виде такой таблицы:
Угол
handle_x
handle_y
Изменение параметров
Левый верх
width-1
height-1
width+, height+, x+, y+
Правый верх
0
height-1
width-, height+, y+
Правый низ
0
0
width-, height-
Левый низ
width-1
0
width+, height-, x+
Далее идет метод track_window_size_and_position, который выполняет две ключевые задачи:
-
Сохранение текущих координат и размер окна, только если оно не максимизировано, чтобы при восстановлении окна из максимизированного/минимизированного состояния можно было вернуть оригинальные размеры
-
Синхронизация дочерних окон (если окно содержит дочерние окна, которые были максимизированы, они автоматически подстраиваются под новый размер родительской области)
def track_window_size_and_position(self, event: tk.Event): """Store window geometry for restore operations. Args: event: The Configure event containing new geometry Used to remember window size/position before maximize/minimize. """ # Track changes in the window's size and position to remember its size before it was maximized or shrinked # This is used to restore the window to its previous size when it is un-maximized or un-shrinked x = event.x y = event.y width = event.width height = event.height if not hasattr(self, 'is_maximized') or not (self.is_maximized): self.previous_size_and_position = (x, y, width, height) # Update maximized child windows when parent size changes if hasattr(self, 'childs'): for child in self.childs: if hasattr(child, 'is_maximized') and child.is_maximized: # Get parent content area dimensions content_width = self.window_content.winfo_width() content_height = self.window_content.winfo_height() # Update child window size to match new parent content area child.window_frame.place(x=-6, y=-6, width=content_width + 12, height=content_height + 13)
Ну и оставшаяся часть:
def lift(self, event: tk.Event): """ Raise window above other windows in z-order. Args: - event: The event that triggered the raise operation """ self.window_frame.lift() def create_child(self, title, content, size, flags): """Create a new window as a child of this window's content area. Returns: Window: The newly created child window """ child_window = Window(self.window_content, title, content, size, flags) # Create a new Window instance as a child # Add the child window to the list of windows self.childs.append(child_window) return child_window def set_content(self, content): """ Set the content widget inside the window and ensure it adapts to the window size. Arguments: - content: The content widget to be placed inside the window (Must be a class, not an instance) """ # We need to destroy the existing content widget before adding a new one # content must be a class, not an instance self.content = content(self.window_content) # then we can place the widget inside the window self.content.pack(fill="both", expand=True)
Метод lift «поднимает» окно вверх


Метод create_child создает дочерние окна

Ну и наконец, метод set_content устанавливает содержимое окна (дочернее от tk.Frame)
На этом класс Window заканчивается. Основная часть нашей программы написана!
Класс WindowManager
Это достаточно простой класс и ничего сложного в нем нет:
import tkinter as tk from .window import Window class WindowManager: """Main window manager class that handles the desktop environment. Provides the main application window and manages creation of child windows. """ def __init__(self, root): """Initialize the window manager. Args: root: The root Tkinter window """ self.root = root self.root.title("Window Manager") self.root.attributes("-fullscreen", True) self.windows = [] self.root.config(cursor="@./assets/cursor_a.cur") self.main_frame = tk.Frame(self.root, bg="#29A97E") self.main_frame.pack(fill="both", expand=True) def create_window(self, title="Window", content=None, size=(50, 50, 210, 200), flags=0): """ Create a new top-level window Args: title: The title of the window content: The content widget to be placed inside the window size: A tuple of (x, y, width, height) for the window's initial position and size flags: Bitmap flags for window options Returns: Window: The newly created window """ window = Window(self.main_frame, title, content, size, flags) self.windows.append(window) return window def create_child(self, parent: Window, title="Window", content=None, size=(50, 50, 210, 200), flags=0): """ Create a new child window inside the given parent window Args: parent: The parent Window instance Returns: Window: The newly created child window """ child = parent.create_child( title, content=content, size=size, flags=flags) self.windows.append(child) return child
Первым делом мы разворачиваем наше окно на весь экран, загружаем олдскульный курсор и заполняем окно фреймом зеленого цвета.
Примечание
Если вы на Linux, загрузку курсора придется выпилить — файлы .cur там не поддерживаются
Далее идут методы create_window и create_child. Они отвечают за добавление нового окна и дочернего окна для уже существующего.
Всё! Давайте протестируем. Для этого в main.py добавим:
import tkinter as tk from sources.window.manager import WindowManager def main(): root = tk.Tk() app = WindowManager(root) app.create_window(title="Notepad", content=None, size=(800, 50, 200, 300)) root.mainloop() if __name__ == "__main__": main()
Запустив это, мы увидим следующее (местоположение курсора съехало, но это баг записи):
Демки
Чтобы сделать наш менеджер поинтереснее добавим три программки. У них отсутствует (кроме, разве только, калькулятора) функциональность, но на них хорошо видны возможности нашей игрушки. Итак:
Калькулятор (/sources/program/calc.py)
import tkinter as tk class Calculator(tk.Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.pack() self.create_widgets() self.expression = "" def create_widgets(self): self.display = tk.Entry(self, font=( 'Arial', 18), borderwidth=2, relief="sunken") self.display.grid(row=0, column=0, columnspan=4, sticky="nsew") buttons = [ '7', '8', '9', '/', '4', '5', '6', '*', '1', '2', '3', '-', '0', '.', '=', '+' ] row_val = 1 col_val = 0 for button in buttons: tk.Button(self, text=button, font=('Arial', 18), command=lambda b=button: self.button_click( b)).grid(row=row_val, column=col_val, sticky="nsew") col_val += 1 if col_val > 3: col_val = 0 row_val += 1 for i in range(1, 5): self.grid_rowconfigure(i, weight=1) for j in range(4): self.grid_columnconfigure(j, weight=1) def button_click(self, char): if char == '=': try: result = str(eval(self.expression)) self.display.delete(0, tk.END) self.display.insert(tk.END, result) self.expression = result except Exception as e: self.display.delete(0, tk.END) self.display.insert(tk.END, "Ошибка") self.expression = "" else: self.expression += str(char) self.display.insert(tk.END, char)
Блокнот (/sources/program/notepad.py)
import tkinter as tk from tkinter import scrolledtext class Notepad(tk.Frame): def __init__(self, parent=None): super().__init__(parent) self.text_area = scrolledtext.ScrolledText(self, wrap=tk.WORD, width=40, height=10, font=("Fixedsys", 12)) self.text_area.pack(fill="both", expand=True)
Терминал (/sources/program/terminal.py)
В Windows 3.1, конечно, не было терминала, так как он работал поверх dos, но я решил добавить его для большей эффектности))
import tkinter as tk from tkinter import scrolledtext class Terminal(tk.Frame): def __init__(self, parent=None): super().__init__(parent) self.text_area = scrolledtext.ScrolledText( self, wrap=tk.WORD, width=80, height=25, font=("Fixedsys", 10), bg="black", fg="white", insertbackground="white", blockcursor=True ) self.text_area.pack(fill="both", expand=True) self.text_area.bind("<Return>", self.execute_command) self.text_area.config(insertofftime=500, insertontime=500) # Blinking cursor self.text_area.focus_set() # Set focus to the text area def execute_command(self, event): command = self.text_area.get("insert linestart", "insert lineend") self.text_area.insert(tk.END, f"\nExecuted: {exec(command)}\n")
Теперь осталось только изменить main.py
import tkinter as tk from sources.window.manager import WindowManager from sources.window.window import WindowFlags from sources.program.notepad import Notepad from sources.program.terminal import Terminal from sources.program.calc import Calculator def main(): root = tk.Tk() app = WindowManager(root) app.create_window(title="Notepad", content=Notepad, size=(800, 50, 200, 300), flags=WindowFlags.WN_DRAGABLE) app.create_child(app.windows[0], title="Notepad", content=Notepad,) app.create_window(title="Terminal", content=Terminal, size=(275, 50, 500, 300), flags=WindowFlags.WN_CONTROLS) app.create_window(title="Calculator", content=Calculator, size=(50, 200, 500, 300), flags=WindowFlags.WN_RESIZABLE) root.mainloop() if __name__ == "__main__": main()
Готово!
Заключение
На этом все. Надеюсь идея вам понравилась, а обилие кода не утомило) На самом деле, эта игрушка не несет никакой практической пользы (кроме обучающей, конечно), но работа с этим кодом мне принесла большое удовольствие, ведь всегда приятно самому сделать что-то, чем раньше сам восхищался 🙂
Весь исходный код можно скачать на моем GitHub: https://github.com/GVCoder09/TkWindowsManager
Спасибо, что уделили время на мою статью (или хотя бы на скроллинг до ее конца)!
ссылка на оригинал статьи https://habr.com/ru/articles/898484/
Добавить комментарий