如何在Kivy中创建带相对尺寸定位的PID控制器自定义Widget
解决方案
核心思路
Rectangle是画布图形元素,本身不支持size_hint和pos_hint,解决办法是用布局组件(如BoxLayout)作为自定义组件的容器,将Rectangle绘制在容器的画布上,通过绑定容器的size和pos属性,让背景矩形随容器自动适配尺寸与位置。内部的显示区、标签、按钮则用嵌套布局组织,全程使用相对尺寸参数实现窗口自适应。
自定义组件实现(纯Python代码)
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.label import Label from kivy.uix.button import Button from kivy.graphics import Color, Rectangle from kivy.properties import NumericProperty, StringProperty class PIDControllerDisplay(BoxLayout): # 定义可外部修改的绑定属性,用于更新温度显示 set_temp = NumericProperty(25.0) current_temp = NumericProperty(23.5) def __init__(self, **kwargs): super().__init__(**kwargs) self.orientation = 'vertical' self.size_hint = (0.45, 0.45) # 相对尺寸,外部可覆盖 self.padding = 10 self.spacing = 10 # 绘制组件浅灰色背景 with self.canvas.before: Color(0.8, 0.8, 0.8, 1) self.bg_rect = Rectangle(size=self.size, pos=self.pos) # 绑定背景矩形与组件尺寸、位置 self.bind(size=self._update_bg, pos=self._update_bg) # 上部黑色显示区容器 display_layout = BoxLayout(orientation='vertical', size_hint=(1, 0.6)) with display_layout.canvas.before: Color(0, 0, 0, 1) self.display_rect = Rectangle(size=display_layout.size, pos=display_layout.pos) display_layout.bind(size=self._update_display_bg, pos=self._update_display_bg) # 温度标签,绑定到组件属性实现自动更新 set_label = Label(text=f"设定温度: {self.set_temp:.1f}°C", color=(1,1,1,1), font_size=20) current_label = Label(text=f"当前温度: {self.current_temp:.1f}°C", color=(1,1,1,1), font_size=20) self.bind(set_temp=lambda _, val: set_label.setter('text')(set_label, f"设定温度: {val:.1f}°C")) self.bind(current_temp=lambda _, val: current_label.setter('text')(current_label, f"当前温度: {val:.1f}°C")) display_layout.add_widget(set_label) display_layout.add_widget(current_label) self.add_widget(display_layout) # 下部按钮区 button_layout = BoxLayout(size_hint=(1, 0.3), spacing=5) for text in ["升温", "降温", "确认", "重置"]: button_layout.add_widget(Button(text=text)) self.add_widget(button_layout) def _update_bg(self, *args): self.bg_rect.size = self.size self.bg_rect.pos = self.pos def _update_display_bg(self, instance, *args): self.display_rect.size = instance.size self.display_rect.pos = instance.pos class PIDApp(App): def build(self): # 主布局,横向+纵向嵌套排列4个自定义组件 main_layout = BoxLayout(orientation='vertical', spacing=10, padding=10) row1 = BoxLayout(spacing=10) row1.add_widget(PIDControllerDisplay()) row1.add_widget(PIDControllerDisplay()) row2 = BoxLayout(spacing=10) row2.add_widget(PIDControllerDisplay()) row2.add_widget(PIDControllerDisplay()) main_layout.add_widget(row1) main_layout.add_widget(row2) return main_layout if __name__ == "__main__": PIDApp().run()
关键细节说明
- 画布绑定:通过
bind方法将Rectangle的尺寸、位置与所属布局的属性关联,布局尺寸变化时自动触发更新,实现背景自适应。 - 相对尺寸:所有控件均使用
size_hint设置相对占比,无需固定像素值,窗口缩放时自动调整。 - 可绑定属性:定义
set_temp、current_temp属性,外部只需修改组件的这些属性,就能自动更新内部标签文本,简化交互逻辑。
替代方案:用KV语言简化UI定义
如果偏好将UI与逻辑分离,可使用KV语言编写组件结构,代码更简洁:
Python代码(main.py)
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.properties import NumericProperty, StringProperty class PIDControllerDisplay(BoxLayout): set_temp = NumericProperty(25.0) current_temp = NumericProperty(23.5) class PIDApp(App): def build(self): return BoxLayout(orientation='vertical', spacing=10, padding=10) if __name__ == "__main__": PIDApp().run()
KV文件(pidapp.kv)
<PIDControllerDisplay>: orientation: 'vertical' size_hint: 0.45, 0.45 padding: 10 spacing: 10 # 组件浅灰色背景 canvas.before: Color: rgba: 0.8, 0.8, 0.8, 1 Rectangle: size: self.size pos: self.pos # 上部黑色显示区 BoxLayout: orientation: 'vertical' size_hint: 1, 0.6 canvas.before: Color: rgba: 0, 0, 0, 1 Rectangle: size: self.size pos: self.pos Label: text: f"设定温度: {root.set_temp:.1f}°C" color: 1,1,1,1 font_size: 20 Label: text: f"当前温度: {root.current_temp:.1f}°C" color: 1,1,1,1 font_size: 20 # 下部按钮区 BoxLayout: size_hint: 1, 0.3 spacing: 5 Button: text: "升温" Button: text: "降温" Button: text: "确认" Button: text: "重置" # 主布局排列4个组件 <BoxLayout>: BoxLayout: spacing: 10 PIDControllerDisplay: PIDControllerDisplay: BoxLayout: spacing: 10 PIDControllerDisplay: PIDControllerDisplay:
内容的提问来源于stack exchange,提问作者PetitFroggie
相关产品推荐
相关产品推荐

