Kivy-super()上的重复类实例

时间:2018-08-28 23:56:34

标签: python python-3.x kivy

在调用super()函数之后,它将立即创建一个重复的WidgetClass实例。

我对我使用的super()的理解是,它引用了要继承的EditImageLayout类。为此,我尝试实现了super()的不同变体,但坦白地说,我只是在这个阶段猜测。

已更新为可以正常使用,我已经剪断了几百行

运行方式>确定>选择图像>打开>裁剪(两个实例从此处开始运行)

App_R3App.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color
from kivy.properties import NumericProperty, ObjectProperty, StringProperty, ListProperty
from kivy.uix.image import Image
import io
from kivy.core.image import Image as CoreImageKivy
from kivy.uix.bubble import Bubble
from kivy.core.window import Window

__version__ = '0.1'


class FirstScreen(Screen):
    pass


class SecondScreen(Screen):
    def hl(self, image_address):
            self.new_image_address = image_address # for the sake of this
            self.callback_image(self.new_image_address, image_address, "Auto-cropped image")

    def callback_image(self, new_image_address_tmp, image_address_tmp, title):
        if new_image_address_tmp:

            third_screen = self.manager.get_screen("_third_screen_")
            new_image_address_tmp = [k.replace("\\", "/") for k in new_image_address_tmp]

            third_screen.callback_image(new_image_address_tmp[0], image_address_tmp[0], title)


class ThirdScreen(Screen, BoxLayout):
    # class attribute references
    image_size = (0, 0)
    image_pos = (0, 0)
    image_address = ""
    new_image_address = ""
    title = "Upload"

    rect_box = ObjectProperty(None)
    t_x = NumericProperty(0.0)
    t_y = NumericProperty(0.0)
    x1 = y1 = x2 = y2 = NumericProperty(0.0)

    def __init__(self, **kwargs):
        super(ThirdScreen, self).__init__(**kwargs)
        pass

    def callback_image(self, new_image_address, image_address, title):
        sm.current = "_third_screen_"

        self.new_image_address = new_image_address
        self.image_address = image_address
        self.title = title
        self.ids.main_image.source = self.new_image_address
        self.ids.main_title.text = self.title

    def enable_cropping(self):
        # overwrite class attributes
        ThirdScreen.image_address = self.image_address
        ThirdScreen.new_image_address = self.new_image_address

        print("enable_cropping")
        sm.current = "_edit_image_screen_"

        return True


class EditImageScreen(Screen):

    def __init__(self, **kwargs):
        print("EditImageScreen")
        super(EditImageScreen, self).__init__(**kwargs)
        self.layout = None

    def on_pre_enter(self):
        print("on_pre_enter")
        self.layout = EditImageLayout()
        self.add_widget(self.layout)


class EditImageLayout(FloatLayout):
    color_button = ListProperty([1, .3, .4, 1])
    button_color = ListProperty([0, 0, 0, 1])

    rectangle_selector = ObjectProperty()
    text_size_rectangle = ObjectProperty()
    image_layout = ObjectProperty()
    bubble_buttons = ObjectProperty()
    bubble_buttons_undo_confirm = ObjectProperty()

    def __init__(self, **kwargs):
        print("EditImageLayout")
        self.sm = kwargs.pop('sm', None)
        self.crop_image_screen = kwargs.pop('crop_image_screen', None)

        # This is where the problem occurs
        super(EditImageLayout, self).__init__(**kwargs)

        self.rectangle_selector.bind(size_selected=self.on_change_size_rectangle_selector)
        self.rectangle_selector.bind(size_selected_temp=self.update_text_size_rectangle)
        self.bind(on_touch_down=self.bubble_buttons.hide)

        self.bubble_buttons.resize_button.bind(on_press=self.on_press_resize_button)
        self.bubble_buttons_undo_confirm.undo_button.bind(on_press=self.on_press_undo_button)
        self.bubble_buttons_undo_confirm.confirm_button.bind(on_press=self.on_press_confirm_button)

    def on_change_size_rectangle_selector(self, instance, size_selected):
        print("on_change_size_rectangle_selector")
        if not self.rectangle_selector.tap_not_draw_a_line():
            self.bubble_buttons.show()
        else:
            self.text_size_rectangle.text = ''

    def on_press_resize_button(self, instance):
        print("on_press_resize_button")
        self.image_layout.resize_image(width=self.rectangle_selector.size_selected[0],
                                       height=self.rectangle_selector.size_selected[1])

        self.rectangle_selector.delete_line()
        self.text_size_rectangle.text = ''

        self.bubble_buttons_undo_confirm.show()

    def on_press_undo_button(self, instance):
        print("on_press_undo_button")
        size = self.image_layout.old_size
        self.image_layout.resize_image(width=size[0], height=size[1])
        self.bubble_buttons_undo_confirm.hide()

    def on_press_confirm_button(self, instance):
        print("on_press_confirm_button")
        self.bubble_buttons_undo_confirm.hide()

    def update_text_size_rectangle(self, instance, size):
        print("update_text_size_rectangle")
        self.text_size_rectangle.text = str('({0}, {1})'.format(int(size[0]), int(size[1])))


