How to only do add_widget once in Kivy - python

I have a dynamic Screen which is generated based on a button you clicked on another screen. Issue is dat every time I enter the Screen, the buttons are regenerated and added to the existing buttons.
The reason is that I use the on_enter method, but I don't know how I can use on_kv_post for example, as these events happen on starting the app.
How can I initialise the screen every time I return to this screen?
class ClientEnvsGrid(Screen):
envProp = StringProperty('')
def __init__(self, **kwargs):
super(ClientEnvsGrid, self).__init__(**kwargs)
def on_enter(self, *args):
clientProp = self.manager.get_screen('clientlist').clientProp
try:
client_filepath = os.path.join('clients', clientProp, "environments.json")
client_file = open(client_filepath)
clientdata = json.loads(client_file.read())
print(clientdata)
self.ids.clientlabel.text = clientdata["clientname"]
for envs in clientdata["environments"]:
print(envs["name"])
envbutton = Button(text=envs["name"])
envbutton.bind(on_press=lambda *args: self.pressed('envbtn', *args))
self.ids.environments.add_widget(envbutton)
except:
print("No client data found")
self.manager.current = 'clientlist'
def pressed(self, instance, *args):
self.envProp = args[0].text

I've managed to fix it to include clear_widgets in the environments GridLayout in the on_leave event.
def on_leave(self, *args):
self.ids.environments.clear_widgets()

Related

Clean way to close splashscreen in kivy after a delay

In Kivy, if you have a ScreenManager with multiple screens already added, you can not change screens with the switch_to function, as that also tries to first add the Screen to the Screenmanager, before switching to it.
Instead, you switch screens simply by setting the current property, like this:
screenmanager.current = "new_screen_name"
The problem is, I wish my SplashScreen to automatically transition to MainScreen after a short delay, using Clock.schedule_once(). But that can only take a function as a parameter, so I I have to write a another function just to change screen, like this:
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MainScreenManager, self).__init__(**kwargs)
Clock.schedule_once(switch_to_main_screen, 3)
def switch_to_main_screen(self, *args):
self.current = "main_screen
I'm just wondering if there is a more efficient way to do this? e.g can I somhow directly set the "self.current" property in Clock.schedule_once? Or, generally speaking, is there a better way to do this simple task? (i.e have a parameter be set a few seconds in the future)
You can use the setattr() python built-in. Try replacing:
Clock.schedule_once(switch_to_main_screen, 3)
with:
Clock.schedule_once(lambda dt: setattr(self, 'current', 'main_screen'), 3)
See the documentation.
Consider this, its my ready sheet in kivy:
import #what u need
Builder.load_file('the.kv')
class fscreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class secscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class thscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class theapp(App):
def build(self):
self.screenm = ScreenManager()
self.fscreen = fscreen() ## as default this is my
screen = Screen(name = "first screen") # my first screen
screen.add_widget(self.fscreen) # if i want second screen
self.screenm.add_widget(screen) # as first i move this below
## secscreen its waay
self.secscreen = secscreen() ### efficient
screen = Screen(name = "secondscreen")
screen.add_widget(self.secscreen)
self.screenm.add_widget(screen)
self.thscreen = thscreen()
screen = Screen(name = "thirdscreen")
screen.add_widget(self.thscreen)
self.screenm.add_widget(screen)
return self.screenm
if __name__ == "__main__":
theapp = theapp()
theapp.run()
This way the fscreen is by default the first screen after the presplash

Binding button to function after adding button with Kivy language

I'm trying to figure out how to bind a button that's been laid out using Kivy language to a function. I've seen plenty of answers when laying out buttons in Python language. But what about once everything is in place and you're now referencing via a custom class that inherits from Button?
On press, the code below throws the error TypeError: show() takes 1 positional argument but 2 were given and crashes the program.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.show)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.hide)
self.bind(on_press=self.show)
The kivy code that calls the function you set in .bind() is passing an argument which your function isn't prepared for. It's been a while since I last used kivy, so I can't be sure, but I think the event details are passed to the function.
As such, your definitions for event handlers should look like this:
def show(self, event):
...
def hide(self, event):
...
If you're curious, you could print(event) within those functions, to see what's being sent in.
The answer is to include the class name, in this case TimerButton, in the function definitions. This is a concept I don't completely understand since the function is defined within the scope of the TimerButton class. But hey, it works.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.show)

Two Windows Opening When Only One Should

