Kivy 项目51斩百词 4

MRWord\pages\addwordpage\addword.kv

<SelectableButton>:
    canvas.before:
        Color:
            rgba: (.0, 0.9, .1, .3) if self.selected else (1, 1, 1, 0.1)
        Rectangle:
            pos: self.pos
            size: self.size
        background_color: (1, 1, 1, 0.1) if self.selected else (255, 255, 255, 1)
        color: (102, 102, 102, 1)

 Invalid data after declaration

经过调查(比对源码)发现,是缩进原因

<SelectableButton>:
    canvas.before:
        Color:
            rgba: (.0, 0.9, .1, .3) if self.selected else (1, 1, 1, 0.1)
        Rectangle:
            pos: self.pos
            size: self.size
    background_color: (1, 1, 1, 0.1) if self.selected else (255, 255, 255, 1)
    color: (102,102,102,1)
代码中,为什么background_color 和color 的缩进要往前进4个空格?否则就会报错呢?

在 Kivy 的 kv 语言中,缩进是用来定义层级结构和元素之间的关系的。Kv 语言使用缩进来决定哪些指令或属性是属于同一个元素的。

background_color 和 color 属性的缩进与前面的 Color 和 Rectangle 指令不一致,这实际上会导致一个错误,因为 Kivy 会尝试将 background_color 和 color 作为 Rectangle 指令的属性,但它们并不是 Rectangle 的有效属性。

正确的做法是将 background_color 和 color 定义为 <SelectableButton> 的属性,而不是 canvas.before 下的子元素。但是,如果你想在 canvas.before 下定义一个背景颜色或文字颜色,你通常会使用 Color 指令来设置绘图的颜色,而不是直接设置 background_color 或 color 属性。

如果 <SelectableButton> 是一个自定义的按钮类,并且你想要动态地改变它的背景颜色,你可能需要在 Python 代码中实现这个逻辑,而不是在 kv 语言中。不过,如果你只是想在 canvas.before 下绘制一个矩形来表示选择状态,你可以这样做:

<SelectableButton>:  
    canvas.before:  
        Color:  
            rgba: (.0, 0.9, .1, .3) if self.selected else (1, 1, 1, 0.1)  
        Rectangle:  
            pos: self.pos  
            size: self.size  
  
    # 其他按钮相关的属性和指令

如果你确实需要设置按钮的背景颜色,并且这个颜色是基于选择状态的,你可能需要在 Python 代码中动态改变按钮的某个属性(例如使用 background_normal 或 background_color,如果它们在你的自定义按钮类中有定义),并在 kv 文件中相应地绑定这个属性。

总之,background_color 和 color 的缩进应该与它们所属的元素(在这个例子中是 <SelectableButton> 或其某个子元素)保持一致,并且你应该根据上下文来决定它们是作为属性还是作为绘图指令的一部分。

BoxLayout

BoxLayout:
    RecycleView:
        viewclass: 'SelectableButton'
        data: [{'text': str(x)} for x in root.data_items]
        SelectableRecycleGridLayout:
            cols: 2
            default_size: None, dp(40)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
            multiselect: True
            touch_multiselect: True
  • 代码段是Kivy框架中用于创建一个可选择的RecycleView的示例。RecycleView是Kivy中的一个高效且灵活的视图,用于显示大量的数据项。下面我将解释这段代码中的每个部分:
  • BoxLayout:

    • 这是一个布局容器,它将只包含一个子元素(即RecycleView)。
    • 默认情况下,BoxLayout中的子元素会按照水平或垂直方向(取决于orientation属性)排列。但在这个例子中,因为RecycleView通常会占据整个可用空间,所以BoxLayout的具体方向可能不太重要。
  • RecycleView:

  • RecycleView — Kivy 2.3.0 documentation

    • 这是一个特殊的滚动视图,用于显示大量的数据项。
    • viewclass属性定义了每个数据项应如何显示。在这个例子中,数据项使用名为SelectableButton的自定义小部件显示。
    • data属性是一个Python列表,它定义了要显示的数据项。在这里,它使用了列表推导式来生成一个列表,其中包含与root.data_items(假设这是应用程序的某个部分定义的一个列表)中每个元素相对应的字符串。
    • 在 Kivy 中,RecycleView 是一个用于展示大量数据的滚动视图组件。与传统的列表视图(如 ListView)相比,RecycleView 采用了更高效的内存管理策略,因为它只渲染当前屏幕上可见的数据项,而不是一次性渲染所有数据项。这使得 RecycleView 非常适合用于展示大型数据集。

      下面是 RecycleView 的基本用法和组成部分:

  • 总的来说,这段代码创建了一个两列的滚动视图,其中每个数据项都是一个SelectableButton,用户可以选择多个数据项。
  1. viewclass:这是一个 Python 类或工厂函数,用于创建表示单个数据项的视图。这个类应该继承自 Kivy 的小部件类(如 BoxLayoutButton 等)。

  2. data:这是一个列表或可迭代对象,包含了要展示的数据。每个数据项都可以是一个简单的数据类型(如字符串、数字),也可以是一个包含多个字段的字典或对象。

  3. template(可选):这是一个指向 kv 模板文件的字符串,该模板定义了如何渲染单个数据项。这个选项与 viewclass 类似,但使用 kv 语言来定义视图。

  4. RecycleBoxLayout 或 RecycleGridLayout(或其他自定义布局):这些是 RecycleView 的子部件,定义了数据项的布局方式。你可以选择水平或垂直布局,或者自定义的网格布局。

下面是一个简单的 RecycleView 示例,展示了如何使用它来展示一个包含字符串的列表:

from kivy.app import App  
from kivy.lang import Builder  
from kivy.uix.recycleview import RecycleView  
from kivy.uix.recycleboxlayout import RecycleBoxLayout  
  
