VKeyboard 问题 - KivyMD TextField 的按键和输入处理不准确

问题描述 投票:0回答:1

描述: 我在 KivyMD 应用程序中使用

VKeyboard
时遇到问题,其中虚拟按键未正确注册,并且输入值未正确输入到 KivyMD
TextField
中。

主要问题如下:

  1. 按键注册不准确

    • 按下虚拟键盘上的某个键时,实际显示或注册的键与按下的键不同。
    • 例如,当在虚拟键盘上按“4”时,应用程序有时会显示“6”。
  2. 无法在

    TextField
    中输入正确的值:

    • 来自
      VKeyboard
      的值未正确反映在 KivyMD
      TextField
      中。
    • 尽管捕获了按键事件 (
      on_key_up
      ),但该值并未正确显示在输入字段中,或者根本没有输入。

代码摘录:

{
    "title" : "Numeric",
    "description" : "A numeric keypad",
    "cols" : 3,
    "rows": 4,
    "normal_1": [
    ["7", "7", "7", 1],
    ["8", "8", "8", 1],
    ["9", "9", "9", 1]],
    "normal_2": [
    ["4", "4", "4", 1],
    ["5", "5", "5", 1],
    ["6", "6", "6", 1]],
    "normal_3": [
    ["1", "1", "1", 1],
    ["2", "2", "2", 1],
    ["3", "3", "3", 1]],
    "normal_4": [
    ["0", "0", "0", 1],
    [".", ".", ".", 1],
    ["\u232b", null, "backspace", 1]],
    "shift_1": [
    ["7", "7", "7", 1],
    ["8", "8", "8", 1],
    ["9", "9", "9", 1]],
    "shift_2": [
    ["4", "4", "4", 1],
    ["5", "5", "5", 1],
    ["6", "6", "6", 1]],
    "shift_3": [
    ["1", "1", "1", 1],
    ["2", "2", "2", 1],
    ["3", "3", "3", 1]],
    "shift_4": [
    ["0", "0", "0", 1],
    [".", ".", ".", 1],
    ["\u232b", null, "backspace", 1]]
    }

下面是处理虚拟键盘(

VKeyboard
)和KivyMD
TextField
的代码部分:

问题总结:

  1. 按键不匹配:

    • VKeyboard
      上的某个键时,显示或注册的键不正确。例如,按“4”可能会导致“6”被注册。
  2. TextField
    输入问题:

    • 即使我通过
      on_key_up
      事件捕获按键,该值也没有正确输入到 KivyMD
      TextField

预期行为:

  • 当按下虚拟键盘上的某个键时,调试输出中应显示正确的对应值,并准确输入到
    TextField
    中。

实际行为:

  • 显示或输入的密钥通常不正确并且与预期输入不匹配。
  • 输入的值未正确显示在
    TextField
    中。

环境:

  • Kivy版本:2.3.0
  • KivyMD版本:2.0.1.dev0
  • Python 版本:3.9
  • 平台:树莓派CM4

重现步骤:

  1. 使用数字布局 (
    VKeyboard
    ) 设置
    numeric.json
    ,如上所示。
  2. 使用焦点事件 (
    VKeyboard
    ) 将
    TextField
    绑定到
    on_focus
    来控制虚拟键盘的可见性。
  3. 运行应用程序并尝试通过虚拟键盘输入数字。
  4. 观察按下的按键经常被错误注册或未正确反映在
    TextField
    中。

附加信息:

  • 本示例中使用的布局文件 (
    numeric.json
    ) 包含一个简单的数字键盘布局。
  • 使用物理键盘没有问题。
  • 调试信息(
    on_key_up
    )确认按键与输入值不匹配。

对于如何解决此问题,我将不胜感激。谢谢!

最小可重现示例