I am trying to make a game where a map Tk() window opens, the player chooses location, the map window closes, and the level window opens. When the player has a choice the leave the level and chooses 'yes', the level Tk() should close and the map should open back up so the player can click on a different location and open another Tk(). For some reason all Tks are opening at once. Here is my code.
class GUI_Control:
def __init__(self, player, delegate, level=-1):
self.delegate = delegate
self.player = player
self.current_level = level
self.map = Map(self)
self.current_level = level
#define level gui's here and put in data structure
hydra_level = Hydra_Level(self)
self.windows = [hydra_level]
def open(self):
if self.current_level == -1:
self.map.mainloop()
else:
self.current_level.mainloop()
def save(self):
self.delegate.save()
def swap_window(self, n):
#pull up the specified window
self.windows[n].mainloop()
class Map(Tk):
MAP_WIDTH = 600
MAP_HEIGHT = 375
def __init__(self, listener, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.listener = listener
# define map gui here
self.canvas = Canvas(self, width=self.MAP_WIDTH, height=self.MAP_HEIGHT)
self.canvas.pack()
self.map_picture = PhotoImage(file=r"images/archipelago.gif")
self.canvas.create_image(0, 0, image=self.map_picture)
def destroy(self, n=0):
Tk.destroy(self)
#send message back to gui_control to bring up another window
self.listener.swap_window(n)
class Hydra_Level(Tk):
def __init__(self, listener, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.listener = listener
def destroy(self):
Tk.destroy(self)
#bring up the map again by sending message back to the control
self.listener.open()
Both windows, the map and the level, open in the function GUI_Control.open(). Is there any way to make them open one at a time?
In a Tkinter/tkinter application, you must have only one Tk() instance running at the same time. So to resolve your problem, you can simply use Toplevel().

Inserted tabs not showing in QTabWidget despite show() called

I have a QTabWidget in PyQt which provides the possibility to tear off tabs and the re-attach them when they are closed. It works well except that newly attached tabs aren't immediately showing. A blank widget is showing and the widget is shown only after the current tab has been changed and then changed back.
I've searched StackOverflow and the answers to similar problems have pointed out that the added widget's show()-method needs to be called after the new tab is added. I tried that but the newly added tab is still not showing.
Slimmed down example code:
from PyQt4 import QtGui, QtCore
class DetachableTabWidget(QtGui.QTabWidget):
""" Subclass of QTabWidget which provides the ability to detach
tabs and making floating windows from them which can be reattached.
"""
def __init__(self, *args, **kwargs):
super(DetachableTabWidget, self).__init__(*args, **kwargs)
self.setTabBar(_DetachableTabBar())
def detach_tab(self, i):
""" Make floating window of tab.
:param i: index of tab to detach
"""
teared_widget = self.widget(i)
widget_name = self.tabText(i)
# Shift index to the left and remove tab.
self.setCurrentIndex(self.currentIndex() - 1 if self.currentIndex() > 0 else 0)
self.removeTab(i)
# Store widgets window-flags and close event.
teared_widget._flags = teared_widget.windowFlags()
teared_widget._close = teared_widget.closeEvent
# Make stand-alone window.
teared_widget.setWindowFlags(QtCore.Qt.Window)
teared_widget.show()
# Redirect windows close-event into reattachment.
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name)
def attach_tab(self, widget, name):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
self.currentWidget().show()
class _DetachableTabBar(QtGui.QTabBar):
def __init__(self, *args, **kwargs):
super(_DetachableTabBar, self).__init__(*args, **kwargs)
self._start_drag_pos = None
self._has_dragged = False
self.setMovable(True)
def mousePressEvent(self, event):
# Keep track of where drag-starts.
self._start_drag_pos = event.globalPos()
super(_DetachableTabBar, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
# If tab is already detached, do nothing.
if self._has_dragged:
return
# Detach-tab if drag in y-direction is large enough.
if abs((self._start_drag_pos - event.globalPos()).y()) >= QtGui.QApplication.startDragDistance()*8:
self._has_dragged = True
self.parent().detach_tab(self.currentIndex())
def mouseReleaseEvent(self, event):
self._has_dragged = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
widget = DetachableTabWidget()
widget.addTab(QtGui.QLabel('Tab 1'), 'tab 1')
widget.addTab(QtGui.QLabel('Tab 2'), 'tab 2')
window.setCentralWidget(widget)
window.show()
sys.exit(app.exec_())
It seems than when you accept the QCloseEvent (default behavior) the widget is hidden at the end of your closeEvent handler. But your call to show() occurs before the end of the handler. The solution is to ignore the QCloseEvent.
...
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name, event)
def attach_tab(self, widget, name, event):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
:param event: close Event
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
event.ignore()

Python Kivy ListAdapter text wrap inside Popup

i've been trying for some time to wrap the text from a list and i don't know how to wrap the text of a simple Label.
I have a list of strings, and i would like to show it with a ListView inside a Popup, and wrapped
class SomeClass(Screen):
def __init__(self, **kwargs):
super(SomeClass, self).__init__(**kwargs)
# TextInput + ListView
...
# Popup
lv_adapter = SimpleListAdapter(data=string_list,cls=Label)
lv = ListView(adapter=lv_adapter)
self.popup = Popup(title='A very large title ..... ....... .... ....',
content=lv,size_hint=[.9,.8])
def some_function(self, *args):
if <some_condition>:
self.popup.open()
Image of the extended Popup
http://oi60.tinypic.com/2cyh5j8.jpg
Image of the shrunken Popup
http://oi61.tinypic.com/2hg4r4m.jpg
#
SOLUTION:
I've found an alternative to wrap text inside a ListView, using a Label inside a BoxLayout, here is the code:
class SomeClass(Screen):
def poplb_update(self, *args):
self.poplb.text_size = self.popup.size
def some_function(self):
self.popup.open()
def __init__(self, **kwargs):
super(SomeClass, self).__init__(**kwargs)
# Some code
. . .
# Popup
self.popup = Popup(title=popupTitle,size_hint=[.9,.6])
self.popbox = BoxLayout()
self.poplb = Label(text=stringPopup,font_size=25,strip=True,
text_size=self.popup.size)
self.popup.bind(size=self.poplb_update)
self.popbox.add_widget(self.poplb)
self.popup.content = self.popbox

Categories

Resources