Builder.load_string('''  
<RV>:  
    viewclass: 'Label'  
    RecycleBoxLayout:  
        default_size: None, dp(56)  
        default_size_hint: 1, None  
        size_hint_y: None  
        height: self.minimum_height  
        orientation: 'vertical'  
''')  
  
class RV(RecycleView):  
    def __init__(self, **kwargs):  
        super(RV, self).__init__(**kwargs)  
        self.data = [{'text': str(x)} for x in range(100)]  # 示例数据  
  
class TestApp(App):  
    def build(self):  
        return RV()  
  
if __name__ == '__main__':  
    TestApp().run()

在这个示例中,我们定义了一个名为 RV 的自定义 RecycleView 类,它使用 Label 作为 viewclass 来展示数据项。我们使用 Builder.load_string 来加载 kv 模板字符串,该字符串定义了 RecycleBoxLayout 作为子部件。在 __init__ 方法中,我们设置了 data 属性为包含 100 个字符串的列表。最后,我们在 TestApp 的 build 方法中返回了这个 RV 实例。

注意:虽然在这个示例中我们使用了简单的 Label 作为视图类,但你可以替换为任何自定义的小部件类,以便根据需要展示更复杂的数据项。

  • SelectableRecycleGridLayout:   自定义类
    • 这是RecycleView中的一个布局管理器,它决定了如何排列数据项。
    • cols属性定义了布局中的列数。在这里,它被设置为2,意味着数据项将按两列排列。
    • default_size属性定义了布局中每个数据项的大小。在这里,它被设置为宽度为None(意味着它会自动调整以适应内容),高度为40dp(dp是密度无关像素)。
    • default_size_hint属性定义了当布局中没有足够空间时,数据项应该如何缩放。在这里,它被设置为(1, None),意味着数据项在水平方向上将尽可能地宽,但在垂直方向上则不会缩放。
    • size_hint_y属性设置为None,意味着布局的高度不会根据其父布局的大小自动调整。
    • height属性被设置为self.minimum_height,这意味着布局将根据其内容的大小自动调整其高度。
    • orientation属性被设置为'vertical',但在这个上下文中,它可能不是必需的,因为SelectableRecycleGridLayout默认就是垂直的。
    • multiselecttouch_multiselect属性都设置为True,这意味着用户可以选择多个数据项,并且可以通过触摸来选择它们。

kivy.uix.recyclegridlayout 的 RecycleGridLayout

RecycleGridLayout — Kivy 2.3.0 documentation

Warning   警告

This module is highly experimental, its API may change in the future and the documentation is not complete at this time.

experimental  美/ɪkˌsperɪˈment(ə)l/    adj.实验性的,试验性的

这模块是高度实验的,它的API 之后可能会变,并且这使用文档这次也不全乎。

kivy.uix.recycleview.layout 的LayoutSelectionBehavior

RecycleView Layouts — Kivy 2.3.0 documentation

LayoutSelectionBehavior是Kivy中RecycleView的一个布局行为,用于实现RecycleView中的选择功能。当您在RecycleView中选择一个项目时,该行为将自动更新其选择的状态。此外,该行为还支持单选和多选模式,并且可以通过代码控制选择状态。

具体而言,LayoutSelectionBehavior支持以下功能:

  • 单选或多选模式。
  • 通过编程方式控制选择项。
  • 获取当前选择项。
  • 清除所有选择项。

在使用LayoutSelectionBehavior时,您需要将其与RecycleView的布局一起使用。例如,如果您要使用Gridlayout作为RecycleView的布局,则可以将其与LayoutSelectionBehavior一起使用,如下所示:

from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.gridlayout import GridLayout
from kivy.properties import BooleanProperty

class SelectableGridLayout(LayoutSelectionBehavior, GridLayout):
    # 控制选择模式
    multiselect = BooleanProperty(False)

    def get_nodes(self):
        nodes = self.get_selectable_nodes()
        if self.nodes_order_reversed:
            nodes = nodes[::-1]
        if not nodes:
            return None, None
        if self.selected_nodes:
            selected = self.selected_nodes[-1]
            index = nodes.index(selected)
            return index, selected
        return None, None

    def select_node(self, node, touch=None):
        if self.multiselect:
            # 多选模式
            if node in self.selected_nodes:
                self.deselect_node(node)
            else:
                self.select_with_touch(node, touch)
        else:
            # 单选模式
            self.select_with_touch(node, touch)

    def deselect_node(self, node):
        self.selected_nodes.remove(node)
        node.unselect()

    def clear_selection(self):
        for node in self.selected_nodes:
            node.unselect()
        self.selected_nodes = []

常用方法

Kivy是一个Python编写的开源框架,用于快速开发跨平台的应用程序。其中,kivy.uix.recycleview.layout.LayoutSelectionBehavior是RecycleViewLayout的子类,提供了一些常用的方法,主要用于处理选中行为。

下面是一些LayoutSelectionBehavior常用的方法:

RecycleView Layouts — Kivy 2.3.0 documentation

The LayoutSelectionBehavior can be combined with RecycleLayoutManagerBehavior to allow its derived classes selection behaviors similarly to how CompoundSelectionBehavior can be used to add selection behaviors to normal layout. RecycleLayoutManagerBehavior manages its children differently than normal layouts or widgets so this class adapts CompoundSelectionBehavior based selection to work with RecycleLayoutManagerBehavior as well. Similarly to CompoundSelectionBehavior, one can select using the keyboard or touch, which calls select_node() or deselect_node(), or one can call these methods directly. When a item is selected or deselected apply_selection() is called. See apply_selection().

这个LayoutSelectionBehavior是基于kivy框架中的一个行为(Behavior),具体来说是CompoundSelectionBehavior的一个变种,用于与RecycleLayoutManagerBehavior结合使用。在kivy中,行为是一种机制,它允许你将某些功能或特性添加到现有的类(如布局或控件)中,而无需通过继承来修改这些类。

让我们来详细中文解说一下这个LayoutSelectionBehavior