此脚本是基于 KivyMD 的图形用户界面 (GUI) 应用程序的最小可重现示例。它展示了以下功能:

  1. 浅色主题用户界面:该应用程序使用浅色主题和绿色主调色板,打造干净简单的外观。
  2. 主屏幕和导航:它由两个主屏幕组成 - 一个主屏幕和一个等温控制屏幕,由屏幕管理器管理,以便于导航。
  3. 使用虚拟键盘输入字段:用户可以使用文本字段输入温度、循环计数和循环时间。该应用程序使用虚拟数字键盘(VKeyboard),当文本字段获得焦点时会出现。
  4. 动态布局:GUI 包含多个用于导航、配置和不同屏幕之间切换的按钮。它还具有后退按钮,方便导航。
  5. 日期时间显示:应用程序显示当前日期和时间,每秒更新一次。
  6. 按钮操作:提供了“热循环”、“等温”和“确认设置”等按钮用于用户交互,但在此最小示例中未实现后端功能。

此示例的主要目的是展示如何使用 KivyMD 创建用户界面,其中包括按钮、文本字段和虚拟键盘等交互元素,同时确保界面响应灵敏且用户友好。


from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText
from kivymd.uix.label import MDLabel
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.fitimage import FitImage
from kivymd.uix.screenmanager import MDScreenManager
from kivymd.uix.textfield import (
    MDTextField,
    MDTextFieldLeadingIcon,
    MDTextFieldHintText,
    MDTextFieldHelperText,
)
from kivy.uix.vkeyboard import VKeyboard
from kivy.clock import Clock
from datetime import datetime

class MainScreen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        # 设置浅色主题和主色调
        self.theme_cls = MDApp.get_running_app().theme_cls
        self.theme_cls.theme_style = "Light"
        self.theme_cls.primary_palette = "Green"

        # 设置屏幕背景颜色
        self.md_bg_color = (1, 1, 1, 1)

        # 创建整体布局
        layout = MDBoxLayout(orientation="horizontal")
        self.left_layout = MDFloatLayout(size_hint=(0.4, 1))
        self.right_layout = MDFloatLayout(size_hint=(0.6, 1))

        qpcr_button = MDButton(
            MDButtonIcon(icon="ruler-square-compass"),
            MDButtonText(text="Thermal Cycle", font_style="Title"),
            style="elevated",
            pos_hint={"center_x": 0.7, "center_y": 0.6},
            height="200dp",
            size_hint=(3.2, 0.8)  # size_hint=(0.4, 0.1)
        )

        iso_button = MDButton(
            MDButtonIcon(icon="liquor"),
            MDButtonText(text="      Isothermal      ", font_style="Title"),
            style="elevated",
            pos_hint={"center_x": 1.8, "center_y": 0.6},
            height="200dp",
            size_hint=(6.4, 1.6)  # size_hint=(0.4, 0.1)
        )
        iso_button.bind(on_press=self.switch_to_isothermal)  # 绑定切换屏幕的方法

        # 将按钮添加到左侧布局
        self.left_layout.add_widget(qpcr_button)
        self.left_layout.add_widget(iso_button)

        # 创建底部的时间标签
        self.date_time_label = MDLabel(
            text="YYYY-MM-DD HH:MM:SS",
            halign="left",
            size_hint=(None, None),
            size=(dp(200), dp(40)),
            pos_hint={"x": -0.65, "y": 0.01},
        )

        # 将时间和 Logo 添加到右侧布局
        self.right_layout.add_widget(self.date_time_label)


        # 将左右布局添加到整体布局中
        layout.add_widget(self.left_layout)
        layout.add_widget(self.right_layout)

        # 将整体布局添加到屏幕中
        self.add_widget(layout)

        # 定期更新时间
        Clock.schedule_interval(self.update_date_time, 1)

    def update_date_time(self, dt):
        now = datetime.now()
        self.date_time_label.text = now.strftime("%Y-%m-%d %H:%M:%S")

    def switch_to_isothermal(self, *args):
        # 切换到名为 'isothermal' 的屏幕
        if self.manager:
            print("Switching to AGD screen...")  # 添加调试信息
            self.manager.current = "isothermal"