class ImageLayout(Image):
    image = ObjectProperty()
    path_image = StringProperty('image_tmp.jpg')
    path_image_tmp = StringProperty('image_tmp.jpg')

    old_size = ListProperty([0, 0])

    def __init__(self, **kwargs):
        print("ImageLayout")
        super(ImageLayout, self).__init__(**kwargs)

        self.path_image = ThirdScreen.image_address

        self.image = CoreImage(self.path_image,
                               data=io.BytesIO(open(self.path_image, "rb").read()),
                               ext=self.path_image[self.path_image.rfind('.') + 1::])
        self.source = self.path_image

    def resize_image(self, width, height, pos_x=None, pos_y=None):
        pos_x, pos_y = abs(Window.width - width)/2 , abs(Window.height - height)/2
        self.image.resize(self.path_image,
                         self.path_image_tmp,
                         int(width),
                         int(height))

        self.source = self.path_image_tmp
        self.pos = pos_x, pos_y
        self.old_size = self.size
        self.size = width, height
        self.reload()


class CoreImage(CoreImageKivy):

    def __init__(self, arg, **kwargs):
        print("CoreImage")
        super(CoreImage, self).__init__(arg, **kwargs)

    def resize(self, fname, fname_scaled, width, height):

        try:
            img = Image.open(fname)
        except Exception as e:
            print('Exception: ', e)
            return

        img = img.resize((width, height), Image.ANTIALIAS)
        try:
            img.save(fname_scaled)
        except Exception as e:
            print('Exception: ', e)
            return


class TouchSelector(Widget):
    # Points of Line object
    Ax = NumericProperty(0)
    Ay = NumericProperty(0)
    Bx = NumericProperty(0)
    By = NumericProperty(0)
    Cx = NumericProperty(0)
    Cy = NumericProperty(0)
    Dx = NumericProperty(0)
    Dy = NumericProperty(0)

    # Object line
    line = ObjectProperty()

    # List of line objects drawn
    list_lines_in_image = ListProperty([])

    # Size of the selected rectangle
    size_selected = ListProperty([0, 0])

    # Size previous of the selected rectangle
    size_selected_previous = ListProperty([0, 0])

    # Size temporary of the selected rectangle
    size_selected_temp = ListProperty([0, 0])

    # Line Color and width
    line_color = ListProperty([0.2, 1, 1, 1])
    line_width = NumericProperty(1)

    # First tap in TouchSelector
    first_tap = True

    def __init__(self, *args, **kwargs):
        super(TouchSelector, self).__init__(*args, **kwargs)
        self.bind(list_lines_in_image=self.remove_old_line)

    def on_touch_up(self, touch): # on button up
        self.size_selected = abs(self.Cx - self.Dx), abs(self.Cy - self.By)
        self.size_selected_previous = self.size_selected
        print(self.Dx, self.Dy, self.Cx, self.Cy)

    def on_touch_down(self, touch):
        with self.canvas:
            Color(self.line_color)

            # Save initial tap position
            self.Ax, self.Ay = self.first_touch_x, self.first_touch_y = touch.x, touch.y

            # Initilize positions to save
            self.Bx, self.By = 0, 0
            self.Cx, self.Cy = 0, 0
            self.Dx, self.Dy = 0, 0

            # Create initial point with touch x and y postions.
            self.line = Line(points=([self.Ax, self.Ay]), width=self.line_width, joint='miter', joint_precision=30)

            # Save the created line
            self.list_lines_in_image.append(self.line)

            print("on_touch_down")

    def remove_old_line(self, instance=None, list_lines=None):
        if len(self.list_lines_in_image) > 1:
            self.delete_line()

    def delete_line(self, pos=0):
        try:
            self.list_lines_in_image.pop(pos).points = []
        except:
            pass

    def on_touch_move(self, touch):
        # Assign the position of the touch at the point C
        self.Cx, self.Cy = touch.x, touch.y

        # There are two known points A (starting point) and C (endpoint)
        # Assign the  positions x and y  known of the points
        self.Bx, self.By = self.Cx, self.Ay
        self.Dx, self.Dy = self.Ax, self.Cy

        # Assign points positions to the last line created
        self.line.points = [self.Ax, self.Ay,
                            self.Bx, self.By,
                            self.Cx, self.Cy,
                            self.Dx, self.Dy,
                            self.Ax, self.Ay]

        self.size_selected_temp = abs(self.Cx - self.Dx), abs(self.Cy - self.By)

    def tap_not_draw_a_line(self):
        return (self.size_selected[0] == 0 and self.size_selected[1] == 0)