功能概述

  • 结合使用LayoutSelectionBehavior可以与RecycleLayoutManagerBehavior结合使用,这样它的派生类就可以具有类似于CompoundSelectionBehavior为常规布局提供的选择行为。
  • 适配选择:由于RecycleLayoutManagerBehavior管理其子控件的方式与常规布局或控件不同,LayoutSelectionBehaviorCompoundSelectionBehavior的选择行为进行了适配,以便与RecycleLayoutManagerBehavior一起正常工作。
  • 选择方式:类似于CompoundSelectionBehavior,你可以通过键盘或触摸来选择节点(即控件或项目),这将会调用select_node()deselect_node()方法。此外,你也可以直接调用这些方法。
  • 选择应用:当一个项目被选中或取消选中时,会调用apply_selection()方法。这个方法允许你在项目状态改变时执行自定义的操作。

细节说明

  • select_node(node):这个方法用于选中一个特定的节点(通常是一个控件或项目)。
  • deselect_node(node):这个方法用于取消选中一个特定的节点。
  • apply_selection():这是一个可以在派生类中重写的方法。当项目被选中或取消选中时,它会被自动调用。你可以在这个方法中实现自定义的逻辑,比如更新UI、更改数据等。

总结

LayoutSelectionBehavior是一个有用的工具,它允许你在使用RecycleLayoutManagerBehavior的布局或控件中实现选择功能。通过结合这两个行为,你可以轻松地为用户提供一个可以交互地选择项目的界面,并在项目状态改变时执行自定义的操作。

1         apply_selection(indexviewis_selected)

applies the selection to the view. This is called internally when a view is displayed and it needs to be shown as selected or as not selected.

允许【选择】到视图中。apply_selection被这样在kivy内部称呼,当一个视图被显示,并且这个视图需要被显示作为 被选择 或 没被选择 的时候

It is called when select_node() or deselect_node() is called or when a view needs to be refreshed. Its function is purely to update the view to reflect the selection state. So the function may be called multiple times even if the selection state may not have changed.

apply_selection 被叫来,当select_node()  或者 deselect_node() 被召唤  或 当一个视图需要被刷新时。  它的功能是单纯地来更新视图来反映特定的选择状态。 所以这玩意的功能可被叫来使唤好多次,即使选择状态没被改变。  

If the view is a instance of RecycleDataViewBehavior, its apply_selection() method will be called every time the view needs to refresh the selection state. Otherwise, the this method is responsible for applying the selection.

如果这视图是一个RecycleDataViewBehaviord的实例, 它的apply_selection()方法将被叫唤每当这视图需要刷新选择状态的时候。另一方面,这方法是负责任的对于使用这选择。

Parameters:   元素

index: int       索引: 整数

The index of the data item that is associated with the view. 与视图相关联的数据包的索引

view: widget     视图: 组件

The widget that is the view of this data item.  这些数据包的视图就是这些组件。

is_selected: bool           波尔类型

2        deselect_node(node)

3        get_index_of_node(nodeselectable_nodes)

4        get_selectable_nodes() 获取当前选中的节点列表。

5        goto_node(keylast_nodelast_node_idx)

6        key_selection

7        select_node(node)  选中指定的节点。

# 导入python内置的SQLite驱动:
import sqlite3
import os.path
import logging
base_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(base_dir, "word.db")


def create_table():
    """创建表"""

    # 连接到SQLite数据库
    # 数据库文件是word.db
    # 如果文件不存在,会自动在当前目录创建:
    conn = sqlite3.connect(db_path)
    # 创建一个Cursor:
    cursor = conn.cursor()
    with open('db/word.sql', "r", encoding="GBK") as f_r:
        f = f_r.read()
    # 执行一条SQL语句,创建表:
    cursor.executescript(f)
    # cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
    # 继续执行一条SQL语句,插入一条记录:
    # cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
    # 通过rowcount获得插入的行数:
    row = cursor.rowcount
    print(row)
    # 关闭Cursor:
    cursor.close()
    # 提交事务:
    conn.commit()
    # 关闭Connection:
    conn.close()


def insert_data(sql):
    """修改数据"""
    conn = sqlite3.connect(db_path)
    try:
        cursor = conn.cursor()
        # 执行sql
        response = cursor.execute(sql)
        cursor.close()
        # 提交事务:
        conn.commit()
        return response
    except Exception as e:
        logging.warning('Exception:%s' % e)
    finally:
        conn.close()


def select_data(sql):
    """查询数据"""
    # cursor.execute('select * from word where id=?', ('1',))
    conn = sqlite3.connect(db_path)
    try:
        cursor = conn.cursor()
        # 执行sql
        cursor.execute(sql)
        response = cursor.fetchall()
        cursor.close()
        return response
    except Exception as e:
        logging.warning('Exception:%s' % e)
    finally:
        conn.close()


if __name__ == '__main__':
    """创建表"""
    create_table()

SQLite – Python | 菜鸟教程

touch_multiselect: True ValueError: SelectableRecycleGridLayout.orientation is set to an invalid option 'vertical'. Must be one of: ['lr-tb', 'tb-lr', 'rl-tb', 'tb-rl', 'lr-bt', 'bt-lr', 'rl-bt', 'bt-rl']

在 Kivy 的 SelectableRecycleGridLayout 中,orientation 属性并不接受 'vertical' 或 'horizontal' 这样的传统布局方向值,因为它是一个专门为 RecycleView 设计的布局,并且它定义了自己的一套方向系统。

SelectableRecycleGridLayout 的 orientation 属性决定了其子项(即你的 SelectableButton)在布局中的流动方向。这个属性接受的值是描述布局中元素流动方向的字符串,如 'lr-tb' 表示从左到右(left-to-right)然后从上到下(top-to-bottom)流动。