class MotorControlScreen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.locked = False
        self.data_records = []
        self.temperature_records = []  # 用于存储温度记录
        self.current_cycle = 0
        self.total_cycles = 20
        self.num_samples = 1000
        self.countdown_time = 360
        self.image_widget = None
        self.show_temperature_plot = False

        # 创建 VKeyboard 对象
        self.keyboard = VKeyboard()
        self.keyboard.layout_path = "./"  # 设置键盘布局文件的路径(假设布局文件位于当前目录下)
        self.keyboard.layout = 'numeric.json'  # 只使用数字键盘布局
        self.keyboard.size_hint = (1, 0.3)
        self.keyboard.pos_hint = {"center_x": 0, "y": 0}
        self.keyboard.bind(on_key_up=self.on_key_up)
        print("Keyboard binding successful")  # 添加调试信息

        # 调用 build_ui() 并将生成的 screen 作为主界面
        self.add_widget(self.build_ui())

    def build_ui(self):
        screen = MDScreen(md_bg_color=(1, 1, 1, 1))
        layout = MDBoxLayout(orientation="horizontal")
        self.left_layout = MDFloatLayout(size_hint=(0.4, 1))
        self.right_layout = MDFloatLayout(size_hint=(0.6, 1))

        back_button = MDButton(
            MDButtonIcon(icon="arrow-left"),  # 设置图标为返回箭头
            MDButtonText(text="Back", font_style="Title"),
            style="elevated",
            pos_hint={"center_x": 2.2, "center_y": 0.95},  # 左上角位置
            size_hint=(0.25, 0.1),
            on_release=self.switch_to_main_screen
        )

        toggle_plot_button = MDButton(
            MDButtonIcon(icon="swap-horizontal"),
            MDButtonText(text="Switching chart"),
            style="elevated",
            pos_hint={"center_x": -0.35, "center_y": 0.2},
            height="56dp",
            size_hint_x=0.6
        )

        # 添加温度输入框
        self.temperature_input = MDTextField(
            MDTextFieldLeadingIcon(icon="thermometer"),
            MDTextFieldHintText(text="Target Temperature (°C)"),
            MDTextFieldHelperText(text="Please enter the target temperature (e.g., 98)", mode="persistent"),
            mode="outlined",
            size_hint_x=None,
            width="240dp",
            pos_hint={"center_x": 0.5, "center_y": 0.9},
        )
        self.temperature_input.bind(focus=self.show_keyboard)

        # 添加循环次数输入框
        self.cycle_count_input = MDTextField(
            MDTextFieldLeadingIcon(icon="repeat"),
            MDTextFieldHintText(text="Cycle Count"),
            MDTextFieldHelperText(text="Please enter the number of cycles (e.g., 20)", mode="persistent"),
            mode="outlined",
            size_hint_x=None,
            width="240dp",
            pos_hint={"center_x": 0.5, "center_y": 0.7},
        )
        self.cycle_count_input.bind(focus=self.show_keyboard)

        # 添加每个循环时间输入框
        self.cycle_time_input = MDTextField(
            MDTextFieldLeadingIcon(icon="timer"),
            MDTextFieldHintText(text="Cycle Time (seconds)"),
            MDTextFieldHelperText(text="Please enter the time for each cycle (e.g., 60)", mode="persistent"),
            mode="outlined",
            size_hint_x=None,
            width="240dp",
            pos_hint={"center_x": 0.5, "center_y": 0.5},
        )
        self.cycle_time_input.bind(focus=self.show_keyboard)

        # 确认按钮,确认设定并开始实验流程
        confirm_button = MDButton(
            MDButtonIcon(icon="check"),
            MDButtonText(text="Confirm Settings"),
            style="elevated",
            pos_hint={"center_x": 0.5, "center_y": 0.3},
            height="56dp",
            size_hint_x=0.6
        )

        # 绑定按钮到 toggle_plot() 函数,用于切换显示的图表
        toggle_plot_button.bind(on_press=self.toggle_plot)

        self.left_layout.add_widget(self.temperature_input)
        self.left_layout.add_widget(self.cycle_count_input)
        self.left_layout.add_widget(self.cycle_time_input)
        self.left_layout.add_widget(confirm_button)
        self.left_layout.add_widget(back_button)

        self.right_layout.add_widget(toggle_plot_button)
        self.right_layout.add_widget(self.keyboard)

        layout.add_widget(self.left_layout)
        layout.add_widget(self.right_layout)

        screen.add_widget(layout)

        return screen

    def show_keyboard(self, instance, value):
        if value:  # 当输入框被聚焦时显示键盘
            self.keyboard.opacity = 1
            instance.focus = True  # 确保输入框获得焦点
        else:  # 当失去焦点时隐藏键盘
            self.keyboard.opacity = 0

    def on_key_up(self, keyboard, keycode, *args):
        print(f"Key pressed: {keycode}")  # 调试信息

        # 确保 keycode 至少包含两个元素,避免索引错误
        if len(keycode) < 2:
            return

        # 检查哪个输入框当前处于聚焦状态
        if self.temperature_input.focus:
            print("Temperature input focused")
        elif self.cycle_count_input.focus:
            print("Cycle count input focused")
        elif self.cycle_time_input.focus:
            print("Cycle time input focused")

        # 将输入的值添加到相应的输入框中
        if keycode[1] == 'backspace':
            # 如果按下的是回删键,则删除输入框中的最后一个字符
            if self.temperature_input.focus:
                self.temperature_input.text = self.temperature_input.text[:-1]
            elif self.cycle_count_input.focus:
                self.cycle_count_input.text = self.cycle_count_input.text[:-1]
            elif self.cycle_time_input.focus:
                self.cycle_time_input.text = self.cycle_time_input.text[:-1]
        elif keycode[1].isdigit() or keycode[1].isalpha():
            # 如果按下的是数字键或字母键,则将字符添加到相应的输入框
            if self.temperature_input.focus:
                self.temperature_input.text += keycode[1]
            elif self.cycle_count_input.focus:
                self.cycle_count_input.text += keycode[1]
            elif self.cycle_time_input.focus:
                self.cycle_time_input.text += keycode[1]

    def switch_to_main_screen(self, instance):
        if self.manager:
            self.manager.current = "main"

    def toggle_plot(self, instance):
        self.show_temperature_plot = not self.show_temperature_plot