class BaseBubbleButtons(Bubble):

    def __init__(self, **kwargs):
        super(BaseBubbleButtons, self).__init__(**kwargs)

    def hide(self, instance=None, value=None):
        self.opacity = 0

    def show(self, instance=None, value=None):
        self.opacity = 1


class BubbleButtons(BaseBubbleButtons):
    resize_button = ObjectProperty()
    cut_button = ObjectProperty()
    rotate_button = ObjectProperty()


class BubbleButtonsUndoConfirm(BaseBubbleButtons):
    undo_button = ObjectProperty()
    confirm_button = ObjectProperty()


class App_R3App(App):
    Builder.load_file('App_R3.kv')

    def build(self):
        return sm

    def on_start(self):
        return True

    def on_pause(self):
        return True

    def on_resume(self):
        return True

    def on_stop(self):
        return True


if __name__ == '__main__':

    # Create the screen manager
    sm = ScreenManager()
    sm.add_widget(FirstScreen(name='_first_screen_'))
    sm.add_widget(SecondScreen(name='_second_screen_'))
    sm.add_widget(ThirdScreen(name='_third_screen_'))
    sm.add_widget(EditImageScreen(name='_edit_image_screen_'))

    App_R3App().run()

App_R3.kv

#:import Window kivy.core.window.Window

<MyScreenManager>:
    FirstScreen:
        id: first_screen
    SecondScreen:
        id: second_screen
    ThirdScreen:
        id: third_screen
    EditImageScreen:
        id: edit_image_screen

<FirstScreen>:
    name: '_first_screen_'
    BoxLayout:
        orientation: "horizontal"
        Label:
            id: first_screen_label
            text: "Hi, I'm the home page"
        BoxLayout:
            orientation: "vertical"
            Button:
                text: "Okay!"
                on_press: root.manager.current = '_second_screen_'
            Button:
                text: "Cancel!"
                on_press: app.stop()

<SecondScreen>:
    name: '_second_screen_'
    id: file_chooser
    BoxLayout:
        id: file_chooser_box_layout
        orientation: "horizontal"
        Button
            text: "Open"
            on_press: root.hl(file_chooser_list_view.selection)
        FileChooserListView:
            id: file_chooser_list_view

<ThirdScreen>:
    name: '_third_screen_'
    id: third_screen

    xx1: root.x1
    yy1: root.y1
    tt_x: root.t_x
    tt_y: root.t_y

    BoxLayout:
        orientation: "vertical"
        id: third_screen_boxlayout

        Label:
            id: main_title
            text: root.title
            size_hint: (1, 0.1)
        BoxLayout:
            id: image_box_layout
            # limits the box layout to the position of the image
            Image:
                id: main_image
                source: root.image_address
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
        BoxLayout:
            id: button_boxlayout
            orientation: "horizontal"
            padding: 10
            size_hint: (1, 0.15)
            Button:
                id: accept_button
                text: "Okay"
                size_hint: (0.33, 1)
                on_press: root.image_accepted_by_user(root.image_address)
            Button:
                id: crop_button
                text: "Crop"
                size_hint: (0.33, 1)
                on_press: root.enable_cropping()
            Button:
                id: cancel_button
                text: "Cancel"
                size_hint: (0.33, 1)
                on_press: root.manager.current = '_first_screen_'


<EditImageLayout>:
    rectangle_selector: rectangle_selector
    text_size_rectangle: text_size_rectangle
    image_layout: image_layout
    bubble_buttons: bubble_buttons
    bubble_buttons_undo_confirm: bubble_buttons_undo_confirm
    canvas.before:
        Color:
            rgba: (1, 1, 1, 0.2)
        Rectangle:
            pos: self.pos
            size: Window.width, Window.height

    Label:
        id: text_size_rectangle
        pos_hint_x: None
        pos_hint_y: None
        pos: Window.width*.45, Window.height*.45
        color: (1, 1, 1, 1)
    ImageLayout:
        id: image_layout
        size: Window.width*.8, Window.height*.8
        pos: Window.width*.1, Window.height*.1
        size_hint: None, None
        pos_hint_x: None
        pos_hint_y: None
    TouchSelector:
        id: rectangle_selector
    BubbleButtons:
        id: bubble_buttons
        size_hint: (None, None)
        size: (200, 40)
        pos_hint_x: None
        pos_hint_y: None
        pos: Window.width*.4, Window.height*.1
        opacity: 0
        arrow_pos: 'top_mid'
    BubbleButtonsUndoConfirm:
        id: bubble_buttons_undo_confirm
        size_hint: (None, None)
        size: (200, 40)
        pos_hint_x: None
        pos_hint_y: None
        pos: Window.width*.4, Window.height*.9
        opacity: 0
        arrow_pos: 'top_mid'