在你的例子中,如果你想让 SelectableRecycleGridLayout 中的按钮在两列中垂直堆叠(即每列从上到下填充),你不需要设置 orientation 属性,因为默认行为就是这样的(除非明确指定了其他方向)。你只需确保 cols 属性设置为 2,这样布局就会有两列。

因此,你可以删除 orientation: 'vertical' 这一行,或者完全忽略它,因为这不是一个有效的设置。下面是一个修正后的代码示例:

BoxLayout:  
    RecycleView:  
        viewclass: 'SelectableButton'  
        data: [{'text': str(x)} for x in root.data_items]  
        SelectableRecycleGridLayout:  
            cols: 2  
            default_size: None, dp(40)  
            default_size_hint: 1, None  
            size_hint_y: None  
            height: self.minimum_height  
            multiselect: True  
            touch_multiselect: True

在这个修正后的布局中,SelectableRecycleGridLayout 会自动处理其内部的按钮,使它们按照两列的方式排列,并且从上到下填充。如果你想要改变按钮在列之间的流动方向(比如从右到左开始填充),你可以使用像 'rl-tb' 这样的值来设置 orientation 属性,但这通常不是必要的,除非你有特定的布局需求。

word.sql

--
-- SQLiteStudio v3.4.0 生成的文件,周五 5月 17 18:24:47 2024
--
-- 所用的文本编码:UTF-8
--
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;

-- 表:known
DROP TABLE IF EXISTS known;

CREATE TABLE IF NOT EXISTS known (
    id          INTEGER  PRIMARY KEY AUTOINCREMENT,
    word_id     INTEGER  REFERENCES word (id) 
                         NOT NULL,
    add_times   INT (10) DEFAULT (0),
    create_date DATE
);

INSERT INTO known (
                      id,
                      word_id,
                      add_times,
                      create_date
                  )
                  VALUES (
                      1,
                      4,
                      0,
                      '2019-11-21'
                  );

INSERT INTO known (
                      id,
                      word_id,
                      add_times,
                      create_date
                  )
                  VALUES (
                      2,
                      3,
                      0,
                      '2019-11-21'
                  );

INSERT INTO known (
                      id,
                      word_id,
                      add_times,
                      create_date
                  )
                  VALUES (
                      3,
                      1,
                      0,
                      '2019-11-21'
                  );

INSERT INTO known (
                      id,
                      word_id,
                      add_times,
                      create_date
                  )
                  VALUES (
                      4,
                      2,
                      0,
                      '2019-11-21'
                  );


-- 表:today
DROP TABLE IF EXISTS today;

CREATE TABLE IF NOT EXISTS today (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,
    word_id INTEGER REFERENCES word (id) 
);

INSERT INTO today (
                      id,
                      word_id
                  )
                  VALUES (
                      30,
                      3
                  );


-- 表:user
DROP TABLE IF EXISTS user;

CREATE TABLE IF NOT EXISTS user (
    id               INTEGER       PRIMARY KEY AUTOINCREMENT,
    name             VARCHAR (20)  UNIQUE
                                   NOT NULL
                                   DEFAULT C0C,
    word_num         INT (4)       DEFAULT (50),
    is_month         VARCHAR (20)  DEFAULT Month,
    head_image       VARCHAR (200) NOT NULL,
    background_image VARCHAR (200) NOT NULL
);

INSERT INTO user (
                     id,
                     name,
                     word_num,
                     is_month,
                     head_image,
                     background_image
                 )
                 VALUES (
                     1,
                     'C0C',
                     50,
                     'Month',
                     'image/c0c.jpg',
                     'image/back.jpg'
                 );


-- 表:word
DROP TABLE IF EXISTS word;

CREATE TABLE IF NOT EXISTS word (
    id          INTEGER       PRIMARY KEY AUTOINCREMENT,
    word        VARCHAR (20)  NOT NULL
                              UNIQUE,
    explains    VARCHAR (20)  NOT NULL,
    phonetic    VARCHAR (20)  NOT NULL,
    examples_en VARCHAR (200),
    examples_cn VARCHAR (200),
    known_time  DATETIME,
    add_times   INT (2)       DEFAULT (0),
    create_time DATETIME      NOT NULL,
    is_delete   BOOLEAN       DEFAULT False
                              NOT NULL
);

INSERT INTO word (
                     id,
                     word,
                     explains,
                     phonetic,
                     examples_en,
                     examples_cn,
                     known_time,
                     add_times,
                     create_time,
                     is_delete
                 )
                 VALUES (
                     1,
                     'word',
                     'n. 
',
                     '[wɜːd]',
                     ' The words stood out clearly on the page. ',
                     '这些单词清晰地呈现在页面上。',
                     NULL,
                     2,
                     '2019-11-19 16:47:43',
                     0
                 );

INSERT INTO word (
                     id,
                     word,
                     explains,
                     phonetic,
                     examples_en,
                     examples_cn,
                     known_time,
                     add_times,
                     create_time,
                     is_delete
                 )
                 VALUES (
                     2,
                     'good',
                     'adj. 好的;优良的;愉快的;
n. 好处;善行;慷慨的行为
adv. 好
n. (Good)人名;(英)古德;(瑞典)戈德


',
                     '[ɡʊd]',
                     ' We had a really good time together. ',
                     '我们一起度过了非常愉快的时光。',
                     NULL,
                     2,
                     '2019-11-19 16:47:44',
                     0
                 );

INSERT INTO word (
                     id,
                     word,
                     explains,
                     phonetic,
                     examples_en,
                     examples_cn,
                     known_time,
                     add_times,
                     create_time,
                     is_delete
                 )
                 VALUES (
                     3,
                     'well',
                     'adv. 很好地;充分地;满意地;
adj. 良好的;健康的;适宜的
n. 井;源泉
v. 涌出

n. (Well)人名;(英、德、荷)韦尔


',
                     '[wel]',
                     ' Well, it''s a pleasure to meet you. ',
                     '哦,很高兴见到你。',
                     NULL,
                     2,
                     '2019-11-19 16:47:45',
                     0
                 );

