§2.1 画布容器控件¶
字数 1775 个 代码 73 行 阅读时间 7 分钟 访问量
一、以画布为底¶
1.1 为什么以画布为底¶
maliang
和 tkinter
程序不同,tkinter
程序是直接以窗口为底板,在上面放置控件的,但由于 maliang
的控件都是虚拟的,需要以画布为底板,在上面呈现出来。所以,在显示任何 maliang
控件之前,都需要一个画布来作为它们的容器。
1.2 将画布当作页面¶
我们可以把画布当作网页的一个个页面,每个画布都独立运作,这样层次就比较清晰,多个场景的切换也比较方便。
二、画布¶
2.1 Canvas
¶
查阅文档可知,Canvas
继承自 tkinter.Canvas
,它是在 tkinter.Canvas
的基础之上“魔改”而来的,增加了很多功能。
2.1.1 初始化参数的说明¶
直接查看文档可能还是不容易理解 Canvas
的初始化参数,这里做一个详细解释。
master
: 父控件;expand
: 这个可以取的值有 4 个,但实际含义就是画布本身的缩放的规则,"x"
表示横向要缩放,"y"
表示纵向要缩放,两个都有就表示横纵都缩放,空字符串则表示不缩放;auto_zoom
: 布尔值,标志是否自动缩放画布里面的东西;keep_ratio
: 可取值有"min"
,"max"
和None
标志缩放保持比例的规则,"min"
表示跟随最小的缩放比,"max"
表示跟随最大的缩放比,None
表示缩放不保持比例;free_anchor
: 布尔值,标志画布的锚点是否为动态的;auto_update
: 布尔值,标志着画布的样式是否自动根据主题而更新
这里着重讲一下后面两个参数。
保持画布横纵比例意味着画布里面的东西永远不会变形,这对于某些场景极为重要,比如播放视频,或者小游戏的画面。有两种模式,"min"
像播放视频那样,保证画面始终在父控件之中,而不会溢出画面,而 "max"
则是保证画面一定被铺满,就算某些部分会被父控件遮挡。
free_anchor
则涉及到画布缩放,且只对 Place 布局有效。它会使锚点位置的是动态缩放的,而不是一直位于某个地方。比如锚点位于画布左上角,初始位置在 (100, 100)
,假设缩放倍率为 2,那么当 free_anchor
的值为 True
时,锚点的位置会变成 (200, 200)
,反之则没有任何变化。
auto_update
为 True
时,maliang
的样式管理机制会自动根据窗口的主题修改画布的主题,也就是说,当它的值为 True
的时候,你无法修改画布的主题,就算通过某种手段修改了,当窗口主题发生变化时,画布的主题还是会自动更新。相对应地,如果你想自定义画布的样式,如背景颜色等,你可能需要将这个参数设为 False
,此时画布的一切样式都会回归 tkinter
的默认值。当然,此时你可以自定义画布的主题:
上述代码可以让你的画布不受窗口主题变化的影响,但同样的,画布的外观细节都需要你自己设置。
2.1.2 隐藏/显示来切换页面¶
页面切换的方式有很多,你可以通过 tkinter
提供的方式,也可以通过 maliang
提供的方式。
当你使用 Place 布局时,在 tkinter
里面可以通过 place_forget
方法来将画布暂时隐藏掉。对于 Pack 和 Grid 布局也是类似的,可以使用 pack_forget
和 grid_forget
隐藏画布。但是当你想重新显示画布的时候,你必须再次使用对应的布局方法。而这个时候,你需要对当前窗口的缩放做出一定的处理。
如果窗口这个时候已经放大了,但你还是使用之前的方式去显示画布,就会导致画布看起来变小了。
下面是一个不正确的示例:
在上述程序开始运行后,用户可以快速缩放窗口,然后等待 3 秒,看看画布是否处于正确的大小。
很显然,上面的示例就会导致画布没有同步缩放。应该修改为如下代码:
- 更新画布,防止执行下一行的操作时画布的数据还没及时更新。
- 根据父控件和画布本身的数据重新调整画布的缩放。
此方法虽然可以,不过需要特别提醒的是,此方法仅对 Place 布局有效。
2.1.3 动画移动来切换页面¶
当然除了上面说的这种方式之外,还有一种比较取巧的办法。那就是在程序开始的时候就把所有的画布一次性创建好,然后通过 Place 布局的方式将它们放置在看不到的位置,需要的时候就把他们移动到可见范围内,反之就移开。正好,maliang
提供了比较好的动画机制,可以试着用这种方式解决这个问题:
上述代码在运行 1 秒后,两个画布会有移动的动画。
特别注意:可能造成性能问题
对于上述第二种方式,在画布的数量较多时缩放窗口可能会产生性能问题。因为缩放机制会对当前所有“可见”的控件进行缩放(未第一次放置或者调用了类似 *_forget
方法的画布不可见),而这里的可见是指在程序层面可见的,故这种方式可能导致缩放时产生明显卡顿。
2.2 混合 tkinter
控件¶
画布可以承载 maliang
的虚拟控件,但它本身也是 tkinter
的控件,因此也可以包含 tkinter
的控件,这是完全可以的。
不过为了兼容性考虑,推荐在 Canvas
里的 tkinter
控件使用 Place
布局方式,这样在缩放时,其缩放模式会和 maliang
的虚拟控件保持一致。使用其它的布局方式也是可以,但无法保证缩放时能得到正确的结果。
上面代码就是在 Canvas
里混合了 tkinter
控件的示例。
运行这段代码,你可能会对这个效果很熟悉。其实在前面的章节 §1.2 实现一个简单的界面 的示例代码中我们已经用 maliang
实现了类似的效果,这里只是用 tkinter.ttk
再次实现了它,但不同的是,这里是 maliang
混合 tkinter.ttk
做的,其不仅具有 tkinter.ttk
的样式,还拥有了 maliang
的功能。(1)
- 💡你可以试着缩放窗口,看看与原生的
tkinter.ttk
程序相比,有什么不同之处
2.4 嵌套画布¶
既然画布本身是 tkinter
控件,那自然画布可以嵌套在画布之中,当然,Canvas
和 Canvas
自身之间也是可以任意嵌套的。