​KIVY 导师课 一个简单地绘画APP

 Tutorials » A Simple Paint App¶​

In the following tutorial, you will be guided through the creation of your first widget. This provides powerful and important knowledge when programming Kivy applications, as it lets you create completely new user interfaces with custom elements for your specific purpose.
在下面的导师课中,你将被引领通过你第一个组件的创造。 这提供了一个能力强大并且重要的知识当构造KIVY应用项目时, 当它允许你完整地创造新的用户界面同你特殊目的的自定义的元素。

Basic Considerations¶   基础考虑因素

When creating an application, you have to ask yourself three important questions:
当创造一个应用, 你不得不问你自己3个重要的问题

  • What data does my application process?  我的应用程序运行什么数据?

  • How do I visually represent that data?    我如何真实的代表那个数据?

  • How does the user interact with that data?   如何用户互动同那个数据?

If you want to write a very simple line drawing application for example, you most likely want the user to just draw on the screen with his/her fingers. That’s how the user interacts with your application. While doing so, your application would memorize the positions where the user’s finger were, so that you can later draw lines between those positions. So the points where the fingers were would be your data and the lines that you draw between them would be your visual representation.
如果你想写一个 非常简单的画线应用作为一个案例, 你最想用户只在屏幕上用他/她的指头们画画。 那是如何用户同你的应用互动的。 当这么干的时候, 你的应用可能记住用户的枝指头碰哪儿了, 以至于等会你可以画多个线在这些位置之间。 因此很多指头碰到的点是你的data数据并且你在它们之间画的线们是你真正的模型。

In Kivy, an application’s user interface is composed of Widgets. Everything that you see on the screen is somehow drawn by a widget. Often you would like to be able to reuse code that you already wrote in a different context, which is why widgets typically represent one specific instance that answers the three questions above. A widget encapsulates data, defines the user’s interaction with that data and draws its visual representation. You can build anything from simple to complex user interfaces by nesting widgets. There are many widgets built in, such as buttons, sliders and other common stuff. In many cases, however, you need a custom widget that is beyond the scope of what is shipped with Kivy (e.g. a medical visualization widget).
在Kivy中,一个应用程序的用户界面由很多组件组成。你在屏幕上看到的一切都是由一个小部件绘制的。你经常可能在不同的背景下想确认你已经写的代码, 这是为什么组件们通常代表那个一问三答的特殊的案例。 一个组件简单概括数据, 定义用户的界面同那数据, 并且绘画它可见的模型。你可以建造任何东西从简单到复杂的用户界面通过布置组件。 这有一些内置的组件, 例如按钮, sliders滑块、和其他常见的东西。 在许多案例中,然而,您需要一个自定义小部件,它超出了Kivy所提供的范围(例如 一个医用形象的组件)

So keep these three questions in mind when you design your widgets. Try to write them in a minimal and reusable manner (i.e. a widget does exactly what its supposed to do and nothing more. If you need more, write more widgets or compose other widgets of smaller widgets. We try to adhere to the Single Responsibility Principle).
因此保持这3个问题在脑海中当你设置你自己的组件。 尝试写他们在一个最小的并且可重复使用的方式(一个小部件只做它应该做的事情,仅此而已。如果你需要更多, 写更多的组件 或者 组合其他的组件的更小的组件。 我们尝试遵守Single Responsibility Principle)

Paint Widget¶ 绘画组件

We’re sure one of your childhood dreams has always been creating your own multitouch paint program. Allow us to help you achieve that. In the following sections you will successively learn how to write a program like that using Kivy. Make sure that you have read and understood Create an application. You have? Great! Let’s get started!

我们确信您的童年梦想之一一直是创建自己的多点触控绘画程序。 允许我们帮你实现它。 在下面的章节 你将成功地学习如何写一个项目像那样使用Kivy。 确信你已经阅读了 和明白Create an application。 你有吗? 很好, 让我们开始吧!

Initial Structure¶  初始架构

Let’s start by writing the very basic code structure that we need. By the way, all the different pieces of code that are used in this section are also available in the examples/guide/firstwidget directory that comes with Kivy, so you don’t need to copy & paste it all the time. Here is the basic code skeleton that we will need:
让我们开始通过编写我们需要的非常简单基础的代码结构。通过这种方式, 所有的不同片段的代码被在这节使用的都在和Kivy一起的 examples/guide/firstwidget 文件夹中适合,所以你不需要总是复制&粘贴它。 这儿是基础的代码