class MainApp(MDApp):
    def build(self):
        # 创建 ScreenManager 来管理所有界面
        sm = MDScreenManager()

        # 添加主界面
        main_screen = MainScreen(name="main")
        sm.add_widget(main_screen)

        # 添加等温控制界面
        isothermal_screen = MotorControlScreen(name="isothermal")
        sm.add_widget(isothermal_screen)

        # 设置初始显示的界面
        sm.current = "main"

        return sm

# 运行应用程序
if __name__ == "__main__":
    MainApp().run()

python kivy
1个回答
0
投票

根据文档

VKeyboard 是 Kivy 的屏幕键盘。其操作的目的是 对用户透明。直接使用小部件是不行的 推荐。

您只需在主 Python 脚本的开头添加以下代码即可:

from kivy.config import Config  # Keep these Config settings at the top
Config.set('kivy', 'keyboard_mode', 'systemanddock')
Config.set('kivy', 'keyboard_layout', './keyboard_layout.json')  # setting a custom keyboard layout from local folder

然后,当

TextInput
收到
focus
时,虚拟键盘将自动创建,无需任何额外的编码。

所有涉及

self.keyboard
的代码,包括
show_keyboard()
on_key_up()
方法,都可以删除。

© www.soinside.com 2019 - 2024. All rights reserved.