Description:
I am encountering an issue using VKeyboard
in my KivyMD application where the virtual key presses do not register correctly, and the input values are not being entered properly into the KivyMD TextField
.
Here are the main issues:
- Inaccurate Key Press Registration:
- When pressing a key on the virtual keyboard, the key that is actually displayed or registered is different from the key that was pressed.
- For example, when pressing "4" on the virtual keyboard, the application sometimes displays "6" instead.
- Failure to Input Correct Values into
TextField
:- The values from
VKeyboard
are not properly reflected in the KivyMDTextField
. - Despite capturing the key press event (
on_key_up
), the value does not appear correctly in the input field, or it does not get entered at all.
- The values from
Code Extract:
{
"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]]
}
Below is the part of the code dealing with the virtual keyboard (VKeyboard
) and the KivyMD TextField
:
Issues Summary:
Key Press Mismatch:
- When pressing a key on the
VKeyboard
, the displayed or registered key is incorrect. For example, pressing "4" may result in "6" being registered.
- When pressing a key on the
Input Issue with
TextField
:- Even though I am capturing key presses through the
on_key_up
event, the value is not correctly entered into the KivyMDTextField
.
- Even though I am capturing key presses through the
Expected Behavior:
- When pressing a key on the virtual keyboard, the correct corresponding value should be displayed in the debug output and accurately entered into the
TextField
.
Actual Behavior:
- The key shown or entered is often incorrect and doesn't match the intended input.
- The entered value is not correctly displayed in the
TextField
.
Environment:
- Kivy Version: 2.3.0
- KivyMD Version: 2.0.1.dev0
- Python Version: 3.9
- Platform: Raspberry Pi CM4
Steps to Reproduce:
- Set up
VKeyboard
with a numeric layout (numeric.json
) as shown above. - Bind the
VKeyboard
to aTextField
using the focus event (on_focus
) to control the visibility of the virtual keyboard. - Run the application and attempt to input numbers through the virtual keyboard.
- Observe that the key that is pressed is often incorrectly registered or not properly reflected in the
TextField
.
Additional Information:
- The layout file (
numeric.json
) used in this example contains a simple numeric keypad layout. - There is no issue when using a physical keyboard.
- Debugging information (
on_key_up
) confirms the mismatch between the key press and the input value.
I would appreciate any advice or suggestions on how to resolve this issue. Thank you!
Minimal Reproducible Example
This script is a minimal reproducible example of a KivyMD-based graphical user interface (GUI) application. It demonstrates the following features:
- Light-themed User Interface: The app uses a light theme with a green primary palette for a clean and simple look.
- Main Screen and Navigation: It consists of two main screens - a main screen and an isothermal control screen, managed by a screen manager for easy navigation.
- Input Fields with Virtual Keyboard: Users can input temperature, cycle count, and cycle time using text fields. The app uses a virtual numeric keyboard (VKeyboard) that appears when a text field is focused.
- Dynamic Layout: The GUI contains multiple buttons for navigation, configuration, and switching between different screens. It also features a back button for easy navigation.
- Date-Time Display: The app displays the current date and time, which updates every second.
- Button Actions: Buttons such as "Thermal Cycle", "Isothermal", and "Confirm Settings" are provided for user interactions, but the backend functionality is not implemented in this minimal example.
The main purpose of this example is to show how to create a user interface with KivyMD that includes interactive elements such as buttons, text fields, and a virtual keyboard, while ensuring the interface is responsive and user-friendly.
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()
Thank you very much for your helpful suggestion! It solved the issue I was facing. I just wanted to note that it's important to ensure that both Kivy and KivyMD are updated to the latest versions for the solution to work effectively. Thanks again for your assistance!
from kivy.metrics import dp
from kivy.config import Config # 必须在其他 import 之前添加
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
Config.set('kivy', 'keyboard_mode', 'systemanddock') # 设置键盘模式
Config.set('kivy', 'keyboard_layout', './numeric.json') # 设置键盘布局路径,可以使用自定义的数字键盘布局
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()
Description:
I am encountering an issue using VKeyboard
in my KivyMD application where the virtual key presses do not register correctly, and the input values are not being entered properly into the KivyMD TextField
.
Here are the main issues:
- Inaccurate Key Press Registration:
- When pressing a key on the virtual keyboard, the key that is actually displayed or registered is different from the key that was pressed.
- For example, when pressing "4" on the virtual keyboard, the application sometimes displays "6" instead.
- Failure to Input Correct Values into
TextField
:- The values from
VKeyboard
are not properly reflected in the KivyMDTextField
. - Despite capturing the key press event (
on_key_up
), the value does not appear correctly in the input field, or it does not get entered at all.
- The values from
Code Extract:
{
"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]]
}
Below is the part of the code dealing with the virtual keyboard (VKeyboard
) and the KivyMD TextField
:
Issues Summary:
Key Press Mismatch:
- When pressing a key on the
VKeyboard
, the displayed or registered key is incorrect. For example, pressing "4" may result in "6" being registered.
- When pressing a key on the
Input Issue with
TextField
:- Even though I am capturing key presses through the
on_key_up
event, the value is not correctly entered into the KivyMDTextField
.
- Even though I am capturing key presses through the
Expected Behavior:
- When pressing a key on the virtual keyboard, the correct corresponding value should be displayed in the debug output and accurately entered into the
TextField
.
Actual Behavior:
- The key shown or entered is often incorrect and doesn't match the intended input.
- The entered value is not correctly displayed in the
TextField
.
Environment:
- Kivy Version: 2.3.0
- KivyMD Version: 2.0.1.dev0
- Python Version: 3.9
- Platform: Raspberry Pi CM4
Steps to Reproduce:
- Set up
VKeyboard
with a numeric layout (numeric.json
) as shown above. - Bind the
VKeyboard
to aTextField
using the focus event (on_focus
) to control the visibility of the virtual keyboard. - Run the application and attempt to input numbers through the virtual keyboard.
- Observe that the key that is pressed is often incorrectly registered or not properly reflected in the
TextField
.
Additional Information:
- The layout file (
numeric.json
) used in this example contains a simple numeric keypad layout. - There is no issue when using a physical keyboard.
- Debugging information (
on_key_up
) confirms the mismatch between the key press and the input value.
I would appreciate any advice or suggestions on how to resolve this issue. Thank you!
Minimal Reproducible Example
This script is a minimal reproducible example of a KivyMD-based graphical user interface (GUI) application. It demonstrates the following features:
- Light-themed User Interface: The app uses a light theme with a green primary palette for a clean and simple look.
- Main Screen and Navigation: It consists of two main screens - a main screen and an isothermal control screen, managed by a screen manager for easy navigation.
- Input Fields with Virtual Keyboard: Users can input temperature, cycle count, and cycle time using text fields. The app uses a virtual numeric keyboard (VKeyboard) that appears when a text field is focused.
- Dynamic Layout: The GUI contains multiple buttons for navigation, configuration, and switching between different screens. It also features a back button for easy navigation.
- Date-Time Display: The app displays the current date and time, which updates every second.
- Button Actions: Buttons such as "Thermal Cycle", "Isothermal", and "Confirm Settings" are provided for user interactions, but the backend functionality is not implemented in this minimal example.
The main purpose of this example is to show how to create a user interface with KivyMD that includes interactive elements such as buttons, text fields, and a virtual keyboard, while ensuring the interface is responsive and user-friendly.
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()
Thank you very much for your helpful suggestion! It solved the issue I was facing. I just wanted to note that it's important to ensure that both Kivy and KivyMD are updated to the latest versions for the solution to work effectively. Thanks again for your assistance!
from kivy.metrics import dp
from kivy.config import Config # 必须在其他 import 之前添加
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
Config.set('kivy', 'keyboard_mode', 'systemanddock') # 设置键盘模式
Config.set('kivy', 'keyboard_layout', './numeric.json') # 设置键盘布局路径,可以使用自定义的数字键盘布局
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()
Share
Improve this question
edited Dec 2, 2024 at 21:27
NEO101
asked Nov 21, 2024 at 0:14
NEO101NEO101
234 bronze badges
2
- You apologize for not including your entire code, but we do not want to see your entire code. Please post a minimal reproducible example. I suggest creating a new project that can be run to demonstrate your problem, but only include the minimum possible code. – John Anderson Commented Nov 21, 2024 at 14:08
- Thank you for your suggestion. I have now created a minimal reproducible example, which you can find below. I hope this helps clarify the issue. – NEO101 Commented Nov 21, 2024 at 18:09
1 Answer
Reset to default 1According to the documentation:
VKeyboard is an onscreen keyboard for Kivy. Its operation is intended to be transparent to the user. Using the widget directly is NOT recommended.
You can simply add the following code at the start of your main python script:
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
Then, when a TextInput
receives focus
, the virtual keyboard will automatically be created without any additional coding required.
All of your code involving self.keyboard
, including the show_keyboard()
and the on_key_up()
methods, can be eliminated.