from kivy.app import App
from kivy.uix.widget import Widget


class MyPaintWidget(Widget):
    pass


class MyPaintApp(App):
    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

This is actually really simple. Save it as paint.py. If you run it, you should only see a black screen. As you can see, instead of using a built-in widget such as a Button (see Create an application), we are going to write our own widget to do the drawing. We do that by creating a class that inherits from Widget (line 5-6) and although that class does nothing yet, we can still treat it like a normal Kivy widget (line 11). The if __name__ ... construct (line 14) is a Python mechanism that prevents you from executing the code in the if-statement when importing from the file, i.e. if you write import paint, it won’t do something unexpected but just nicely provide the classes defined in the file.
这是真正的真的简单。 保存它作为paint.py。  如果你运行它, 你应该只能看见一个黑色屏幕。 像你看见的的一样,替代使用一个嵌套组件作为一个按钮,我们打算写我们自己的组件来画画。 我们通过创造一个类,这个类继承Widget, 并尽管那个类啥也没做, 我们仍然可以把它当作一个普通的Kivy 组件来对待。 if  __name__  结构是Python 构造, 这构造可以保护你执行代码在一个 if陈述语句里当导入这个文件。 如果你用import paint, 它不会做一些想不到的事但是只是友好的提供在文件里定义好的类。

Note

You may be wondering why you have to import App and Widget separately, instead of doing something like from kivy import *. While shorter, this would have the disadvantage of polluting your namespace and make the start of the application potentially much slower. It can also introduce ambiguity into class and variable naming, so is generally frowned upon in the Python community. The way we do it is faster and cleaner.
你可能很好奇为什么你不得不导入App 和Widget 单独地, 而不是像 from kivy import *。 简单来说,这可能有命名空间污染上的劣势, 并且使程序的一开始潜在地变慢。 它还会给类和变量命名带来歧义,因此通常在Python社区上会有所不满。 这个方式我们会做起来更快、更清洁。

Adding Behaviour¶   增加Behaviour

Let’s now add some actual behaviour to the widget, i.e. make it react to user input. Change the code like so:
现在让我们增加一些真正的行为给widget。 使他和用户的input输入相反应。 改变代码像这样:

from kivy.app import App
from kivy.uix.widget import Widget


class MyPaintWidget(Widget):
    def on_touch_down(self, touch):
        print(touch)


class MyPaintApp(App):
    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

This is just to show how easy it is to react to user input. When a MotionEvent (i.e. a touch, click, etc.) occurs, we simply print the information about the touch object to the console. You won’t see anything on the screen, but if you observe the command-line from which you are running the program, you will see a message for every touch. This also demonstrates that a widget does not have to have a visual representation.
这只是展示了反应用户的输入时多么的简单。 当一个 MotionEvent 触屏事件(也就是 一个触摸,点击等)呈现, 我们简单地打印关于这个触摸对象的信息给console控制台。 你不想在屏幕上看任何事, 但是如果你观察你正在运行项目的命令行, 你将看到每一个touch出没的message 留言。 这也示范了一个组件并不需要非得有一个真正的模型。

Now that’s not really an overwhelming user experience. Let’s add some code that actually draws something into our window:
现在那并不是一个应接不暇的用户体验。  让我们在我们的窗口中增加一些真正地绘画一些东西的代码。

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        with self.canvas:
            Color(1, 1, 0)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

../_images/guide-3.jpg

简单一问:该kivy 项目的代码中为什么使用with?

在Kivy框架中,with self.canvas: 语句用于创建一个上下文,在这个上下文中可以方便地进行绘图操作。Kivy的Canvas对象提供了一个绘图API,允许你在屏幕上绘制形状、线条、图像等。

with self.canvas: 的使用有几个重要原因:

  1. 上下文管理with 语句在Python中用于简化资源管理,如文件的打开和关闭、网络连接的建立和断开等。在Kivy中,with self.canvas: 创建了一个临时的绘图上下文,确保在该上下文内进行的所有绘图操作都应用于当前Widgetcanvas

  2. 简化代码:通过使用with语句,你可以避免反复写self.canvas. 前缀。在with块内部,你可以直接调用绘图命令(如ColorEllipse等),而Kivy会自动将这些命令应用到当前Widgetcanvas上。

  3. 分组操作:在with块内进行的所有绘图操作可以被视为一个组。这对于后续的图形管理(如清除、移动或变换)非常有用。

  4. 性能优化:虽然这不是with语句直接带来的好处,但Kivy的Canvas设计是为了高效绘图。通过在Canvas上直接进行批量绘图操作,而不是每次绘制一个形状就更新屏幕,可以提高渲染性能。

