Tkinter编程应知应会(23)-活用Canvas的对象tag_控件

我们继续以围棋小程序为例说明Tkinter中Canvas控件的用法。当控件的内容表示之后,一般也需要根据用户的需要进行更新。本文介绍借助Tkinter提供的对象Tag来实现管理和控制Canvas对象的方法。

首先还是动作的视频:

​视频链接​

为了方便管理程序中的数据,我们以Canvas为基类构建一个棋盘类:


 


 

class GoBoard(Canvas):
def __init__(self, master, size, span):
Canvas.__init__(self, master, height=size * span, width=size * span)
self.size = size
self.span = span
# create font
self.font1 = Font(family='Times', size=12)
self.__draw_board()
self.__create_cursor()
self.step = 0

这个类一个有4个数据成员,其中棋盘大小(路数)、线间距这两个成员由用户通过参数指定以确定棋盘的大小;另外两个font1和step用于管理数字的字体和当前棋子的步数。

除此之外,初始化函数还调用__draw_board方法描画棋盘:


 


 

def __draw_board(self):
# crate pan
self.create_rectangle(self.span / 2,
self.span / 2,
self.span * self.size - self.span / 2,
self.span * self.size - self.span / 2,
fill='#eeaa40')
# draw horizental lines
for r in range(0, self.size):
self.create_line(self.span / 2,
self.span / 2 + r * self.span,
self.span * self.size - self.span / 2,
self.span / 2 + r * self.span)
# draw vertical lines
for c in range(0, self.size):
self.create_line(self.span / 2 + c * self.span, self.span / 2,
self.span / 2 + c * self.span, self.span * self.size - self.span / 2)


 


这部分代码和前一篇文章中描画棋盘的代码相同。接着调用__create_cursor函数构建光标对象:


 


 

def __create_cursor(self):
# add go shape
side = 5
self.create_rectangle(self.__cursor_rect(int(self.size / 2), int(self.size / 2)),
tag='cursor',
fill='white')

__create_cursor方法主要是构建了一个tag为‘cursor’的矩形,接下来所有的光标操作都是通过这个‘cursor’来获得光标对象。

add_go用于在光标位置增加一个棋子,颜色由当前步数step决定。


 


 

def add_go(self):
row, col = self.get_cursor()
tag = self.__oval_tag(row, col)
if self.find_withtag(tag):
return
r = 11
if self.step % 2 == 0:
go_color = 'black'
else:
go_color = 'white'
# add go shape
self.create_oval(self.__oval_rect(row, col),
fill=go_color,
tags=['go',
'go_oval',
tag,
'step' + str(self.step)])
# move cursor to upmost
self.tag_raise('cursor', 'go')
self.step += 1

需要注意的是:在构建椭圆对象的时候,tags参数一个指定了四个tag值,第三个tag包含位置信息,而第四个tag包含步数信息。指定其中任何一个tag都可以选中这个对象。构建椭圆对象之后,通过tag_raise将光标对象移动到所有tag中包含‘go’的对象(棋子和数字)之上。

设定数字函数set_number的功能和set_go类似:


 


 

def set_number(self, row, col, number):
if number > 0:
tag = self.__oval_tag(row, col)
if self.find_withtag(tag):
if self.itemcget(tag, 'fill') == 'white':
font_color = 'black'
else:
font_color = 'white'
self.create_text(self.span / 2 + col * self.span,
self.span / 2 + row * self.span,
font=self.font1, fill=font_color,
text=str(number),
tags=['go', 'go_number', self.__font_tag(row, col)])
# move cursor to upmost
self.tag_raise('cursor', 'go')

代码中同样包含移动光标的处理。

reset_numbers首先获得当前位置棋子椭圆对象的位置tag,然后通过gettags方法获得这个对象的所有tag。参照椭圆生成部分的代码可以知道,第四个tag就是步数tag。

 

def reset_numbers(self):
row, col = self.get_cursor()
self.delete('go_number')
tags = self.gettags(self.__oval_tag(row, col))
number = 1
if tags:
start_step = int(tags[3][4:])
for s in range(start_step, self.step):
tag = 'step{}'.format(s)
if self.find_withtag(tag):
row, col = self.to_rc(self.coords(tag))
self.set_number(row, col, number)
number += 1

获得选中棋子的步数tag之后,循环生成后续的步数tag并通过find_withtag确认对应棋子存在之后再逐个生成数字对象就实现了为棋子标号的功能。

del_go首先使用光标的当前位置分别生成椭圆和数字的位置tag。如果这两个tag对应对象存在,则删除它们。


 


 

def del_go(self):
row, col = self.get_cursor()
tag = self.__oval_tag(row, col)
if self.find_withtag(tag):
self.delete(tag)
tag = self.__font_tag(row, col)
if self.find_withtag(tag):
self.delete(tag)


 


move_curosr方法获得光标的当前位置之后根据输入参数对这个对象的坐标进行调整:


 


 

def move_cursor(self, offset_r, offset_c):
row, col = self.get_cursor()
row += offset_r
col += offset_c
if 0 < col < self.size and 0 < row < self.size:
self.move('cursor', offset_c * self.span, offset_r * self.span)

而光标的当前位置又是通过光标对象的坐标计算而来的:


 


 

def get_cursor(self):
coords = self.coords('cursor')
x = (coords[0] + coords[2]) / 2
y = (coords[1] + coords[3]) / 2
return int((y - self.span / 2) / self.span), int((x - self.span / 2) / self.span)

棋盘类完成之后,用起来就简单了:

 

# create the main window
root = Tk()
go_board = GoBoard(root, 13, 40)
go_board.grid(row = 0, column = 0)
root.bind_all('<Key-Up>', lambda event:go_board.move_cursor(-1, 0))
root.bind_all('<Key-Down>', lambda event:go_board.move_cursor(1, 0))
root.bind_all('<Key-Left>', lambda event:go_board.move_cursor(0, -1))
root.bind_all('<Key-Right>', lambda event:go_board.move_cursor(0, 1))
root.bind_all('<Key-Insert>', lambda event:go_board.add_go())
root.bind_all('<Key-Delete>', lambda event:go_board.del_go())
root.bind_all('<Key-Home>', lambda event:go_board.reset_numbers())
root.mainloop()

需要强调的是:

  1. 棋盘类GoBoard中并没有准备管理Canvas对象的数据结构,而是通过规则生成对象tag并获取Canvas管理的对象。
  2. 巧妙设计对象的tag,就可以用非常小的代价实现很多功能。

 

完整代码可以从以下地址下载:

​https://github.com/xueweiguo/TkinterPrimer/blob/master/Sample/23%20Go.py​

 

觉得本文有帮助?请分享给更多人。

阅读更多更新文章,请关注微信公众号【面向对象思考】