Top.Mail.Ru

CustomTkinter + Sockets: пишем красивого сетевого помощника за 20 минут

CustomTkinter + Sockets: пишем красивого сетевого помощника за 20 минут

Проблема: скучные консольные сокеты

Когда нужно передать данные по сети (TCP/UDP), большинство пишет консольный скрипт. Но:

  • неудобно тестировать

  • непонятно, отправилось ли сообщение

  • нет истории переписки

А под рукой — обычный Tkinter, который выглядит как программа из 90-х.

Решение: берём CustomTkinter для дизайна и сокеты для передачи данных. Получаем стильное приложение, которое можно показывать заказчику или портфолио.

Ключевые слова статьи: python socket gui customtkinter, клиент сервер приложение на python с интерфейсом, чат на сокетах с темной темой


Что будем строить

Простой TCP-чат (или отправитель команд) с двумя режимами:

  • Сервер — принимает подключения и сообщения

  • Клиент — подключается к серверу и отправляет данные

Окно на CustomTkinter:

  • поле для IP и порта

  • лог сообщений (скроллируемый)

  • поле ввода текста

  • кнопка отправки

  • выбор темы (тёмная/светлая)


Установка библиотек

pip install customtkinter

Встроенный модуль socket дополнительно ставить не нужно.


Структура приложения

Мы не будем делать два отдельных скрипта. Сделаем одно окно, где можно запустить и сервер, и клиента (по кнопкам).

import customtkinter as ctk
import socket
import threading

ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

Класс сетевого приложения

 
class SocketApp:
    def __init__(self):
        self.window = ctk.CTk()
        self.window.title("Socket Commander")
        self.window.geometry("600x500")
        
        # Переменные
        self.server_socket = None
        self.client_socket = None
        self.is_running = False
        
        self.create_widgets()
        
    def create_widgets(self):
        # Верхняя панель: IP + порт
        top_frame = ctk.CTkFrame(self.window)
        top_frame.pack(pady=10, padx=10, fill="x")
        
        ctk.CTkLabel(top_frame, text="IP:").grid(row=0, column=0, padx=5)
        self.ip_entry = ctk.CTkEntry(top_frame, placeholder="127.0.0.1")
        self.ip_entry.grid(row=0, column=1, padx=5)
        
        ctk.CTkLabel(top_frame, text="Port:").grid(row=0, column=2, padx=5)
        self.port_entry = ctk.CTkEntry(top_frame, placeholder="12345")
        self.port_entry.grid(row=0, column=3, padx=5)
        
        # Кнопки режимов
        btn_frame = ctk.CTkFrame(self.window)
        btn_frame.pack(pady=5, padx=10, fill="x")
        
        self.server_btn = ctk.CTkButton(btn_frame, text="Запустить сервер", command=self.start_server)
        self.server_btn.pack(side="left", padx=5)
        
        self.client_btn = ctk.CTkButton(btn_frame, text="Подключиться как клиент", command=self.start_client)
        self.client_btn.pack(side="left", padx=5)
        
        # Лог сообщений
        self.log_text = ctk.CTkTextbox(self.window, height=250)
        self.log_text.pack(pady=10, padx=10, fill="both", expand=True)
        
        # Нижняя панель: отправка
        bottom_frame = ctk.CTkFrame(self.window)
        bottom_frame.pack(pady=10, padx=10, fill="x")
        
        self.msg_entry = ctk.CTkEntry(bottom_frame, placeholder="Введите сообщение...")
        self.msg_entry.pack(side="left", fill="x", expand=True, padx=(0, 5))
        
        self.send_btn = ctk.CTkButton(bottom_frame, text="Отправить", command=self.send_message)
        self.send_btn.pack(side="right")
        
        # Переключатель темы
        theme_switch = ctk.CTkSwitch(
            self.window, 
            text="Тёмная тема", 
            command=self.toggle_theme,
            onvalue="dark", 
            offvalue="light"
        )
        theme_switch.pack(pady=5)
        theme_switch.select()  # по умолчанию тёмная
        
    def log(self, message):
        """Вывод в текстовое поле"""
        self.log_text.insert("end", f"{message}\n")
        self.log_text.see("end")
        
    def toggle_theme(self):
        current = ctk.get_appearance_mode()
        new = "light" if current == "Dark" else "dark"
        ctk.set_appearance_mode(new)

Реализация сервера (TCP)

    def start_server(self):
        ip = self.ip_entry.get()
        port = int(self.port_entry.get())
        
        def server_worker():
            try:
                self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.server_socket.bind((ip, port))
                self.server_socket.listen(1)
                self.log(f"[Сервер] Запущен на {ip}:{port}")
                self.log("[Сервер] Ожидание подключения...")
                
                conn, addr = self.server_socket.accept()
                self.log(f"[Сервер] Подключён клиент: {addr}")
                self.client_socket = conn
                
                while self.is_running:
                    data = conn.recv(1024)
                    if not data:
                        break
                    self.log(f"[Клиент -> Сервер] {data.decode('utf-8')}")
                    
            except Exception as e:
                self.log(f"[Ошибка сервера] {e}")
            finally:
                if self.server_socket:
                    self.server_socket.close()
                    
        self.is_running = True
        thread = threading.Thread(target=server_worker, daemon=True)
        thread.start()

Реализация клиента

    def start_client(self):
        ip = self.ip_entry.get()
        port = int(self.port_entry.get())
        
        def client_worker():
            try:
                self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.client_socket.connect((ip, port))
                self.log(f"[Клиент] Подключён к {ip}:{port}")
                
                while self.is_running:
                    # В реальном чате тут нужен приём сообщений
                    # Для простоты — клиент только отправляет
                    pass
                    
            except Exception as e:
                self.log(f"[Ошибка клиента] {e}")
                
        self.is_running = True
        thread = threading.Thread(target=client_worker, daemon=True)
        thread.start()

Отправка сообщений

    def send_message(self):
        msg = self.msg_entry.get()
        if not msg:
            return
            
        if self.client_socket:
            try:
                self.client_socket.send(msg.encode('utf-8'))
                self.log(f"[Вы -> Сервер] {msg}")
                self.msg_entry.delete(0, "end")
            except Exception as e:
                self.log(f"[Ошибка отправки] {e}")
        else:
            self.log("[Ошибка] Нет активного подключения")

Запуск

    def run(self):
        self.window.mainloop()

if __name__ == "__main__":
    app = SocketApp()
    app.run()

Вы только что написали профессиональное сетевое приложение с интерфейсом, которое не стыдно показать. CustomTkinter убил двух зайцев:

  • дал современный внешний вид (скругления, hover, тёмная тема)

  • оставил родную логику Tkinter + сокетов

Автор:

9

Читайте также

0 комментариев

Оставьте комментарий

Комментарии

×
Подпишитесь на наш Telegram-канал, чтобы быть в курсе всех новостей и акций!
Подписаться