在你给出的代码中,当用户触摸屏幕时(on_touch_down事件被触发),程序会在触摸点的位置绘制一个黄色的圆形(Ellipse)。通过使用with self.canvas:,代码更加简洁且易于管理。

简而言之,with self.canvas: 在Kivy中用于创建一个临时的绘图上下文,简化绘图操作的代码,并提高代码的可读性和可维护性。

If you run your code with these modifications, you will see that every time you touch, there will be a small yellow circle drawn where you touched. How does it work?
如果你运行这段修改的代码, 你将看到每次你触摸, 那儿将是一个小黄圈儿被画在你触摸的地方。运行的怎么样?

  • Line 9: We use Python’s with statement with the widget’s Canvas object. This is like an area in which the widget can draw things to represent itself on the screen. By using the with statement with it, all successive drawing commands that are properly indented will modify this canvas. The with statement also makes sure that after our drawing, internal state can be cleaned up properly.
    第9行: 我们是用Python的with陈述  组件的Canvas对象。 这就像一个区域组件可以画一些东西来代表它们自己个在屏幕上。 通过使用with陈述, 所有连续的画画指令们都被恰当地交错,这些将会修饰这个画布。 with陈述也确保在我们画画后, 内部的状态可以
    恰当地清除。

  • Line 10: You might have guessed it already: This sets the Color for successive drawing operations to yellow (default color format is RGB, so (1, 1, 0) is yellow). This is true until another Color is set. Think of this as dipping your brushes in that color, which you can then use to draw on a canvas until you dip the brushes into another color.
    第十行:  你可能已经猜到了: 这设置了 连续画画行动是黄色(默认颜色格式是RGB, 因此(1,1,0)是黄色)。一直是黄色知道其他的Color颜色被设置。 想想这就像蘸毛刷转变成其他的颜色。

  • Line 11: We specify the diameter for the circle that we are about to draw. Using a variable for that is preferable since we need to refer to that value multiple times and we don’t want to have to change it in several places if we want the circle bigger or smaller.
    第11行: 我们特别说明了我们要画的那圈地直径diameter。 使用一个合适的变量给直径因为我们需要声明那值多次并且,我们不想不得不改变它在多个地方,如果我们像画更大或者更小地圈。

  • Line 12: To draw a circle, we simply draw an Ellipse with equal width and height. Since we want the circle to be drawn where the user touches, we pass the touch’s position to the ellipse. Note that we need to shift the ellipse by -d/2 in the x and y directions (i.e. left and downwards) because the position specifies the bottom left corner of the ellipse’s bounding box, and we want it to be centered around our touch.
    第12行: 为了画一个圈,我们简单地画了一个高度和宽度想的的椭圆Ellipse。自从我们想让圈被画在用户触碰到地地方,我们传递了 触碰位置 给这个椭圆。 注意我们需要转变这椭圆通过 直径的一半在 x和y方向上 (也就是说, 左和下降的)。 注因为位置特别说明了椭圆的中间的左边角落围绕着box那块区域, 并且我们想让它居中在我们触摸的中心。

That was easy, wasn’t it? It gets better! Update the code to look like this:
那很简单,不是吗? 它变得更好! 更新代码像这样:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        with self.canvas:
            Color(1, 1, 0)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

../_images/guide-4.jpg