INSERT INTO word (
                     id,
                     word,
                     explains,
                     phonetic,
                     examples_en,
                     examples_cn,
                     known_time,
                     add_times,
                     create_time,
                     is_delete
                 )
                 VALUES (
                     4,
                     'mail',
                     'n. 邮件;邮政,邮递;盔甲
vt. 邮寄;给…穿盔甲
vi. 邮寄;寄出
n. (Mail)人名;(法)马伊


',
                     '[meɪl]',
                     ' Your cheque is in the mail. ',
                     '你的支票在邮寄中。',
                     NULL,
                     2,
                     '2019-11-19 16:47:46',
                     0
                 );


COMMIT TRANSACTION;
PRAGMA foreign_keys = on;

这段SQL代码执行了一系列数据库操作,包括删除旧表、创建新表,以及向其中一个表插入数据。以下是详细解释:

  1. PRAGMA foreign_keys = off;

    • 这行代码禁用了外键约束。外键约束用于确保在关系型数据库中引用完整性。但在某些数据库操作中,为了简化或加速操作,可能会暂时禁用它们。
  2. BEGIN TRANSACTION;

    • 这行代码开始一个新的事务。事务是一系列数据库操作的逻辑单元,它们要么全部执行,要么全部不执行,以确保数据的一致性。
  3. DROP TABLE ...;

    • 这几行代码删除了名为knowntodayword的旧表(如果存在的话)。
  4. CREATE TABLE ...;

    • 接着,这些代码创建了新的knowntodayword表,并定义了它们的结构。
    • known表包含与word表相关的单词ID、添加次数和创建日期。
    • today表只包含与word表相关的单词ID。
    • word表包含单词本身、解释、音标、英文和中文示例、已知时间、添加次数、创建时间以及一个表示是否删除的布尔字段。
    • 注意:在knownword表的add_times字段中,你分别指定了(10)(2)作为整数字段的大小。但在SQLite中,整数的大小(INT)是固定的,所以这些括号内的数字实际上是多余的。
  5. DROP TABLE user;

    • 这行代码删除了user表(如果存在的话)。
  6. CREATE TABLE user ...;

    • 接着,这行代码创建了一个新的user表,并定义了其结构。这个表包含用户ID、用户名、单词数量、月份(尽管这里将其定义为VARCHAR而不是日期或枚举类型可能不是最佳选择)、头像和背景图片的URL。
  7. INSERT INTO user ...;

    • user表创建后,这行代码向其中插入了一行数据,代表一个名为'C0C'的用户。
  8. COMMIT TRANSACTION;

    • 这行代码提交了事务,确保之前所有的数据库操作都被永久保存。
  9. PRAGMA foreign_keys = on;

    • 最后,这行代码重新启用了外键约束。

注意

  • 在实际部署到生产环境之前,请确保你有完整的数据库备份,并且已经充分测试了这些更改。
  • 确保word表在knowntoday表之前被创建,因为这两个表都引用了word表的id字段作为外键。
  • 在SQLite中,BOOLEAN类型实际上会被存储为整数(0表示False,1表示True)。但这里你使用了False作为默认值,这在SQLite中可能不被直接支持。你可能需要将其更改为0或其他合适的整数值。
  • is_month字段被定义为VARCHAR(20),但如果你只打算存储月份信息,考虑使用更合适的类型,如TEXTENUM(如果数据库支持的话)。
  • 确保image/c0c.jpgimage/back.jpg这两个图片文件在数据库可以访问的位置上真实存在。
known、today和word的旧表 如果不存在, 报错怎么规避?

在SQL中,如果你尝试删除一个不存在的表,大多数数据库系统(包括SQLite)会抛出一个错误。但是,你可以使用条件逻辑来检查表是否存在,然后只在表存在时才尝试删除它。然而,SQLite没有直接的SQL命令来检查表是否存在,但你可以使用PRAGMA table_info(table_name);来间接地检查,或者捕获错误并忽略它。

在SQLite中,一种常见的做法是使用DROP TABLE IF EXISTS语句,这样如果表不存在,它就不会报错,而是静默地失败。

SQL代码可以修改为:

PRAGMA foreign_keys = off;  
BEGIN TRANSACTION;  
  
-- Drop known table if it exists  
DROP TABLE IF EXISTS known;  
CREATE TABLE known (id INTEGER PRIMARY KEY AUTOINCREMENT, word_id INTEGER REFERENCES word (id) NOT NULL, add_times INTEGER DEFAULT 0, create_date DATE);  
  
-- Drop today table if it exists  
DROP TABLE IF EXISTS today;  
CREATE TABLE today (id INTEGER PRIMARY KEY AUTOINCREMENT, word_id INTEGER REFERENCES word (id));  
  
-- Drop word table if it exists  
DROP TABLE IF EXISTS word;  
CREATE TABLE word (id INTEGER PRIMARY KEY AUTOINCREMENT, word VARCHAR (20) NOT NULL UNIQUE, explains VARCHAR (20) NOT NULL, phonetic VARCHAR (20) NOT NULL, examples_en VARCHAR (200), examples_cn VARCHAR (200), known_time DATETIME, add_times INTEGER DEFAULT 0, create_time DATETIME NOT NULL, is_delete BOOLEAN DEFAULT 0 NOT NULL);  
  
-- ... (The rest of your SQL code)  
  
COMMIT TRANSACTION;  
PRAGMA foreign_keys = on;

请注意,对add_timesis_delete字段的默认值进行了修改,以符合SQLite的语法。在SQLite中,BOOLEAN类型实际上是以整数形式存储的(0表示FALSE,1表示TRUE),所以我将is_delete的默认值设置为0。

这样,即使这些表在数据库中不存在,DROP TABLE IF EXISTS语句也会静默地失败,并且不会阻止后续的CREATE TABLE语句执行。

