开源文本编辑器
2026-07-26 16:21:51
发布于:浙江
直接上代码!

如果出现问题可能是没有安装tkinter库,如果是正版的python(就是python IDE)是自带的,非正版的注意安装
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, font
import time
import os
# ====================== 安装窗口类 ======================
class InstallerWindow:
def __init__(self, root, on_finish_callback):
# 主窗口基础配置
self.root = root
self.root.title("软件安装")
self.root.geometry("570x220")
self.root.resizable(False, False)
self.center_window()
# 安装完成回调(安装结束后启动编辑器)
self.on_finish_callback = on_finish_callback
# 核心状态与进度变量
self.is_installing = True
self.current_progress = 0
self.max_progress = 100
self.start_time = time.time()
self.pause_time = 0
# 创建界面组件
self.create_widgets()
def center_window(self):
"""窗口居中显示"""
self.root.update_idletasks()
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - self.root.winfo_width()) // 2
y = (screen_height - self.root.winfo_height()) // 2
self.root.geometry(f"+{x}+{y}")
def create_widgets(self):
"""创建所有界面组件:状态标签、进度条+剩余时间、按钮"""
self.status_label = ttk.Label(
self.root,
text="正在安装软件,请稍候...",
font=("微软雅黑", 12)
)
self.status_label.pack(pady=15)
self.progress_frame = ttk.Frame(self.root)
self.progress_frame.pack(pady=5, padx=20, fill=tk.X)
# 进度条
self.progress_bar = ttk.Progressbar(
self.progress_frame,
orient=tk.HORIZONTAL,
length=350,
mode="determinate"
)
self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.remaining_time_label = ttk.Label(
self.progress_frame,
text="剩余时间:--:--:--",
font=("微软雅黑", 10)
)
self.remaining_time_label.pack(side=tk.LEFT, padx=10)
self.btn_frame = ttk.Frame(self.root)
self.btn_frame.pack(pady=15)
self.pause_btn = ttk.Button(
self.btn_frame,
text="暂停",
command=self.pause_install
)
self.pause_btn.grid(row=0, column=0, padx=10)
self.continue_btn = ttk.Button(
self.btn_frame,
text="继续",
command=self.continue_install,
state=tk.DISABLED
)
self.continue_btn.grid(row=0, column=1, padx=10)
self.update_progress()
def format_seconds(self, total_seconds):
"""工具方法:将秒数格式化为「HH:MM:SS」样式"""
total_seconds = int(max(total_seconds, 0))
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
def calculate_remaining_time(self):
"""核心计算:根据已用时间和当前进度,计算剩余安装时间"""
if not self.is_installing or self.current_progress == 0:
return None
if self.current_progress >= 100:
return 0
total_elapsed = time.time() - self.start_time
effective_elapsed = total_elapsed - self.pause_time
progress_ratio = self.current_progress / 100
remaining_ratio = (100 - self.current_progress) / 100
remaining_seconds = (effective_elapsed / progress_ratio) * remaining_ratio
return remaining_seconds
def update_progress(self):
"""更新进度条 + 剩余时间 + 状态,安装完成1秒后自动关闭并启动编辑器"""
if self.is_installing and self.current_progress < self.max_progress:
self.current_progress += 1
self.progress_bar["value"] = self.current_progress
remaining_seconds = self.calculate_remaining_time()
if remaining_seconds is not None:
formatted_time = self.format_seconds(remaining_seconds)
self.remaining_time_label.config(text=f"剩余时间:{formatted_time}")
if self.current_progress == self.max_progress:
self.status_label.config(text="安装完成!")
self.remaining_time_label.config(text="剩余时间:00:00:00")
self.pause_btn.config(state=tk.DISABLED)
self.continue_btn.config(state=tk.DISABLED)
# 1秒后关闭安装窗口,执行回调打开编辑器
self.root.after(1000, self.close_window)
self.root.after(100, self.update_progress)
def close_window(self):
"""关闭安装窗口,触发编辑器启动"""
self.root.destroy()
# 调用回调打开文本编辑器
self.on_finish_callback()
def pause_install(self):
"""暂停安装"""
self.is_installing = False
self.status_label.config(text="安装已暂停")
self.pause_btn.config(state=tk.DISABLED)
self.continue_btn.config(state=tk.NORMAL)
self.pause_start = time.time()
def continue_install(self):
"""继续安装"""
self.is_installing = True
self.status_label.config(text="继续安装中...")
self.continue_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.NORMAL)
self.pause_time += time.time() - self.pause_start
self.update_progress()
# ====================== 文本编辑器类 ======================
class TextEditor:
def __init__(self, root):
self.root = root
self.root.title("简易文本编辑器")
self.root.geometry("800x600")
self.file_path = None
self.is_saved = True
self.base_font_size = 12
self.current_font_size = self.base_font_size
self.current_font_family = "Consolas"
self.text_font = (self.current_font_family, self.current_font_size)
self.main_frame = tk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 初始化行号栏
self.current_line_digits = 2
self.line_numbers = tk.Text(self.main_frame, width=3, padx=2, state=tk.DISABLED,
background="#f0f0f0", font=self.text_font, wrap=tk.NONE)
self.line_numbers.pack(side=tk.LEFT, fill=tk.Y)
self.text_area = tk.Text(self.main_frame, wrap=tk.NONE, undo=True, font=self.text_font)
self.v_scrollbar = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.text_area.yview)
self.h_scrollbar = ttk.Scrollbar(self.root, orient=tk.HORIZONTAL, command=self.text_area.xview)
self.text_area.configure(yscrollcommand=self.v_scrollbar.set, xscrollcommand=self.h_scrollbar.set)
self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
self.text_area.bind("<<Modified>>", self.on_text_modified)
self.text_area.bind("<Control-MouseWheel>", self.on_zoom)
self.text_area.bind("<KeyRelease>", self.update_line_numbers)
self.text_area.bind("<MouseWheel>", self.update_line_numbers)
self.text_area.bind("<ButtonRelease-1>", self.update_line_numbers)
self.root.bind("<Configure>", self.update_line_numbers)
self.create_menu()
self.find_window = None
self.find_entry = None
self.replace_entry = None
self.current_find_index = tk.END
self.update_line_numbers()
def create_menu(self):
menubar = tk.Menu(self.root)
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
file_menu.add_command(label="另存为", command=self.save_as_file, accelerator="Ctrl+Shift+S")
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.exit_app, accelerator="Alt+F4")
menubar.add_cascade(label="文件", menu=file_menu)
edit_menu = tk.Menu(menubar, tearoff=0)
edit_menu.add_command(label="查找替换", command=self.show_find_replace, accelerator="Ctrl+F")
edit_menu.add_command(label="撤销", command=self.text_area.edit_undo, accelerator="Ctrl+Z")
edit_menu.add_command(label="重做", command=self.text_area.edit_redo, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="放大字体", command=lambda: self.adjust_font_size(1), accelerator="Ctrl+=")
edit_menu.add_command(label="缩小字体", command=lambda: self.adjust_font_size(-1), accelerator="Ctrl+-")
edit_menu.add_command(label="恢复默认字体", command=self.reset_font_size, accelerator="Ctrl+0")
edit_menu.add_separator()
edit_menu.add_command(label="更改字体...", command=self.change_font, accelerator="Ctrl+T")
menubar.add_cascade(label="编辑", menu=edit_menu)
self.root.config(menu=menubar)
self.root.bind("<Control-o>", lambda e: self.open_file())
self.root.bind("<Control-s>", lambda e: self.save_file())
self.root.bind("<Control-Shift-s>", lambda e: self.save_as_file())
self.root.bind("<Control-f>", lambda e: self.show_find_replace())
self.root.bind("<Control-equal>", lambda e: self.adjust_font_size(1))
self.root.bind("<Control-minus>", lambda e: self.adjust_font_size(-1))
self.root.bind("<Control-0>", lambda e: self.reset_font_size())
self.root.bind("<Control-t>", lambda e: self.change_font())
def on_text_modified(self, event):
if self.is_saved:
self.is_saved = False
self.update_title()
self.text_area.edit_modified(False)
def update_title(self):
title = "简易文本编辑器"
if self.file_path:
title = f"{os.path.basename(self.file_path)} - 简易文本编辑器"
if not self.is_saved:
title = f"*{title}"
self.root.title(title)
def open_file(self):
if not self.is_saved:
response = messagebox.askyesnocancel("提示", "当前文件有未保存的修改,是否保存?")
if response:
self.save_file()
elif response is None:
return
file_path = filedialog.askopenfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, "r", encoding="utf-8") as file:
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, file.read())
self.file_path = file_path
self.is_saved = True
self.update_title()
self.update_line_numbers()
except Exception as e:
messagebox.showerror("错误", f"打开文件失败:{str(e)}")
def save_file(self):
if not self.file_path:
self.save_as_file()
return
try:
with open(self.file_path, "w", encoding="utf-8") as file:
file.write(self.text_area.get(1.0, tk.END))
self.is_saved = True
self.update_title()
messagebox.showinfo("提示", "文件保存成功!")
except Exception as e:
messagebox.showerror("错误", f"保存文件失败:{str(e)}")
def save_as_file(self):
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, "w", encoding="utf-8") as file:
file.write(self.text_area.get(1.0, tk.END))
self.file_path = file_path
self.is_saved = True
self.update_title()
messagebox.showinfo("提示", "文件另存为成功!")
except Exception as e:
messagebox.showerror("错误", f"另存为失败:{str(e)}")
def show_find_replace(self):
if self.find_window and self.find_window.winfo_exists():
self.find_window.lift()
return
self.find_window = tk.Toplevel(self.root)
self.find_window.title("查找替换")
self.find_window.geometry("400x150")
self.find_window.resizable(False, False)
self.find_window.transient(self.root)
self.find_window.grab_set()
tk.Label(self.find_window, text="查找:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.find_entry = tk.Entry(self.find_window, width=30)
self.find_entry.grid(row=0, column=1, padx=5, pady=5)
self.find_entry.focus()
tk.Label(self.find_window, text="替换为:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.replace_entry = tk.Entry(self.find_window, width=30)
self.replace_entry.grid(row=1, column=1, padx=5, pady=5)
btn_frame = tk.Frame(self.find_window)
btn_frame.grid(row=2, column=1, padx=5, pady=5, sticky="w")
tk.Button(btn_frame, text="查找下一个", command=self.find_next).grid(row=0, column=0, padx=2)
tk.Button(btn_frame, text="替换", command=self.replace_current).grid(row=0, column=1, padx=2)
tk.Button(btn_frame, text="全部替换", command=self.replace_all).grid(row=0, column=2, padx=2)
tk.Button(btn_frame, text="取消", command=self.find_window.destroy).grid(row=0, column=3, padx=2)
self.find_entry.bind("<Return>", lambda e: self.find_next())
def find_next(self):
search_text = self.find_entry.get()
if not search_text:
return
self.text_area.tag_remove("find", 1.0, tk.END)
start_index = self.current_find_index if self.current_find_index != tk.END else 1.0
pos = self.text_area.search(search_text, start_index, stopindex=tk.END, nocase=True)
if pos:
end_pos = f"{pos}+{len(search_text)}c"
self.text_area.tag_add("find", pos, end_pos)
self.text_area.tag_config("find", background="yellow")
self.text_area.see(pos)
self.text_area.mark_set(tk.INSERT, end_pos)
self.current_find_index = end_pos
else:
pos = self.text_area.search(search_text, 1.0, stopindex=tk.END, nocase=True)
if pos:
end_pos = f"{pos}+{len(search_text)}c"
self.text_area.tag_add("find", pos, end_pos)
self.text_area.tag_config("find", background="yellow")
self.text_area.see(pos)
self.text_area.mark_set(tk.INSERT, end_pos)
self.current_find_index = end_pos
else:
messagebox.showinfo("提示", f"未找到 '{search_text}'")
self.current_find_index = tk.END
def replace_current(self):
search_text = self.find_entry.get()
replace_text = self.replace_entry.get()
if not search_text or self.current_find_index == tk.END:
self.find_next()
return
start_index = self.text_area.index(tk.SEL_FIRST) if self.text_area.tag_ranges(tk.SEL) else None
if start_index:
end_index = self.text_area.index(tk.SEL_LAST)
self.text_area.delete(start_index, end_index)
self.text_area.insert(start_index, replace_text)
self.current_find_index = f"{start_index}+{len(replace_text)}c"
self.find_next()
def replace_all(self):
search_text = self.find_entry.get()
replace_text = self.replace_entry.get()
if not search_text:
return
if self.find_window:
self.find_window.destroy()
self.text_area.tag_remove("find", 1.0, tk.END)
count = 0
start_index = 1.0
while True:
pos = self.text_area.search(search_text, start_index, stopindex=tk.END, nocase=True)
if not pos:
break
end_pos = f"{pos}+{len(search_text)}c"
self.text_area.delete(pos, end_pos)
self.text_area.insert(pos, replace_text)
count += 1
start_index = f"{pos}+{len(replace_text)}c"
messagebox.showinfo("提示", f"已替换 {count} 处匹配项")
self.update_line_numbers()
def exit_app(self):
if not self.is_saved:
response = messagebox.askyesnocancel("提示", "当前文件有未保存的修改,是否保存?")
if response:
self.save_file()
elif response is None:
return
self.root.quit()
def on_zoom(self, event):
if event.delta > 0:
self.adjust_font_size(1)
else:
self.adjust_font_size(-1)
def adjust_font_size(self, delta):
new_size = self.current_font_size + delta
if 6 <= new_size <= 72:
self.current_font_size = new_size
self.text_font = (self.current_font_family, self.current_font_size)
self.text_area.config(font=self.text_font)
self.line_numbers.config(font=self.text_font)
self.update_line_numbers()
def reset_font_size(self):
self.current_font_size = self.base_font_size
self.text_font = (self.current_font_family, self.current_font_size)
self.text_area.config(font=self.text_font)
self.line_numbers.config(font=self.text_font)
self.update_line_numbers()
def update_line_numbers(self, event=None):
line_count = int(self.text_area.index(tk.END).split('.')[0]) - 1
new_digits = len(str(max(line_count, 1)))
if new_digits != self.current_line_digits:
self.current_line_digits = new_digits
new_width = new_digits + 1
self.line_numbers.config(width=new_width)
self.line_numbers.config(state=tk.NORMAL)
self.line_numbers.delete(1.0, tk.END)
for i in range(1, line_count + 1):
line_num_str = f"{i:0{self.current_line_digits}d}\n"
self.line_numbers.insert(tk.END, line_num_str)
if line_count == 0:
self.line_numbers.insert(tk.END, f"01\n")
self.line_numbers.config(state=tk.DISABLED)
self.line_numbers.yview_moveto(self.text_area.yview()[0])
def change_font(self):
font_window = tk.Toplevel(self.root)
font_window.title("更改字体")
font_window.geometry("450x300")
font_window.resizable(False, False)
font_window.transient(self.root)
font_window.grab_set()
tk.Label(font_window, text="字体:").grid(row=0, column=0, padx=10, pady=10, sticky="ne")
tk.Label(font_window, text="字号:").grid(row=1, column=0, padx=10, pady=10, sticky="ne")
font_families = sorted(font.families())
font_frame = tk.Frame(font_window)
font_frame.grid(row=0, column=1, padx=10, pady=10)
font_listbox = tk.Listbox(font_frame, width=30, height=8, exportselection=False)
font_scrollbar = ttk.Scrollbar(font_frame, orient=tk.VERTICAL, command=font_listbox.yview)
font_listbox.configure(yscrollcommand=font_scrollbar.set)
font_listbox.pack(side=tk.LEFT, fill=tk.BOTH)
font_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
for f in font_families:
font_listbox.insert(tk.END, f)
try:
current_index = font_families.index(self.current_font_family)
font_listbox.see(current_index)
font_listbox.selection_set(current_index)
except:
font_listbox.selection_set(0)
size_var = tk.StringVar(value=str(self.current_font_size))
size_spinbox = tk.Spinbox(font_window, from_=6, to=72, textvariable=size_var,
width=10, increment=1)
size_spinbox.grid(row=1, column=1, padx=10, pady=10, sticky="nw")
def apply_font():
try:
selected_font = font_listbox.get(font_listbox.curselection())
selected_size = int(size_var.get())
if 6 <= selected_size <= 72:
self.current_font_family = selected_font
self.current_font_size = selected_size
self.text_font = (self.current_font_family, self.current_font_size)
self.text_area.config(font=self.text_font)
self.line_numbers.config(font=self.text_font)
self.update_line_numbers()
font_window.destroy()
else:
messagebox.showwarning("警告", "字号必须在6-72之间!")
except:
messagebox.showwarning("警告", "请选择有效的字体和字号!")
btn_frame = tk.Frame(font_window)
btn_frame.grid(row=2, column=0, columnspan=2, pady=20)
tk.Button(btn_frame, text="确定", command=apply_font, width=10).pack(side=tk.LEFT, padx=20)
tk.Button(btn_frame, text="取消", command=font_window.destroy, width=10).pack(side=tk.LEFT)
# ====================== 程序入口控制逻辑 ======================
def start_editor():
"""安装完成后启动编辑器"""
editor_root = tk.Tk()
TextEditor(editor_root)
editor_root.mainloop()
if __name__ == "__main__":
# 先启动安装窗口,传入编辑器启动回调
install_root = tk.Tk()
installer = InstallerWindow(install_root, on_finish_callback=start_editor)
install_root.mainloop()
感谢您对创新工作室的大力支持!

这里空空如也













有帮助,赞一个