This is what has changed: 这是已经改变的玩意:

  • Line 3: We now not only import the Ellipse drawing instruction, but also the Line drawing instruction. If you look at the documentation for Line, you will see that it accepts a points argument that has to be a list of 2D point coordinates, like (x1, y1, x2, y2, ..., xN, yN).
    第三行: 我们现在不仅仅导入Ellipse 绘画结构,也有线 Line画画结构。 如果你看Line的文档,你将看到它接收一个 points  变量,这变量又不得不是一个2D点坐标的列表。

  • Line 13: This is where it gets interesting. touch.ud is a Python dictionary (type <dict>) that allows us to store custom attributes for a touch.
    第13行: 这儿变得有趣起来。 touch.ud 是一个Python 字典(type <dict>),它允许我们来存储自定义属性给一个触摸。

  • Line 13: We make use of the Line instruction that we imported and set a Line up for drawing. Since this is done in on_touch_down, there will be a new line for every new touch. By creating the line inside the with block, the canvas automatically knows about the line and will draw it. We just want to modify the line later, so we store a reference to it in the touch.ud dictionary under the arbitrarily chosen but aptly named key ‘line’. We pass the line that we’re creating the initial touch position because that’s where our line will begin.
    第13行: 我们利用我们导入的Line结构并且设置一个线给画画。 自从这在on_touch_down里完成以后, 这将是一个新的线给每一个touch。 通过在with块儿里创造线,画布自动地知道线并且绘画它。我们只是想稍后修改该行,所以我们在触摸touch.ud字典中存储对该行的引用,在任意选择地并且命名恰当的键' line '下打开该字典。我们传递我们一开始触碰地位置从而创造的线因为那儿是我们线将开始的地方。

  • Lines 15: We add a new method to our widget. This is similar to the on_touch_down method, but instead of being called when a new touch occurs, this method is being called when an existing touch (for which on_touch_down was already called) moves, i.e. its position changes. Note that this is the same MotionEvent object with updated attributes. This is something we found incredibly handy and you will shortly see why.
    第15行: 我们添加了一个方法给我们的组件。 这是和on_touch_down相同的方法,但是替代当一个新的touch触摸出现时被召唤, 这方法是当一个存在的touch(就是那个已经被叫作on_touch_down的玩意儿)移动时, 也就是说 它的位置改变。 注意这是相同的也有updated attributes更新属性的 MotionEvent 平移事件 对象。 这是我们发现的不可置信、有用的些事儿,你马上就会看到为啥。
     

  • Line 16: Remember: This is the same touch object that we got in on_touch_down, so we can simply access the data we stored away in the touch.ud dictionary! To the line we set up for this touch earlier, we now add the current position of the touch as a new point. We know that we need to extend the line because this happens in on_touch_move, which is only called when the touch has moved, which is exactly why we want to update the line. Storing the line in the touch.ud makes it a whole lot easier for us as we don’t have to maintain our own touch-to-line bookkeeping.
    第16行: 记住: 这是我们在on_touch_down中获得的相同的触摸对象, 因此我们可以简单地访问我们储存在touch.ud字典中的数据。对于之前我们画的这线, 我们现在添加了目前的touch触摸位置作为一个新的point点。我们知道我们需要延申这条线因为这在on_touch_move中发生了,这只有当这触摸被移动时才被召唤,这就是为什么我们像更新这条线。 储存这线在touch.ud使对于我们来说当我们不需要保持我们自己的touch-to-line账薄。

So far so good. This isn’t exactly beautiful yet, though. It looks a bit like spaghetti bolognese. How about giving each touch its own color? Great, let’s do it:

到目前为止还好。 这并不非常好,尽管它看起来有一点像波隆那肉酱意大利面条。 给每个touch它自己个的颜色怎么样? 棒极了,咱就这么干!

from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (random(), random(), random())
        with self.canvas:
            Color(*color)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

../_images/guide-5.jpg

代码中的 Color(*color)

Color(*color) 语句是关键的一部分,用于设置接下来要绘制的图形(在这里是一个椭圆)的颜色。

具体地:

  1. color = (random(), random(), random()):这行代码生成了一个随机的 RGB 颜色。random() 函数默认返回一个 0 到 1 之间的浮点数,这正好是 Kivy 中颜色值所需的范围(对于 8 位颜色值,范围是 0-255,但在 Kivy 中通常使用 0-1 的浮点数范围)。
  2. with self.canvas::这是一个上下文管理器,用于确保在 with 语句块内进行的所有绘图操作都被添加到该控件的 canvas 上。在 Kivy 中,canvas 是一个图形绘制的上下文,你可以在上面添加各种图形元素。
  3. Color(*color):在这里,Color 是一个用于设置绘图颜色的指令。*color 是一个解包操作,它将 color 元组中的三个值(红、绿、蓝)分别作为参数传递给 Color 指令。这样,接下来的绘图操作(在这里是 Ellipse)就会使用这种颜色。

总的来说,Color(*color) 在这里的作用是设置接下来要绘制的椭圆的颜色为随机生成的 RGB 值。

