Kivy Clear_widget strange behaviour (reproducible code attached)

Question:

just a rundown of what I’m trying to do. I’ve got two TextInput widgets in a FloaLayout widget, all defined in a .kv file as shown below. (I asked this question on kivy forums but the solution offered didn’t work out so I’m asking it here to get some new ideas)

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

Now when I enter any text in one of the TextInput widgets (t2), what the program does is it searches for that text in a string. This search is performed whenever the TextInput widgets text changes. So basically as soon as you start typing the search begins dynamically. The search result can have many matches (all these matches are stored in a list called result_list (see below code)) and based on the number of matches, I add a GridLayout with the number of buttons that equal to the number of results (i.e. number of elements in the result_list) from the search. Now when I click the button, what happens is that it transfers the button text to the other TextInput widget (t4 as shown above). Below is the entire code from the .py file The program basically is a search utility with auto-complete functionality. The problem that I’m encountering is that clear_widgets doesn’t seem to work in the current context. So I get many widgets on top of eachother and I need to click through each of them to get rid of them (refer to the code below for a clear explanation)

I’d suggest you run the code on your machine to get a good idea of what is happening (try entering ‘silicon’ in t2 textinput widget and you can see how clear_widgets is not working).

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() 

Thanks!

Asked By: Crust3

||

Answers:

You are trying to clear the Scrollview, where you should be clearing the layout you add to the ScrollView.

Widget.Clear_widget() only clears the current widget’s children, it’s not recursive and is not meant to be so.

Kivy doesn’t provide a traditional editable combo-box. How ever Kivy makes it extremely easy for you to create your own widget combining a TextInput and a DropDown.

You should be using something like a ComboEdit as shown in the snippets wiki, and modifying it to suit your needs.

So, what you are trying to achieve can be done like so::

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']

'''))
Answered By: qua-non
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.