抽空做的一个显示行数的文本框,结合上一次的带关闭按钮的Notebook,做个简单的框架,菜单功能未添加,有需要的拿去使用。
目前就文本框的里面相对完善可以自动同步行数,包括:选中拖动、上下左右按钮、修改内容、光标在范围外按钮。
不多说什么自己试下。
import tkinter as tk, os
from tkinter import ttk
class CustomNotebook(ttk.Notebook): # 带关闭按钮的Notebook
"""定义一个名为CustomNotebook的类,继承自ttk.Notebook"""
__initialized = False # 初始化一个私有变量,用于标记是否已经初始化
def __init__(s, *args, **kwargs):
# 如果尚未初始化,则调用自定义初始化方法,并设置已初始化标志
if not s.__initialized:
s.__initialize_custom_style()
CustomNotebook.__initialized = True
# s.__inititialized = True # 这样会导致第二次调用出错,请用上面的方法标记
# 设置notebook的样式为"CustomNotebook"
kwargs["style"] = "CustomNotebook"
# 调用父类的初始化方法
ttk.Notebook.__init__(s, *args, **kwargs)
s._active = None # 初始化一个私有变量,用于存储当前活动的tab
# 绑定鼠标左键按下事件到on_close_press方法
s.bind("" , s.on_close_press, True)
# 绑定鼠标左键释放事件到on_close_release方法
s.bind("" , s.on_close_release)
def __initialize_custom_style(s):
# 创建一个ttk样式对象
style = ttk.Style()
# 定义四个图片对象,分别表示关闭按钮的不同状态
s.images = (
# 元素普通状态时图标
tk.PhotoImage("img_closenormal", data='''
R0lGODdhCwALAIMAAJKSkpeXl5ubm5+fn6CgoKampqqqqq2trbGxsba2tr29vcHBwdnZ2QAAAAAAAAAA
ACwAAAAACwALAAAIXQAXJEBwwEDBAgUGHEigYOCBhwYMEBCAIAEDBgYQXhwg4ACCiwwKgOQIEeRGjgUK
GgBJYABKgyYZuBRAQORFmwwEBBhgE6FNAQAEDCBAtCVHoAcC6AzANAAAAAYCAgA7
'''),
# 选中的选项卡的关闭图标
tk.PhotoImage("img_closeselected", data='''
R0lGODdhCwALAIVUAJ9dcptfe6NfcpthfZ5hfJ9ifp5nfaNjda9lc6Rmea1nfqRpe6Fqfq9ofK1ofqxr
frFndbJqeLJufZ9rgKpngaBqgKtpg6tphK5uhKxvia5xhq91iq1xjK1zj692jLFyg7N1hrJ3i7B2jbF4
ja51ka94kq97l698mq9+m7B8mbB/nLCDn6aEoayMprCForGMq7KNq7KPrrOXt7WfwP///wAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAACwALAAAIcgBnwFiBwoQJEh0yUGARQ8YLFwRPHLwQ
wEUMGjQOcsB4YYAKFxhpbMRooYCJFSlCYlTgoAAJFSlMhHTgQEGBDilSqoyggACGEiFFhGxwAINQGiNC
gMAIQcADDR48bAjxQUIEBABaGJgwoQKDBQkOCGAREAA7
'''),
# 鼠标经过时选项卡的关闭图标
tk.PhotoImage("img_closeactive", data='''
R0lGODdhCwALAIVSAN5IN+lRNeBQP+5bPvtSP+FSQuJXR+VbRu5dQetfRuVbSf9dS/xeTO5iR+hhTOxl
TP1wTP1iUP1kUv9lVPxnVv9rVP1pWP9rWv9vXv5/WP54Xf9yYv11YP90ZP99YP94af97bP99bv6BW/6C
Yv6FYP+BZf6JZv+Eb/6Jav+Oa+eJe/+Bcf+Edf+Hef+KfP+Rd/+WfP+bev+Pgu+Rhv+RhP+ViP+Zjf+h
lv+lmv+1rP+/twAAAAAAAAAAAAAAACwAAAAACwALAAAIcwB13KDhgkWIEB8wRJiBIwcOGzRktFjx
wUKBGjh27GABYoNGCQZc2NC4owNJkCsIktRYoYKCDywmkizhQYODDSFWrNyRYsSDCSY1wiBJosGCCxpP
vIihMcMAAgwoXOBQAoUJERACzAAAoICAAwkaIAigIiAAOw==
'''),
# 按下关闭按钮时选项卡的关闭图标
tk.PhotoImage("img_closepressed", data='''
R0lGODdhCwALAIUAAGsiD20jEHIkEHYlEXsnEX4oEogrE4orFIwsFJEtFJUvFZoxFpwwFp4yF6EzF6Yz
F642GbU3GbY4Grk5Grk6G7s7G7w6G7w8G8A+HcJAHcVDH8ZEH8dEIMhGIMtJIsxKIs1KIpdxZ8eAbtqI
cuKdiQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAACwALAAAIcABDABBA4AACBQscNBARIAGDBxAkVKhw
oeIABiRIRJhQIeMFDAUeZCTRMWOGCwYgRBhpMgMGBBEqWBipYcOGDAoolBz5wcOGBRdGchjps0FQEhk6
eMgIgoODihgycOgAAoQHDiIsYMBwoeYGDhtGBAQAOw==
'''),
)
# 使用element_create方法创建一个名为"close"的元素,类型为"image",图像文件名为"img_closenormal"
style.element_create("close", "image", "img_closenormal",
# 当元素处于激活、按下、未禁用状态时,显示"img_closepressed"图片
("active", "pressed", "!disabled", "img_closepressed"),
# 当元素处于激活且未禁用状态时,显示"img_closeactive"图片
("active", "!disabled", "img_closeactive"),
# 当选项卡处于选中状态时,显示"img_closeselected"图片
("selected", "img_closeselected"),
# 设置元素的边框宽度为8像素,无边框;设置元素的粘性属性为空字符串,表示不粘附在其他元素上。
border=9, sticky='')
'''
notebook有如下状态
disabled 禁用状态,该状态下的控件无法接收用户输入。
normal 正常状态,该状态下的控件可以接收用户输入。
active 激活状态(鼠标经过),该状态下的控件可以接收用户输入,并且会显示特殊效果(如闪烁)。
selected 选中状态,该状态下的控件会显示特殊效果(如高亮)。
insensitive 不敏感状态,该状态下的控件不会响应用户的键盘操作。
focus 聚焦状态,该状态下的控件会显示特殊效果(如边框)。
'''
# 设置Notebook的样式为"CustomNotebook",并为"CustomNotebook.client"添加一个样式选项,设置其"sticky"属性为"nswe"
style.layout("CustomNotebook", [("CustomNotebook.client", {"sticky": "nswe"})])
# 设置CustomNotebook.Tab的布局样式
style.layout("CustomNotebook.Tab", [
("CustomNotebook.tab", { # 设置 CustomNotebook.tab 的样式
"sticky": "nswe", # 设置 tab 的粘性属性为 NSWE,表示在水平方向上可拉伸,垂直方向上可滚动
"children": [ # 设置 tab 的子元素
("CustomNotebook.padding", { # 设置 CustomNotebook.padding 的样式
"side": "top", # 设置 padding 的侧边距在顶部
"sticky": "nswe", # 设置 padding 的粘性属性为 NSWE
"children": [ # 设置 padding 的子元素
("CustomNotebook.focus", { # 设置 CustomNotebook.focus 的样式
"side": "top", # 设置 focus 的侧边距在顶部
"sticky": "nswe", # 设置 focus 的粘性属性为 NSWE
"children": [ # 设置 focus 的子元素
# 设置 CustomNotebook.label 的样式
("CustomNotebook.label", {"side": "left", "sticky": ''}), # 设置 label 的侧边距在左侧,无粘性
# 设置 CustomNotebook.close 的样式
("CustomNotebook.close", {"side": "left", "sticky": ''}), # 设置 close 的侧边距在左侧,无粘性
]
})
]
})
]
})
])
def on_close_press(s, event):
"""当按钮被按下时触发,位于关闭按钮上方"""
# 获取鼠标点击位置的元素
element = s.identify(event.x, event.y)
# 如果元素包含"close",则执行以下操作
if "close" in element:
# 获取鼠标点击位置的索引值
index = s.index("@%d,%d" % (event.x, event.y))
# 将按钮状态设置为按下
s.state(['pressed'])
# 将_active属性设置为点击的索引值
s._active = index
def on_close_release(s, event):
"""
当鼠标在关闭按钮上释放时调用此方法。
event: 包含鼠标事件信息的对象。
"""
if not s.instate(['pressed']): # 如果按钮没有按下状态,直接返回
return
try:
element = s.identify(event.x, event.y) # 获取鼠标释放位置的元素
index = s.index("@%d,%d" % (event.x, event.y)) # 获取元素在列表中的索引
if "close" in element and s._active == index: # 元素是关闭按钮,且当前激活的标签页与释放位置的标签页相同
s.event_generate("<>" ) # 生成一个表示标签页关闭的事件
#s.forget(index) # 删除该标签页,但并未销毁,要销毁需要用destroy(),也可以将删除写入NotebookTabClosed事件里面
except: pass
s.state(["!pressed"]) # 将按钮状态设置为非按下状态
s._active = None # 将当前激活的标签页设置为None
class ScrolledText(tk.Text): # 带右侧滚动条的文本框
def __init__(s, master=None, **kw):
s.frame = tk.Frame(master)
s.vbar = tk.Scrollbar(s.frame)
s.vbar.pack(side='right', fill='y')
kw.update({'yscrollcommand': s.vbar.set})
tk.Text.__init__(s, s.frame, **kw)
s.pack(side='left', fill='both', expand=True)
s.vbar['command'] = s.yview
# Copy geometry methods of s.frame without overriding Text
# methods -- hack!
text_meths = vars(tk.Text).keys()
methods = vars(tk.Pack).keys() | vars(tk.Grid).keys() | vars(tk.Place).keys()
methods = methods.difference(text_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(s, m, getattr(s.frame, m))
# 为底层控件创建一个代理
# 为变量s添加一个属性_orig,其值为s的_w属性值加上"_orig"字符串
s._orig = s._w + "_orig"
# 使用tkinter库的call方法,将s的_w属性对应的窗口重命名为_orig
s.tk.call("rename", s._w, s._orig)
# 使用tkinter库的createcommand方法, 为s的_w属性对应的窗口创建一个命令, 该命令执行s的_proxy属性对应的函数
s.tk.createcommand(s._w, s._proxy)
def __str__(s):
return str(s.frame)
def _proxy(s, command, *args):
# 避免复制时出错
if command == 'get' and (args[0] == 'sel.first' and args[1] == 'sel.last') and not s.tag_ranges('sel'): return
# 避免删除时出错
if command == 'delete' and (args[0] == 'sel.first' and args[1] == 'sel.last') and not s.tag_ranges('sel'): return
#if command not in ['index','yview','count']:
#print (command, args)
cmd = (s._orig, command) + args # 将原始对象、命令和参数组合成一个新的命令
result = s.tk.call(cmd) # 调用新命令并获取结果
if command in ("insert", "delete", "replace"): # 如果命令是插入、删除或替换操作
s.event_generate("<>" ) # 生成一个<>事件,表示文本已被修改
return result
class RowScrolledText: # 左侧带行数显示的文本框
def __init__(s, frame, master, spacing=5, font=("等线等线 (Light)", 14)):
s.root = master
s.frame = frame
# 创建一个文本框,用于输入和显示文本
s.line_text = tk.Text(s.frame, width=5, height=600, spacing3=spacing, bg="#DCDCDC", bd=0,
font=font, takefocus=0, state="disabled", cursor="arrow")
s.line_text.pack(side="left", expand="no")
frame.update() # 更新画布和文本框的显示
# 创建一个带滚动条的文本框,用于显示大文本,设置边框样式为"ridge"、"solid"、"double"、"groove"、"ridgeless"或"none"
s.ScrolledText = ScrolledText(s.frame, height=1, wrap="none", spacing3=spacing, bg="white", bd=0,
font=font, undo=True, insertwidth=1, relief="solid")
s.ScrolledText.vbar.configure(command=s.scroll)
s.ScrolledText.pack(side="right", fill="both", expand=True)
#events = s.ScrolledText.event_info()
#print(events)
# 每行插入数字,用来对比行数显示的效果
for i in range(60): s.ScrolledText.insert('end', str(i+1)+"\n")
s.line_text.bind ("" , s.wheel) # line_text鼠标滚轮事件
s.ScrolledText.bind("" , s.wheel) # ScrolledText鼠标滚轮事件
s.ScrolledText.bind("" , s.KeyPress_scroll)
s.ScrolledText.bind("" , s.KeyPress_scroll)
s.ScrolledText.bind("" , s.KeyPress_scroll)
s.ScrolledText.bind("" , s.KeyPress_scroll)
s.ScrolledText.bind("<>" , s.on_selection) # 文本选中事件
s.ScrolledText.bind("<>" , s.get_txt) # 绑定文本修改事件
s.show_line() # 显示行数
def on_selection(s,event): # 处理选中文本事件
# text = event.widget.get("sel.first", "sel.last") # 获取选中文本的内容
s.line_text.yview('moveto', s.ScrolledText.vbar.get()[0]) # 确保选中拖动导致滚动条滚动时行数显示能同步
def wheel(s, event): # 处理鼠标滚轮事件
# 根据鼠标滚轮滚动的距离,更新line_text和ScrolledText的垂直滚动位置
s.line_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
s.ScrolledText.yview_scroll(int(-1 * (event.delta / 120)), "units")
return "break"
def see_line(s, line):
s.ScrolledText.see(f"{line}.0")
s.line_text.see(f"{line}.0")
def KeyPress_scroll(s, event=None, moving = 0, row = 0):
# 光标所在行的行数和位置
line, column = map(int, s.ScrolledText.index("insert").split('.'))
# 屏幕显示范围最上面的行
first_line = int(s.ScrolledText.index("@0,0").split('.')[0])
# 屏幕显示范围最下面的行
last_line = int(s.ScrolledText.index("@0," + str(s.ScrolledText.winfo_height())).split('.')[0])
# 光标超显示范围事件,先滚动屏幕到光标能显示区域
if line <= first_line+row or line >= last_line-row:
s.see_line(line)
if row: return # show_line 转过来的到这里结束
if event.keysym == 'Up': # 按上键,在光标小于顶部能显示的下一行时激活滚动
if line <= first_line+1: moving = -1 # 这里用first_line+1,是为了防止最上面一行只露出一点的情况,下面同理
elif event.keysym == 'Down': # 按下键,在光标大于底部能显示的上一行时激活滚动
if line >= last_line-1: moving = 1
elif event.keysym == 'Left': # 按左键,在光标小于顶部能显示的下一行且光标在开头时激活滚动
if line <= first_line+1 and not column: moving = -1
elif event.keysym == 'Right': # 按右键,在光标大于底部能显示的上一行且光标在结尾时激活滚动
text = s.ScrolledText.get("1.0", "end") # 获取文本内容
cursor_line = text.split("\n")[line-1] # 获取光标所在行内容
line_length = len(cursor_line) # 光标在当前行的位置
if line >= last_line-1 and column == line_length: moving = 1
s.line_text.yview_scroll(moving, "units")
s.ScrolledText.yview_scroll(moving, "units")
def scroll(s, *xy): # 处理滚动条滚动事件
# 根据滚动条,更新line_text和ScrolledText的垂直滚动位置
s.line_text.yview(*xy)
s.ScrolledText.yview(*xy)
def get_txt(s, event=None): # 用于获取文本内容并显示
'修改内容后需要的操作都可以写在这里'
# txt = s.ScrolledText.get("1.0", "end")[:-1] # 文本框内容
s.show_line()
def show_line(s):
# 获取文本行数
text_lines = int(s.ScrolledText.index('end-1c').split('.')[0])
# 计算行数最多右几位数,调整
len_lines = len(str(text_lines))
s.line_text['width'] = len_lines + 2
# 将显示行数文本的状态设置为正常
s.line_text.configure(state="normal")
# 删除行文本中的所有内容
s.line_text.delete("1.0", "end")
# 遍历文本数组,逐行插入到行文本中
for i in range(1, text_lines + 1):
if i == 1: s.line_text.insert("end", " "*(len_lines-len(str(i))+1) + str(i))
else: s.line_text.insert("end", "\n"+" "*(len_lines-len(str(i))+1) + str(i))
s.scroll('moveto', s.ScrolledText.vbar.get()[0]) # 模拟滚动条滚动事件
s.line_text.configure(state="disabled") # 将行文本的状态设置为禁用
s.KeyPress_scroll(row = 1) # 处理光标超过显示范围事件,否则行数会不同步
class Editor: # 主窗口
def __init__(s):
super().__init__()
s.scrolledtext_list = {} # 存储scrolledtext窗口
s.root = tk.Tk()
s.save_images = (
# 原色
tk.PhotoImage('save_original', data = '''
R0lGODdhDgAOAIU9ADFjpTFjrTFjtTFrtTljrTlrrcDAwEJzvUpzvUp7vVJ7rVJ7vVqEvWOEvWOMzmuU
zmuU1nOUxnOc1nOc3nuUxnucxnul53ut54Slzoylxoyl1oyt1ozGY5StxpS13py13qWtxqW9563G57XO
773O78bW78bW987e9zlrvQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAADgAOAAAIjAAzUFhwIIXBgykYgDAQ4cQJFBAjQiTh
oEOCExdQOJA4kcSBAycsaOSIwkKJASlCkoQ4IYSAFCYsOHDwoOZMBxI+vIxpoWfPCRKC6kxRwqdPoEE9
BEhBwqiFoFA3LG26EgUEDUtHiOTAtSuHBxiybvXK1UEFAAhEiKhagUIBCg0ODBBAN4DdAAAIKAgIADs=
'''),
# 黑白
tk.PhotoImage('save_gray', data = '''
R0lGODdhDgAOAIU9AFxcXF1dXV9fX2JiYmRkZGVlZW1tbW9vb3R0dHZ2dn5+foGBgYeHh46Ojo+Pj5CQ
kJKSkpaWlpeXl6CgoKGhoaOjo6WlpaioqKmpqa2trbCwsLKysru7u8DAwMLCwsrKys3NzdTU1NXV1dzc
3P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAADgAOAAAIiwApQEhgoIDBgwUUZOjwYMQIEhAjQgTB
4AKCERZIMJA4EYQBAyMmaORIYkKIAQVCkoQogUOAAiImMGDQoOZMBhE2vIw5oWdPCRGC6iwQwqdPoEE1
ACgAwuiEoFAvLG26koSDCks/iMTAtSuGBhOybvXKlYEEAAc8eKgqAQIBCAsMDAhAF4BduwIQBAQAOw==
'''),
# 红色
tk.PhotoImage('save_red', data = '''
R0lGODdhDgAOAIU9AP/h4f++vv+3t/+2tv+vr/+srP+kpP+iov+dnf+UlP+Skv+Pj/+Li/+Kiv+Hh/+F
hf+Dg/+Cgv95ef94eP90dP9ycv9xcf9wcP9paf9jY/9gYP9YWP9WVv9RUf9PT/tHR/pGRvhERPVBQfM/
P/I+PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAADgAOAAAIiwAhUNjg4YPBgx80LDhQIUAAABAjQiSA
oQGHAA4AYJA4kYAHDwEiaOQIIMKAEB9CkoQoAcGIDwIiYMBwoeZMDBMSvIwZoWdPCROC6vwwwKdPoEEV
kPhAwGiEoFAbLG26EoCFB0sLiGTAtSuDCxGybvXKFYMEEh0MGKgqgQIIChk8hBhBl4RduyI4BAQAOw==
'''),
# 淡红色
tk.PhotoImage('save_lowred', data = '''
R0lGODdhDgAOAIU9AP/w8P/p6f/o6P/h4f/e3v/W1v/U1P/Pz//Gxv/ExP/Bwf+9vf+8vP+5uf+3
t/+1tf+0tP+rq/+qqv+mpv+kpP+jo/+iov+bm/+Vlf+Skv+Kiv+IiP+Dg/+Bgft5efp4ePh2dvVzc/Nx
cfJwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAADgAOAAAIiwAhUNjg4YPBgx80LDhQIUAAABAjQiSA
oQGHAA4AYJA4kYAHDwEiaOQIIMKAEB9CkoQoAcGIDwIiYMBwoeZMDBMSvIwZoWdPCROC6vwwwKdPoEEV
kPhAwGiEoFAbLG26EoCFB0sLiGTAtSuDCxGybvXKFYMEEh0MGKgqgQIIChk8hBhBl4RduyI4BAQAOw==
'''),
)
def close(s, event=None):
"关闭窗口。"
s.root.destroy()
os._exit(0)
def win_menu(s):
def child_command(parent, item):
for m in item.keys():
if type(item[m]) == dict:
menu = tk.Menu(parent, tearoff=0)
parent.add_cascade(label = m, menu = menu)
child_command(menu, item[m])
elif "separator" in m:
parent.add_separator()
elif m == "历史记录":
for L in item[m]:
parent.add_command(label=L, command=None)
else:
parent.add_command(label=m, command=item[m])
menu_dict = {
"文件(F)":{
"新建":None,
"打开":None,
"打开所在文件夹":None,
"使用默认查看器打开":None,
"打开文件夹作为工作区":None,
"重新读取文件":None,
"保存":None,
"另存为":None,
"全部保存":None,
"重命名":None,
"关闭":None,
"全部关闭":None,
"更多关闭方式":{
"关闭当前以外所有文件":None,
"关闭左侧所有文件" :None,
"关闭右侧所有文件" :None,
"关闭所有未修改文件":None},
"从磁盘删除":None,
"separator1" :None,
"读取会话" :None,
"保存会话" :None,
"separator2" :None,
"历史记录":['1.aaaa.py','2.bbbb.py','3.cccc.py','4.dddd.py','5.eeee.py'],
"separator3" :None,
"恢复最近关闭文件":None,
"打开文件列表":None,
"清除文件列表":None,
"separator":None,
"退出":None,},
"编辑(E)":{
"撤销":None,
"恢复":None,
"separator1":None,
"剪切":None,
"复制":None,
"粘贴":None,
"删除":None,
"全选":None,
"开始/结束 选择":None,
"separator2":None,
"复制到剪切板":{
"复制当前文件路径":None,
"复制当前文件名" :None,
"复制当前目录路径":None,},
"缩进":{
"插入制表符(缩进)":None,
"删除制表符(退格)":None},
"转换大小写":{
"转成大写":None,
"转成小写":None,
"每词转成仅首字母大写":None,
"每词的首字母转成大写":None,
"每句转成仅首字母大写":None,
"每句的首字母转成大写":None,
"大小写互换":None,
"随机大小写":None,},
"行操作":{
"复制当前行":None,
"删除连续重复行":None,
"分割行":None,
"合并行":None,
"上移当前行":None,
"下移当前行":None,
"移除空行":None,
"移除空行(包括空白字符)":None,
"在当前行上方插入空行":None,
"在当前行下方插入空行":None,
"separator1":None,
"升序排列文本行":None,
"升序排列整数":None,
"升序排列小数(逗号作为小数点)":None,
"升序排列小数(句号作为小数点)":None,
"separator2":None,
"降序排列文本行":None,
"降序排列整数":None,
"降序排列小数(逗号作为小数点)":None,
"降序排列小数(句号作为小数点)":None,},
"注释/取消注释":{
"添加/删除单行注释":None,
"设置行注释":None,
"取消行注释":None,
"区块注释":None,
"取消区块注释":None,},
"空白字符操作":{
"移除行尾空格":None,
"移除行首空格":None,
"移除行首和行尾空格":None,
"EOL转空格":None,
"移除非必须的空白和EOL":None,
"separator1":None,
"TAB转空格":None,
"空格转TAB(全部)":None,
"空格转TAB(行首)":None,},
"separator3":None,
"历史剪切板":None,
"设为只读":None,
"清除只读标记":None,},
"搜索(S)":{
"查找":None,
"在文件中查找":None,
"查找下一个":None,
"查找上一个":None,
"选定并查找下一个":None,
"选定并查找上一个":None,
"快速查找下一个":None,
"快速查找上一个":None,
"替换":None,
"增量查找":None,
"寻找结果":None,
"下一个寻找结果":None,
"上一个寻找结果":None,
"行定位":None,
"转到匹配的括号":None,
"选中所有匹配括号间字符":None,
"标记":None,
"separator1":None,
"标记所有":{
"使用格式1":None,
"使用格式2":None,
"使用格式3":None,
"使用格式4":None,
"使用格式5":None,},
"清除颜色标记":{
"清除格式1":None,
"清除格式2":None,
"清除格式3":None,
"清除格式4":None,
"清除格式5":None,
"清除所有格式":None,},
"到上一个颜色标记":{
"格式1":None,
"格式2":None,
"格式3":None,
"格式4":None,
"格式5":None,
"寻找格式":None,},
"到下一个颜色标记":{
"格式1":None,
"格式2":None,
"格式3":None,
"格式4":None,
"格式5":None,
"寻找格式":None,},
"separator2":None,
"书签":{
"设置/取消书签":None,
"上一书签":None,
"下一书签":None,
"清除所有书签":None,
"剪切书签行":None,
"复制书签行":None,
"粘贴(替换)书签行":None,
"删除书签行":None,
"删除未标记行":None,
"反向标记书签":None,},
"separator3":None,
"查找范围内字符":None,
},
"视图(V)":{
"总在最前":None,
"切换全屏模式":None,
"便签模式":None,
"separator1":None,
"将当前文件显示到":None,
"separator2":None,
"显示符号":None,
"缩放":None,
"移动/复制当前文档":None,
"标签页(Tab)":None,
"自动换行":None,
"激活从视图":None,
"隐藏行":None,
"separator3":None,
"折叠所有层次":None,
"展开所有层次":None,
"折叠当前层次":None,
"展开当前层次":None,
"折叠层次":None,
"展开层次":None,
"separator4":None,
"摘要...":None,
"separator5":None,
"工程":None,
"文件夹工作区":None,
"文档结构图":None,
"函数列表":None,
"separator6":None,
"垂直同步滚动":None,
"水平同步滚动":None,
"separator7":None,
"文字方向从右到左":None,
"文字方向从左到右":None,
"separator8":None,
"监视日志 (tail -f)":None,},
"编码(N)":{
"使用 ANSI 编码":None,
"使用 UTF-8 编码":None,
"使用 UTF-8-BOM 编码":None,
"使用 UCS-2 Big Endian 编码":None,
"使用 UCS-2 Little Endian 编码":None,
"编码字符集":None,
"separator1":None,
"转为 ANSI 编码":None,
"转为 UTF-8 编码":None,
"转为 UTF-8-BOM 编码":None,
"转为 UCS-2 Big Endian 编码":None,
"转为 UCS-2 Little Endian 编码":None,},
"设置(T)":{
"首选项...":None,
"语言格式设置...":None,
"管理快捷键...":None,
"separator1":None,
"导入":{
"导入插件":None,
"导入主题":None,},
"separator2":None,
"编辑弹出菜单":None,},
"工具(O)":{
"MD5":{
"生成...":None,
"从文件生成...":None,
"从选区生成并复制到剪切板":None,},
"SHA-256":{
"生成...":None,
"从文件生成...":None,
"从选区生成并复制到剪切板":None,},},
"运行(R)":{
"运行":None,
"管理快捷键":None,},
"插件(P)":{
"PyNpp":None,
"MIME":None,
"separator1":None,
"插件管理":None,
"separator2":None,
"打开插件文件夹":None,},
}
menubar = tk.Menu(s.root)
s.root.config(menu = menubar)
child_command(menubar,menu_dict)
def geticonimage(s, name):
"根据名称获取图标文件,生成tkImage对象返回"
try: return s.iconimages[name] # 如果存在同名图标,返回已经生成的tkImage对象
except KeyError: pass
file, ext = os.path.splitext(name) # 获取文件名和后缀
ext = ext or ".gif" # 没有后缀的以".gif"为后缀
fullname = os.path.join(icon_path, file + ext) # 连接文件路径
image = tk.PhotoImage(master=s.canvas, file=fullname) # 生成tkImage对象
s.iconimages[name] = image # 将tkImage对象缓存在s.iconimages
return image
def button_menu(s, button_frame):
canvas = tk.Canvas(button_frame, height=24, highlightthickness=0)
canvas.pack(anchor='w', fill='x')
#绘制图片,贴图(这里的贴图必须是 全局 或者和 mainloop在同一个函数下,否则会被清除导致不显示)
for i in range(16):
canvas.create_image(10 + i*25, 12, anchor='w', image = ['save_original','save_gray','save_red','save_lowred'][i%4], tags = "button")
def main(s):
# 设置窗口大小和可调整性
s.root.resizable(True, True)
s.root.geometry("900x600")
s.root.title("文本编辑器")
s.root.protocol("WM_DELETE_WINDOW", s.close)
s.root.focus_set()
s.win_menu() # 顶部菜单
button_frame = tk.Frame(s.root)
button_frame.pack(anchor='w', side='top', fill='x')
s.button_menu(button_frame)
editor_frame = tk.Frame(s.root)
editor_frame.pack(anchor='w', side='bottom', fill='x')
s.editor_tab = CustomNotebook(editor_frame)
#s.editor_tab = ttk.Notebook(editor_frame) # 创建Notebook选项卡控件
s.editor_tab.pack(expand = 1, fill = "both") # 让Notebook控件显示出来
s.editor_tab.enable_traversal() # 为s.editor_tab启用键盘快捷方式,Control-Tab向后切换,Shift-Control-Tab向前切换
s.editor_tab.bind("<>" , s.EditorTabClosed)
s.new_scrolledtext(title = 'new 1.py')
s.new_scrolledtext(title = 'new 2.py', image = 'save_gray')
s.new_scrolledtext(title = 'new 3.py', image = 'save_red')
s.new_scrolledtext(title = 'new 4.py', image = 'save_lowred')
s.root.mainloop()
def EditorTabClosed(s, event=None):
children = s.editor_tab.children # 所有标签信息,只要没有destroy,从创建开始的都可以查询到
childstr = s.editor_tab.tabs() # 所有的显示标签名称
select = s.editor_tab.select() # 选中要关闭的标签名称
index = s.editor_tab.index('current') # 选中要关闭的标签序号
tab_dict = s.editor_tab.tab(index) # 选中要关闭的标签信息
select_frame = s.editor_tab.children[ select.split('.')[-1] ] # 选中标签页的frame对象
data = {'所有标签信息': children,
'所有标签名称': childstr,
'选中标签信息': tab_dict,
'选中标签名称': select,
'选中标签序号': index ,
'选中标签对象': select_frame,
}
#print (data)
s.editor_tab.forget(index) # 删除选中标签页,但并未销毁,再一次add输入选项卡名称可以继续调用,要销毁需要用destroy(),
# select_frame.destroy() # 销毁选中标签页的frame对象
if len(childstr) == 1: s.new_scrolledtext(title = 'new 1.py') # 删除的是最后一个标签,则创建一个
def new_scrolledtext(s, path = None, title = '', image = 'save_original'):
new_tab = tk.Frame(s.editor_tab) # 添加tab选项卡
s.editor_tab.add(new_tab, text = title, image = image, compound='left')
new_Frame = tk.Frame(new_tab)
new_Frame.pack(fill="both", expand=True)# 这里需要 fill="both", expand=True 否则窗口可能无法填充满
rowscrolledtext = RowScrolledText(new_Frame, s.root)
rowscrolledtext.see_line(0)
ScrolledText = rowscrolledtext.ScrolledText
text = ScrolledText.get(0.0, 'end')
if __name__ == "__main__":
run = Editor()
run.main()