Here are the changes:

  • Line 1: We import Python’s random() function that will give us random values in the range of [0., 1.).
    第一行: 我们导入了Python的 random()功能, 这功能将给我们[0., 1.)里的随机值

  • Line 10: In this case we simply create a new tuple of 3 random float values that will represent a random RGB color. Since we do this in on_touch_down, every new touch will get its own color. Don’t get confused by the use of tuples. We’re just binding the tuple to color for use as a shortcut within this method because we’re lazy.
    第10行: 在这案例中我们简单地创造了一个新的有三个随机浮点值代表随机RGB颜色的元组。 自从我们在on_touch_down里这么做了,每个新的touch将获得它自己的颜色。不要困惑元组的使用。我们只是绑定了元组和颜色来用, 作为一种捷径在这个方法内,因为我们挺懒。

  • Line 12: As before, we set the color for the canvas. Only this time we use the random values we generated and feed them to the color class using Python’s tuple unpacking syntax (since the Color class expects three individual color components instead of just 1. If we were to pass the tuple directly, that would be just 1 value being passed, regardless of the fact that the tuple itself contains 3 values).
    第12行: 像之前一样,我们设置了canvas的颜色。 只有这次我们使用产生的随机值并且把它们塞进color 类使用Python的元组解包语法(自从颜色类期望3个独立的颜色元素替代1。如果我们直接地传递元素,那将只是1值被传递,而不是那个事实元组包含3个值。)

This looks a lot nicer already! With a lot of skill and patience, you might even be able to create a nice little drawing!
这看起来已经更好了! 在一些技能和耐心中,你甚至可能创建一个小的好的画画。

Note

Since by default the Color instructions assume RGB mode and we’re feeding a tuple with three random float values to it, it might very well happen that we end up with a lot of dark or even black colors if we are unlucky. That would be bad because by default the background color is dark as well, so you wouldn’t be able to (easily) see the lines you draw. There is a nice trick to prevent this: Instead of creating a tuple with three random values, create a tuple like this: (random(), 1., 1.). Then, when passing it to the color instruction, set the mode to HSV color space: Color(*color, mode='hsv'). This way you will have a smaller number of possible colors, but the colors that you get will always be equally bright: only the hue changes.
自从默认颜色结构具有RGB 模式并且我们塞了一个3个随机浮点数值的元组给它后, 它可能是不幸地以一些灰色或者黑色结束的。那坏透了因为默认背景颜色也是黑色,因此你不能简单地看到你画的线。 这是一个挺好的把戏来阻止这现象: 替代创造一个掺和着3个随机值的元组, 创造一个元组像这:(random(), 1., 1.). 然后, 当传递它给颜色结构时, 设置给HSV颜色空间的模式: (Color(*color, mode='HSV')).  通过这种方式,你将有一个更小可能的颜色们,但是你将获得的颜色们将总是均匀的亮度: 只有颜色改变。

Bonus Points¶    额外得分

At this point, we could say we are done. The widget does what it’s supposed to do: it traces the touches and draws lines. It even draws circles at the positions where a line begins.
在这点,我们可以说我们完成了。 这组件的确支持来这么做: 它跟踪touches 并且画多条线。 它甚至从这线的开始画了一个小圈圈。

But what if the user wants to start a new drawing? With the current code, the only way to clear the window would be to restart the entire application. Luckily, we can do better. Let us add a Clear button that erases all the lines and circles that have been drawn so far. There are two options now:
但是如果用户想开始一个新的画画该如何呢?  在当前的代码中,只有一种方式来清理窗口就是重新启动整个app。 幸运的是,我们可以做的更好。 让我们增加一个Clear按钮,这按钮擦掉所有的我们画的线和圈圈。现在有2个选择:

  • We could either create the button as a child of our widget. That would imply that if you create more than one widget, every widget gets its own button. If you’re not careful, this will also allow users to draw on top of the button, which might not be what you want.
    我们要么创造按钮作为我们组件的子类。 那将意味着如果你创造了超过一个组件, 每个组件就会获得它自己的按钮。 如果你不小心, 这将也允许用户在按钮的上面画画, 这可能不是你想要的。

  • Or we set up the button only once, initially, in our app class and when it’s pressed we clear the widget.
    或者我们单次设置按钮,初始地, 在我们app类中并且当它压这个钮,我们清空了组件。

