Kivy Clear_widget 奇怪行为(附可复现代码)

2 投票
1 回答
2453 浏览
提问于 2025-04-17 17:49

我来简单说一下我想做的事情。我在一个叫做FloaLayout的布局里放了两个文本输入框,这些都是在一个.kv文件里定义的,下面会展示这个文件的内容。(我之前在kivy论坛上问过这个问题,但得到的解决方案没有用,所以我在这里再问问,看看能不能得到新的想法)

test.kv

<TESTGUI>:
    t2: TI2
    t4: TI4
    fl1: FL1

FloatLayout:
    orientation: 'lr-bt'
    id: FL1
    size: root.size
    TextInput:
        id: TI2
        size_hint: 1, 0.1  
        pos_hint: {'top': 1}
        font_size: 35
        on_text: root.realTimeSearch(TI2, TI2.text)
    TextInput:
        id: TI4
        size_hint: 1, 0.1
        pos_hint: {'top': 0.86}
        font_size: 15

现在,当我在其中一个文本输入框(t2)里输入任何文字时,程序会在一个字符串里搜索这个文字。每当文本输入框的内容改变时,搜索就会进行。所以基本上,只要你开始输入,搜索就会动态开始。搜索结果可能会有很多匹配项(这些匹配项都存储在一个叫做result_list的列表里,具体代码见下文),根据匹配的数量,我会添加一个网格布局,里面的按钮数量和搜索结果的数量相等(也就是result_list里的元素数量)。当我点击按钮时,按钮上的文字会传送到另一个文本输入框(t4,如上所示)。下面是.py文件里的完整代码。这个程序基本上是一个带有自动补全功能的搜索工具。现在我遇到的问题是,clear_widgets在当前的上下文中似乎不起作用。所以我会看到很多小部件重叠在一起,我需要逐个点击才能把它们去掉(具体的代码可以参考下面的内容)。

我建议你在自己的电脑上运行这个代码,这样你就能更清楚地看到发生了什么(试着在t2文本输入框里输入'silicon',你会发现clear_widgets没有起作用)。

import re
import sys
import kivy
kivy.require('1.5.1')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.popup import Popup
from kivy.uix.scrollview import ScrollView
from collections import Counter
from functools import partial
reload(sys)
sys.setdefaultencoding('utf-8')

rtsstr =",,,Substrate1,,,Substrate1,,,Substrate1,,,Substrate1,,,Substrate1,,,Substrate_coating,,,silicon,,,silicon_Substrate,,,substrate_silicon,,,"
#rtsstr is the string on which search is being performed


class TESTGUI(Widget):

t2 = ObjectProperty(None)
t4 = ObjectProperty(None)
fl1 = ObjectProperty(None)

def realTimeSearch(self, instance, value):
    """
    this function is used as a real time update of search results based on the search query. It also identifies partial matches (e.g. a search for silicon can result in entries such as silicon, silicon nitride, silicon dioxide etc. being displayed
    """
    if value != '':            
        match = re.findall("(?<=,{3})(?:(?!,{3}).)*?%s.*?(?=,{3})" % value, rtsstr, re.IGNORECASE)
        result_list = list(set(match)) #using a set to remove duplicates, if any.
        self.create_search(result_list)

def create_search(self, result_list):
    layt = GridLayout(cols=3, size_hint_y = None)
    layt.bind(minimum_height=layt.setter('height'))
    scrlv = ScrollView(size_hint=(1, 0.8), pos_hint={'top': 0.8})
    self.fl1.remove_widget(scrlv)

    for result in result_list:
        buttn2 = Button(text = str(result), size_hint = (0.3, None), height = 40)
        buttn2.bind(on_press = partial(self.transferSearchText, buttn2.text, scrlv))
        layt.add_widget(buttn2)
    scrlv.add_widget(layt)
    self.fl1.add_widget(scrlv)   

def transferSearchText(self, text, scrlv, *args):
    self.t4.insert_text(text + ',')
    scrlv.clear_widgets()
    self.fl1.remove_widget(scrlv) 
    self.t2.text = ''                     

class TestApp(App):
    def build(self):
        return TESTGUI()


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

谢谢!

1 个回答

3

你想要清空Scrollview,但其实你应该清空添加到ScrollView里的布局。

Widget.Clear_widget() 只会清除当前小部件的子项,它不会递归清除,也不是为了这样设计的。

Kivy并没有提供传统的可编辑组合框。不过,Kivy让你非常容易创建自己的小部件,把文本输入框和下拉菜单结合起来。

你应该使用像ComboEdit这样的东西,具体可以参考这个代码片段的维基,然后根据你的需求进行修改。

所以,你想要实现的功能可以这样做:

import re

from kivy.clock import Clock
from kivy.factory import Factory
from kivy.properties import ListProperty, StringProperty
from kivy.lang import Builder

Builder.load_string(''' <DDNButton@Button>
    size_hint_y: None
    height: dp(45) ''')


class ComboEdit(Factory.TextInput):

    options = ListProperty(('', ))
    _options = ListProperty(('', ))
    options_cls = StringProperty(Factory.DDNButton)

    def __init__(self, **kw):
        ddn = self.drop_down = Factory.DropDown()
        ddn.bind(on_select=self.on_select)
        super(ComboEdit, self).__init__(**kw)

    def on_options(self, instance, value):
    self._options = value

    def on__options(self, instance, value):
        ddn = self.drop_down
        ddn.clear_widgets()
        for option in value:
            widg = self.options_cls(text=option)
            widg.bind(on_release=lambda btn: ddn.select(btn.text))
            ddn.add_widget(widg)

    def on_select(self, *args):
        self.text = args[1]

    def on_text(self, instance, value):
        if value == '':
            instance._options = self.options
        else:
            r = re.compile(f".*{value}")
            match = filter(r.match, instance.options)
            #using a set to remove duplicates, if any.
            instance._options = list(set(match))
            print(instance._options)
        try:
            instance.drop_down.open(instance)
        except Factory.WidgetException:
            #  instance.drop_down.parent.remove_widget(instance.drop_down)
            instance.drop_down.parent = None
            Clock.schedule_once(lambda dt:  instance.drop_down.open(instance))

    def on_focus(self, instance, value):
        if value:
            self.drop_down.open(self)
            self.text = ''

if __name__ == '__main__':
    from kivy.base import runTouchApp
    runTouchApp(Builder.load_string(''' FloatLayout:
    ComboEdit:
        size_hint: .5, None
        pos: 0, root.top - self.height
        options: ['Hello', 'World']

'''))

撰写回答