在使用SQLiteStudio 编辑数据库添加数据地过程中 提示attempt to write a readonly database

发现是 没有使用右键的 管理员运行 SQLiteStudio 所致。


MRWord\pages\addwordpage\addword.kv

class TextInputPopup(Popup):
    obj = ObjectProperty(None)
    obj_text = StringProperty("")

    def __init__(self, obj, **kwargs):
        super(TextInputPopup, self).__init__(**kwargs)
        self.obj = obj
        self.obj_text = obj.text
        self.insert_today()

    def insert_today(self):
        """today表添加数据"""
        global word_id
        try:
            word_id = int(self.obj_text)
        except ValueError:
            select_id = "SELECT id FROM word WHERE word = '%s'" % self.obj_text
            rows = select_data(select_id)
            for row in rows:
                for col in row:
                    word_id = col
        finally:
            # 插入数据并更新word表添加次数
            insert_sql = "INSERT INTO today(word_id) SELECT '%d' WHERE NOT EXISTS(SELECT 1 FROM today WHERE word_id = '%d')" % (
            word_id, word_id)
            update_sql = "UPDATE word SET add_times = add_times + 1 WHERE id = '%d'" % word_id
            insert_data(insert_sql)
            insert_data(update_sql)
            print('%s is successful to add' % self.obj_text)

这个TextInputPopup类继承自Popup(很可能来自某个UI框架,比如Kivy),用于创建一个弹出窗口来接收用户的文本输入,并基于这个输入进行一系列数据库操作。下面是对代码的详细解释和几个潜在问题的指出:

  1. 属性定义
    • obj 是一个ObjectProperty,预期用于存储一个对象。
    • obj_text 是一个StringProperty,用于存储从obj中获取的文本。
  2. __init__ 方法
    • 当创建TextInputPopup实例时,会调用这个方法。
    • 通过super()调用父类Popup__init__方法。
    • 设置objobj_text的值。
    • 调用insert_today()方法。
  3. insert_today 方法
    • 这个方法试图将obj_text的内容插入到某个数据库表的word_id字段(看起来是通过间接方式)。
    • 首先,它尝试将obj_text转换为整数,这暗示word_id可能是整数类型。
    • 如果转换失败(即obj_text不是数字),它会执行一个SQL查询来从word表中查找与obj_text匹配的id
    • 然后,它尝试在today表中插入这个word_id(仅当该word_id尚未存在于today表中时)。
    • 最后,它更新word表中的add_times字段,表示这个单词被添加了一次。

潜在问题

  • 全局变量word_id是一个全局变量,这可能会导致难以追踪的bug,特别是在多线程或复杂的应用程序中。
  • SQL注入:直接使用字符串格式化来构建SQL查询(例如"%s")是不安全的,因为它容易受到SQL注入攻击。应该使用参数化查询或ORM(对象关系映射)来避免这个问题。
  • 异常处理:虽然使用了try-except来处理可能的ValueError,但select_datainsert_data函数的调用可能会引发其他异常,而这些异常并没有被捕获。
  • 插入逻辑INSERT INTO ... WHERE NOT EXISTS 的SQL语法可能是不正确的。通常,你会使用INSERT IGNORE(如果数据库支持)或INSERT ... ON DUPLICATE KEY UPDATE来处理这种情况。
  • 数据库连接:代码中没有显示数据库连接的管理。确保在应用程序的不同部分正确管理数据库连接(例如,使用上下文管理器或确保在不再需要时关闭连接)。
  • 线程安全:如果这个类在多线程环境中使用,并且word_id是共享的,那么可能会出现问题。
  • 更新逻辑UPDATE语句会无条件地增加add_times,即使INSERT没有成功(例如,因为word_id已经存在于today表中)。

建议

  • 避免使用全局变量。
  • 使用参数化查询或ORM来防止SQL注入。
  • 捕获并适当处理所有可能的异常。
  • 检查并修复SQL语法和逻辑。
  • 确保数据库连接被正确管理。
  • 如果在多线程环境中使用,请确保线程安全。

UI框架

您提到的Popup类通常与某种用户界面(UI)框架相关联,特别是当涉及到移动应用开发或桌面应用开发时。不同的编程语言和平台有不同的UI框架,这些框架提供了创建和管理用户界面元素(如窗口、按钮、文本框等)的工具和类。