For our simple example, it doesn’t really matter that much. For larger applications you should give some thought to who does what in your app. We’ll go with the second option here so that you see how you can build up your application’s widget tree in your app class’s build() method. We’ll also change to the HSV color space (see preceding note):
为了我们简单地案例, 它并不真的关系那么多。 为了更大的应用你应该给予一些想法给谁在你app干了啥。 我们将沿着第二个选择,因此你看见你如何发展你的应用组件树在你的app类的build()方法中。

from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (random(), 1, 1)
        with self.canvas:
            Color(*color, mode='hsv')
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        parent = Widget()
        self.painter = MyPaintWidget()
        clearbtn = Button(text='Clear')
        clearbtn.bind(on_release=self.clear_canvas)
        parent.add_widget(self.painter)
        parent.add_widget(clearbtn)
        return parent

    def clear_canvas(self, obj):
        self.painter.canvas.clear()


if __name__ == '__main__':
    MyPaintApp().run()

../_images/guide-6.jpg

Here’s what happens:

  • Line 4: We added an import statement to be able to use the Button class.
    第4行:我们增加了一个import 陈述来 可以 使用Button 类

  • Line 25: We create a dummy Widget() object as a parent for both our painting widget and the button we’re about to add. This is just a poor-man’s approach to setting up a widget tree hierarchy. We could just as well use a layout or do some other fancy stuff. Again: this widget does absolutely nothing except holding the two widgets we will now add to it as children.
    第25行:  我们创造了一个  虚设的 Widget()对象作为所有我们画画组件和添加按钮的父类。 这只是一个可怜人的方法来设置一个有等级制度的组件树。我们也可以使用一个布局 或者 做些其他复杂的东西。 再说一边:  这组件的确啥也没干,除了容纳了我们马上要给它添加的2个组件作为子类。

  • Line 26: We create our MyPaintWidget() as usual, only this time we don’t return it directly but bind it to a variable name.
    第26: 我们和寻常一样,创造了我们的 MyPaintWidget(), 只是这次我们没有直接返回它而是把它绑定给了一个变量名。

  • Line 27: We create a button widget. It will have a label on it that displays the text ‘Clear’.
    第27行:我们创造了一个button组件。 它将有一个标签在它上面, 这标签展示了text'clear'

  • Line 28: We then bind the button’s on_release event (which is fired when the button is pressed and then released) to the callback function clear_canvas defined on below on Lines 33 & 34.
    第28行: 我们然后bind绑定了按钮的 on_release 事件(这事件被激发 当 这按钮被传递 然后被释放) 给 回调函数功能 clear_canvas, 这回调函数功能被定义在第33行和34行。

  • Line 29 & 30: We set up the widget hierarchy by making both the painter and the clearbtn children of the dummy parent widget. That means painter and clearbtn are now siblings in the usual computer science tree terminology.
    第29和30行: 我们设置了组件的等级制度通过使所有的虚设的父类组件painter 和清空按钮子类。 这意味着 painter 和清空组件按钮 现在是兄弟关系在平常的计算机等级树科学里。  【叨叨叨了半天就是为了说明painter这个类的实例  和 咱弄得这个clearbtn是兄弟关系,平等关系   这或许就是为什么画画的那些线啊 什么的不能画在这些按钮之上的原因吧?】

  • Line 33 & 34: Up to now, the button did nothing. It was there, visible, and you could press it, but nothing would happen. We change that here: we create a small, throw-away function that is going to be our callback function when the button is pressed. The function just clears the painter’s canvas’ contents, making it black again.
    第33行和 34行: 到现在为止, 按钮啥也没干。 它就在那, 可见的, 并且你可以按它, 但是啥事也不会发生。我们这做些改变: 我们创造一个小的,用后可扔的功能。这功能就是当按钮的被按下的时候, 是 我们的回调函数,而不是啥也没有。 这回调函数功能仅仅是清空了painter的画布内容,让它再一次变得乌黑。

Note

The Kivy Widget class, by design, is kept simple. There are no general properties such as background color and border color. Instead, the examples and documentation illustrate how to easily handle such simple things yourself, as we have done here, setting the color for the canvas, and drawing the shape. From a simple start, you can move to more elaborate customization. Higher-level built-in widgets, deriving from Widget, such as Button, do have convenience properties such as background_color, but these vary by widget. Use the API docs to see what is offered by a widget, and subclass if you need to add more functionality.
kivy组件类,被设计的, 是保持简单。 这不是通常的属性 例如 背景颜色和 边界颜色。 相替的,案例和文档  说明如何让你自己个简单地操控如此间的事情, 当我们做到这的时候, 设置画布的颜色, 并且画画图形。 从一个简单的开始, 你可以移动更多的复杂的个性化。 更高级别 嵌套的组件,起源组件。  使用API文档来看看  组件提供了些啥, 并且 是否你需要增加更多的功能子类。