<BubbleButtons>:
    resize_button: resize_button
    cut_button: cut_button
    rotate_button: rotate_button
    BubbleButton:
        id: resize_button
        text: 'Resize'
    BubbleButton:
        id: cut_button
        text: 'Cut'
    BubbleButton:
        id: rotate_button
        text: 'Rotate'

<BubbleButtonsUndoConfirm>:
    undo_button: undo_button
    confirm_button: confirm_button
    BubbleButton:
        id: undo_button
        text: 'Undo'
    BubbleButton:
        id: confirm_button
        text: 'Confirm'

控制台输出,也就是代码输出的内容(您可以看到ImageLayout和CoreImage运行两次)

EditImageScreen
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage

我怀疑正在发生的事情是super()正在调用基类EditImageLayout,该基类的静态元素正在调用.kv文件,然后从那里初始化ImageLayout和CoreImage类。同时,“自我”开始行动并做完全相同的事情。稍后在我对其执行on_touch_down时,这会引起麻烦(on_touch_down然后出现两次,依此类推。)

Output

1 个答案:

答案 0 :(得分:1)

问题

ImageLayout和CoreImage被调用两次。

[INFO   ] [GL          ] Unpack subimage support is available
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage
[INFO   ] [Base        ] Leaving application in progress...

Process finished with exit code 0

根本原因

对ImageLayout和CoreImage的两次调用是由于存在两个kv文件App_R3.kvapp_r3.kv,如下面的屏幕快照所示。在应用程序中,应用程序类为App_R3App(App):,它使用Builder.load_file('App_R3.kv')将kv代码加载到应用程序中。

Img01 - Two kv files, App_R3.kv and app_r3.kv

解决方案

删除kv文件app_r3.kv。在示例中,我们将其重命名为app_r3-off.kv

Img02 - No duplicate calls to ImageLayout and CoreImage

Kv language » How to load KV

  

有两种方法可以将Kv代码加载到您的应用程序中:

     

按名称惯例:

     

Kivy在以下位置查找与您的App类同名的Kv文件   小写字母,如果以“ App”结尾则减去“ App”,例如:

MyApp -> my.kv
     

如果此文件定义了Root Widget,它将被附加到应用程序的   根属性,并用作应用程序小部件树的基础。

     

通过Builder:

     

您可以告诉Kivy直接加载字符串或文件。如果这个字符串   或文件定义了根窗口小部件,它将由以下方法返回:

Builder.load_file('path/to/file.kv')
     

或:

Builder.load_string(kv_string)

建议

由于类规则<MyScreenManager>:是在kv文件中定义的,因此您应该使用它,而不是在Python代码中定义sm = ScreenManager()。最重要的是,将视图/设计分开。

kv文件

EditImageScreen 添加缺少的类规则。

<EditImageScreen>:
    name: '_edit_image_screen_'

<EditImageLayout>:

Python代码

  1. MyScreenManager
  2. 添加类定义
  3. build()方法中,将return sm替换为return MyScreenManager()
  4. App_R3().run()之前,删除对sm的所有引用
  5. 由于每个屏幕默认都有一个属性manager,该属性为您提供所使用的ScreenManager的实例。在callback_image()enable_cropping()方法中,将sm.current替换为self.manager.current
  6. __init__()类的EditImageLayout()方法中,删除self.sm = kwargs.pop('sm', None)。如果您需要访问根目录/ ScreenManager,请使用sm = App.get_running_app().root

摘要

class MyScreenManager(ScreenManager):
    pass
...
    def callback_image(self, new_image_address, image_address, title):
        self.manager.current = "_third_screen_"

        self.new_image_address = new_image_address
...
    def enable_cropping(self):
        ...
        print("enable_cropping")
        self.manager.current = "_edit_image_screen_"
...
    def __init__(self, **kwargs):
        print("EditImageLayout")
        self.crop_image_screen = kwargs.pop('crop_image_screen', None)

...
class App_R3App(App):

    def build(self):
        return MyScreenManager()


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

ScreenManager » Basic Usage

  

每个屏幕默认都有一个属性manager,该属性为您提供   使用的ScreenManager实例。