以下是一些流行的UI框架及其对应的主要编程语言和平台:

  1. Kivy (Python):
    • Kivy 是一个开源的 Python 库,用于开发多触摸应用。它允许你使用 Python 和 Kivy 语言来创建应用,这些应用可以在 Linux/OS X/Windows/Android/iOS 等平台上运行。
    • Popup 是 Kivy 框架中的一个类,用于创建弹出窗口。
  2. React Native (JavaScript/React):
    • React Native 是 Facebook 开发的一个 JavaScript 框架,用于构建原生移动应用。它结合了 React 的声明式 UI 编程方式和原生平台的能力。
    • React Native 没有直接提供 Popup 类,但开发者可以使用 Modal、Dialog 或其他组件来实现弹出窗口的效果。
  3. Flutter (Dart):
    • Flutter 是 Google 开发的一个开源移动 UI 框架,用于构建高性能的原生界面。它使用 Dart 语言编写,支持 Android 和 iOS 平台。
    • 在 Flutter 中,可以使用 showDialog 方法或 AlertDialog 类来创建弹出窗口。
  4. Swing (Java):
    • Swing 是 Java 平台的一个老牌的 UI 框架,用于构建桌面应用。它提供了丰富的 GUI 组件和布局管理器。
    • Swing 没有直接提供 Popup 类,但可以使用 JOptionPane 或自定义的 JWindow 来实现弹出窗口。
  5. Qt (C++):
    • Qt 是一个跨平台的 C++ GUI 应用开发框架。它提供了丰富的库和工具,用于开发桌面、嵌入式和移动应用。
    • 在 Qt 中,可以使用 QMessageBoxQDialog 或自定义的窗口类来实现弹出窗口。
  6. Xamarin (C#):
    • Xamarin 是一个跨平台的移动应用开发框架,允许开发者使用 C# 和 .NET 技术栈来构建 Android、iOS 和 macOS 应用。
    • Xamarin.Forms 提供了 DisplayAlert 方法用于显示简单的弹出警告,而 Popup 或更复杂的弹出窗口可能需要自定义控件或第三方库。
  7. Electron (JavaScript/HTML/CSS):
    • Electron 是一个使用 Web 技术(HTML、CSS 和 JavaScript)构建跨平台桌面应用的框架。
    • 在 Electron 应用中,可以使用 HTML 的 <dialog> 元素或 JavaScript 库(如 SweetAlert2)来实现弹出窗口。

这些框架中的每一个都提供了用于创建和管理用户界面元素的类和工具,而 Popup(或其类似的概念)通常用于实现需要用户额外输入或确认的临时界面元素。

ObjectProperty()

ObjectProperty 是 Kivy 框架中用于在 Kivy 应用程序中声明属性的一种方式,它使得属性可以在 Kivy 的事件系统中进行绑定和通知。ObjectProperty 通常在 Kivy 的小部件(Widgets)或应用程序的其他部分中用于声明属性,以便在属性更改时触发事件或通知其他对象。

使用方法

1         导入
首先,你需要从 Kivy 的 properties 模块中导入 ObjectProperty

from kivy.properties import ObjectProperty

2         声明属性
在 Kivy 的类(通常是 Widget 或其子类)中,你可以使用 ObjectProperty 来声明一个属性。这个属性可以是任何对象,但通常用于存储对另一个对象的引用。

class MyWidget(Widget):  
    my_property = ObjectProperty(None, allownone=True)  
  
    def __init__(self, **kwargs):  
        super(MyWidget, self).__init__(**kwargs)  
        self.my_property = SomeOtherObject()  # 假设 SomeOtherObject 是另一个类

在上面的例子中,my_property 是一个 ObjectProperty,初始值被设置为 Noneallownone=True 允许这个属性被设置为 None
3.         绑定事件
你可以使用 bind 方法来监听 ObjectProperty 的变化。当属性的值发生变化时,绑定的函数将被调用。

def on_my_property_change(instance, value):  
    print(f"my_property has changed to {value}")  
  
widget = MyWidget()  
widget.bind(my_property=on_my_property_change)  
  
# 更改属性将触发事件  
widget.my_property = "New Value"  # 这将输出 "my_property has changed to New Value"

4        在 kv 文件中使用
在 Kivy 的 kv 语言文件中,你也可以使用 ObjectProperty。但更常见的是,你会在 kv 文件中引用在 Python 代码中定义的属性。

例如,假设你在 Python 代码中有一个类 MyApp,它有一个 root 属性,该属性是一个 MyWidget 实例:

class MyApp(App):  
    def build(self):  
        self.root = MyWidget()  
        return self.root

在 kv 文件中,你可以这样引用 root 属性:

#:kivy 1.0.9  
  
<MyWidget>:  
    Label:  
        text: app.root.my_property  # 假设你想显示 my_property 的值

但请注意,直接在 kv 文件中声明 ObjectProperty 是不常见的,因为 kv 语言主要用于描述小部件的布局和样式,而不是定义它们的属性或行为。
5. 注意事项
* 确保在初始化对象之前不要访问 ObjectProperty,因为它们的值在初始化之前可能是 None
ObjectProperty 主要是为了与 Kivy 的事件系统一起使用,以便在属性更改时触发事件。如果你只是需要一个简单的属性,而不需要这些事件功能,那么可以使用 Python 的普通属性。

StringProperty()

在 Kivy 框架中,StringProperty 是一个用于定义字符串类型属性的类,它属于 Kivy 的 Property 类体系。这个类使得你能够定义可以在 Kivy 应用程序中动态更改并触发界面更新的属性。

StringProperty 的主要用途是在一个 Kivy 应用程序中定义一个字符串类型的属性,然后你可以通过绑定 (binding) 来监听这个属性的变化,并在其变化时执行特定的操作,如更新界面元素。

下面是一个简单的例子来说明如何使用 StringProperty

from kivy.app import App  
from kivy.uix.label import Label  
from kivy.properties import StringProperty  
  
class MyWidget(Label):  
    text_prop = StringProperty('Hello, World!')  
  
    def __init__(self, **kwargs):  
        super(MyWidget, self).__init__(**kwargs)  
        # 你可以在这里设置 text_prop 的初始值,但通常最好在类定义中设置  
        # self.text_prop = 'Hello, World!'  # 这行通常是不必要的  
  
    def on_text_prop(self, instance, value):  
        # 当 text_prop 发生变化时,这个函数会被调用  
        # 但注意,对于 Label 的 text 属性,Kivy 已经内置了这样的机制  
        # 所以这个示例主要是为了演示 StringProperty 的使用  
        print(f"Text property changed to: {value}")  
  
class TestApp(App):  
    def build(self):  
        w = MyWidget()  
        # 你可以通过实例来更改 text_prop 的值  
        w.text_prop = 'Hello, Kivy!'  
        # 这会触发 on_text_prop 方法的调用,并打印出新的值  
        return w  
  
if __name__ == '__main__':  
    TestApp().run()

然而,在上面的例子中,我们实际上并没有真正利用到 StringProperty 的优势,因为我们只是更改了一个 Label 的内部属性,而这个属性(text)在 Kivy 的 Label 类中已经被正确地处理为一个可以动态更改的属性了。

但是,如果你正在定义一个自定义的类,并且你想要在这个类中有一个可以动态更改并触发特定操作的字符串属性,那么 StringProperty 就会非常有用。

没有使用bind函数将StringProperty与def on_text_prop(self, instance, value): 函数绑定,那么该函数是如何实现动态检测数据变化的呢?

在 Kivy 中,当你为类的属性定义一个 kivy.properties.Property(如 StringPropertyNumericPropertyBooleanProperty 等)时,Kivy 的属性系统会自动处理该属性的变化通知。这意味着,当你更改该属性的值时,Kivy 会自动触发一个以 on_<property_name> 命名的方法(如果该方法存在的话)。

在你的例子中,你定义了 MyWidget 类,该类继承自 Label,并添加了一个 StringProperty 名为 text_prop。同时,你也定义了一个 on_text_prop 方法。当你更改 text_prop 的值时(例如 w.text_prop = 'Hello, Kivy!'),Kivy 的属性系统会自动调用 on_text_prop 方法,并将变化的实例(在这个例子中是 self,即 MyWidget 的实例)和新值作为参数传递。

这种机制是通过 Kivy 的属性系统实现的,而不是通过显式调用 bind 函数。Kivy 的属性系统会在设置属性值时检查是否有相应的 on_<property_name> 方法,并在需要时自动调用它。

需要注意的是,虽然这个例子使用了 StringProperty 并定义了 on_text_prop 方法来演示属性变化的处理,但在实际情况下,如果你只是想改变 Label 的文本,通常应该直接使用 Label 的 text 属性,而不是定义一个额外的 StringPropertyLabel 的 text 属性已经是一个 StringProperty,并且 Kivy 提供了内置的机制来处理其变化。

总结一下,你的 on_text_prop 方法之所以能够被自动调用,是因为 Kivy 的属性系统会在属性值变化时检查并调用相应的 on_<property_name> 方法。这种机制是自动的,不需要显式使用 bind 函数。

MRWord\pages\addwordpage\addword.py

class TextInputPopup(Popup):
    obj = ObjectProperty(None)
    obj_text = StringProperty("")

    def __init__(self, obj, **kwargs):
        super(TextInputPopup, self).__init__(**kwargs)
        self.obj = obj
        self.obj_text = obj.text
        self.insert_today()

    def insert_today(self):
        """today表添加数据"""
        global word_id
        try:
            word_id = int(self.obj_text)
        except ValueError:
            select_id = "SELECT id FROM word WHERE word = '%s'" % self.obj_text
            rows = select_data(select_id)
            for row in rows:
                for col in row:
                    word_id = col
        finally:
            # 插入数据并更新word表添加次数
            insert_sql = "INSERT INTO today(word_id) SELECT '%d' WHERE NOT EXISTS(SELECT 1 FROM today WHERE word_id = '%d')" % (
            word_id, word_id)
            update_sql = "UPDATE word SET add_times = add_times + 1 WHERE id = '%d'" % word_id
            insert_data(insert_sql)
            insert_data(update_sql)
            print('%s is successful to add' % self.obj_text)

在您提供的 TextInputPopup 类中,有几个问题和潜在的风险点需要注意。以下是对您的代码的分析和改进建议:

  1. 避免使用全局变量:在 insert_today 方法中,您使用了全局变量 word_id。这可能会导致难以追踪的副作用,特别是在多线程或多实例应用中。建议使用局部变量或将这些值作为方法参数传递。
  2. SQL 注入风险:您的 SQL 查询中存在 SQL 注入的风险。使用字符串格式化来构建 SQL 查询是不安全的,因为它允许恶意用户插入自己的 SQL 代码。应该使用参数化查询来避免这种风险。
  3. 异常处理:虽然您尝试捕获 ValueError,但您没有捕获 SQL 查询可能引发的其他异常,如 sqlite3.Error
  4. SQL 查询效率:您首先尝试将 obj_text 转换为整数,如果失败则查询数据库。但如果您知道该值可能不是整数,那么可能首先进行数据库查询会更有效。
  5. 代码可读性:使用更描述性的变量名可以提高代码的可读性。

以下是对您代码的一些改进:

from kivy.uix.popup import Popup  
from kivy.properties import ObjectProperty, StringProperty  
  
class TextInputPopup(Popup):  
    obj = ObjectProperty(None)  
    obj_text = StringProperty("")  
  
    def __init__(self, obj, **kwargs):  
        super(TextInputPopup, self).__init__(**kwargs)  
        self.obj = obj  
        self.obj_text = obj.text  
        self.insert_today()  
  
    def insert_today(self):  
        """today表添加数据"""  
        try:  
            # 尝试将obj_text转换为整数  
            word_id = int(self.obj_text)  
            self._insert_or_update(word_id)  
        except ValueError:  
            # 如果转换失败,查询数据库中的id  
            word_id = self._get_word_id(self.obj_text)  
            if word_id is not None:  
                self._insert_or_update(word_id)  
  
    def _get_word_id(self, text):  
        # 使用参数化查询来避免SQL注入  
        select_id = "SELECT id FROM word WHERE word = ?"  
        rows = select_data(select_id, (text,))  # 假设select_data是一个函数,接受查询和参数  
        for row in rows:  
            for col in row:  
                return col  
        return None  
  
    def _insert_or_update(self, word_id):  
        # 使用参数化查询来插入数据  
        insert_sql = "INSERT INTO today(word_id) SELECT ? WHERE NOT EXISTS(SELECT 1 FROM today WHERE word_id = ?)"  
        update_sql = "UPDATE word SET add_times = add_times + 1 WHERE id = ?"  
        try:  
            insert_data(insert_sql, (word_id, word_id))  
            insert_data(update_sql, (word_id,))  
            print('%s is successfully added' % self.obj_text)  
        except Exception as e:  
            print(f"Error adding {self.obj_text}: {e}")

注意:上面的代码假设 select_data 和 insert_data 是已经定义好的函数,用于执行 SQL 查询和插入数据。您需要根据自己的数据库访问库(如 sqlite3)来实现这些函数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xinzheng新政

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

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

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

打赏作者

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

抵扣说明:

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

余额充值