Congratulations! You’ve written your first Kivy widget. Obviously this was just a quick introduction. There is much more to discover. We suggest taking a short break to let what you just learned sink in. Maybe draw some nice pictures to relax? If you feel like you’ve understood everything and are ready for more, we encourage you to read on.

恭喜你!  你已经写了第一个Kivy程序。 显然地, 这只是一个简单地介绍。 这有更多来发现的。我们建议你刚学了这么多了快歇歇吧。 或许画画一些好点的图片们来放松放松?  如果你觉的你已经明白任何事物并且准备好了继续更多, 我们鼓励你来     继续阅读。

小试牛刀:

如何在原有代码上增加线宽?

from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (random(), 1, 1)
        with self.canvas:
            Color(*color, mode='hsv')
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        parent = Widget()
        self.painter = MyPaintWidget()
        clearbtn = Button(text='Clear')
        bigbtn = Button(text='big', pos =(60,0))
        clearbtn.bind(on_release=self.clear_canvas)
        bigbtn.bind(on_release=self.big_line)
        parent.add_widget(self.painter)

        parent.add_widget(clearbtn)
        parent.add_widget(bigbtn)
        return parent

    def big_line(self, obj):
        self.painter.canvas.Line.width = 10
    def clear_canvas(self, obj):
        self.painter.canvas.clear()


if __name__ == '__main__':
    MyPaintApp().run()

在您的代码中,有几个问题需要解决以设置线宽和正确地在按钮事件处理程序中引用线宽。

首先,您尝试在big_line函数中直接设置self.painter.canvas.Line.width,但这是不正确的,因为canvas是一个绘图指令的容器,并不直接包含可以更改其属性的Line对象。此外,线宽是在创建Line对象时设置的,而不是在之后

要解决这个问题,您可以MyPaintWidget类中添加一个属性来保存当前线宽并在绘制新线时使用该线宽。然后,您可以在按钮的回调函数中更改这个线宽属性。

下面是修改后的代码示例:

from random import random  
from kivy.app import App  
from kivy.uix.widget import Widget  
from kivy.uix.button import Button  
from kivy.graphics import Color, Ellipse, Line  
  
  
class MyPaintWidget(Widget):  
    line_width = 1  # 添加一个线宽属性  
  
    def on_touch_down(self, touch):  
        color = (random(), 1, 1)  
        with self.canvas:  
            Color(*color, mode='hsv')  
            d = 30.  
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))  
            touch.ud['line'] = Line(points=(touch.x, touch.y), width=self.line_width)  # 使用线宽属性  
  
    def on_touch_move(self, touch):  
        touch.ud['line'].points += [touch.x, touch.y]  
  
  
class MyPaintApp(App):  
  
    def build(self):  
        parent = Widget()  
        self.painter = MyPaintWidget()  
        clearbtn = Button(text='Clear')  
        bigbtn = Button(text='big', pos=(60, 0))  
        clearbtn.bind(on_release=self.clear_canvas)  
        bigbtn.bind(on_release=self.set_big_line_width)  # 更改回调函数名以更清晰地表达其功能  
        parent.add_widget(self.painter)  
        parent.add_widget(clearbtn)  
        parent.add_widget(bigbtn)  
        return parent  
  
    def set_big_line_width(self, obj):  
        self.painter.line_width = 10  # 更改线宽属性  
  
    def clear_canvas(self, obj):  
        self.painter.canvas.clear()  
  
  
if __name__ == '__main__':  
    MyPaintApp().run()

现在,当您点击"big"按钮时,MyPaintWidget的线宽属性将更改为10,并在后续的绘图操作中使用这个新的线宽。

这看似的蜻蜓点水般地转折就是对象编程语言的奥妙所在, 就像赛车道上的drifting。

我又在下面的代码中这样设置代码:

    def big_line(self, obj):
        self.line_width = 10

由此可以看到这里的self是  MyPaintWidget

设置的是类的实例的属性! 

添加类的.实例后  还是报错?

原来是类中一开始就没有对line_width 进行定义

哦吼吼吼, 好兴奋哦,和想法一致!!  还要怎么对它进行开发呢!!

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinzheng新政

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值