Kivy class in .py and .kv interaction 2 - python

Follow up from Kivy class in .py and .kv interaction , but more complex.
Here is the full code of what I'm writing:
The data/screens/learnkanji_want.kv has how I want the code to be, but I don't fully understand how the class KanjiOriginScreen() plays it's role in screen management.
data/screens/learnkanji.kv works how I want it, but for this to work I have to put keyb_height in class KanjiOriginScreen() (main.py). However I want that code to be in the class LayoutFunction() (learnkanji.py).
Question
How can I put keyb_height in the function LayoutFunction() and access this in the .kv file in <LayoutFunction>?
Could you also explain why KanjiOriginScreen: can be put in learnkanji.kv without < > and the program still recognizes it should use this?
If anything is unclear, please ask :)
Edit
I found out that I didn't import the learnkanji.py in the learnkanji.kv file and that caused that it couldn't find the class 'LayoutFunction'.
#:import learnkanji data.screens.learnkanji

To answer your questions:
The way you are doing it should work. You should be able to access object attributes from kv. If your attribute is going to change, however, and you want the UI to update when it does, you should use Kivy Properties. If it is constant, a normal attribute works fine.
From the Kivy Docs, <Widget>: is a class rule that will be applied to every instance of that class. Widget: will create an actual instance of that class (in this case it is your root widget).
As for ScreenManager and Screens, you can think of them this way. Each Screen is it's own individual UI (it's own root widget). The screen manager is a container that holds your Screen and can swap between different Screens. This lets you create separate UIs that you can toggle between. Each UI is a separate widget tree with a Screen at its root. The docs are actually pretty good at describing ScreenManager.

How can I put keyb_height in the function LayoutFunction() and access this in the .kv file in ?
You can't do this with a function. You need to make LayoutFunction into a class to do this. Like so:
main.py
class LayoutClass(BoxLayout): # I made it a boxlayout, you could make it anything you want
keyb_height = NumericProperty(260) # from kivy.properties import NumericProperty
kv file:
<LayoutClass>: # can only access it this way if it's a class in main.py
something: root.keyb_height
Could you also explain why KanjiOriginScreen: can be put in learnkanji.kv without < > and the program still recognizes it should use this?
It sounds like you're asking how you can achieve this.. but I can't think why?
Unless you want it managed by a ScreenManager perhaps? However, the only way you can have KanjiOriginScreen within the kv file without the <> is if it is inside another root widget. For instance, see Testy and ScreenTwo as they are in the kv file under <Manager> in my answer to your other question(here). They are without <> because they are class instances, WITHIN another class(the Manager class). Only root widgets have the <> around them in the kv file. If none of this makes sense to you, you need to do a tutorial on kivy.
Check out this tutorial I made a while back, it explains a little about root widgets in kv(at around 4.30).

Sorry I was not clear with my question, but with the help on IRC on #Kivy I ended up with the following:
learnkanji.py
class LayoutFunctioning(BoxLayout):
keyb_height = NumericProperty(260)
learnkanji.kv
KanjiOriginScreen:
name: 'LearnKanji'
fullscreen: True
LayoutFunction:
id: lfunc
#...code...
height: lfunc.keyb_height #Instead of root.keyb_height
Now I understand how to use the id, I can use lfunc to call my code in LayoutFunction() :)

Related

Python3.8 with PySide2 Class to Class Usage

My GUI essentially wraps various backend PowerShell scripts that perform some automated functions. Kind of beside the point, but alright, here's where I'm stuck at.
I've got my interface designed in Qt Designer, outputted to a .ui file, converted to a .py file via PySide2-UIC, and a mainwindow class that is a subclass of the main window class I created in Qt Designer. All is well. No issues with any of that.
I'm now on to a part in my programming that I'm capturing form data from QWidgets (which is working) to a list. I've got a completely custom written class that is meant to handle taking that user input, setting other variables like filenames or paths to certain configuration files that are needed, and executing a subprocess PowerShell command with all of that information. Where I'm stuck at is trying to determine what the right place is to instantiate this custom object, inside my MainWindow class, outside my MainWindow class? But if so, where? Here's some simplified code to help explain my dilemma.
Interface Sequence
App start
MainWindow appears
User browses to form with input controls
User enters info like (IP address, username, password)
User clicks button that is connected to a method in the class
Method recurses through the child widgets on the page and captures info into a dictionary via finding qLabels and qLineEdit (buddies)
Questions:
How do I call the next method (only once even though the capturing of data is recursive)? I'm thinking about just connecting the signal to a second method that handles taking the captured data and sending/setting it into the custom class object. However, when I instantiate my custom object inside of the MainWindow class and I try to reference the object by self.customObject.sendUsesrInput(self.userInputVariable), PyCharm doesn't think self is defined inside this particular method. It doesn't properly highlight the word "self" like in the rest of the class definition, and it suggests that I need to import self.
Update
I was able to clear the errors around "import self" in PyCharm. It had something to do with improper spaces vs. tabs, even though I only ever use the tab key to do indentation. Might need to go and check my inpection settings closer. The other questions still stand though. Where is the best place to call methods on my custom class to "form a command", and "run a command", should that be executed by the mainWindow class, or should I set a flag on the customObject class that then triggers those other actions? Or more generally, should an object be in charge of executing it's own functions/methods, something tells me not usually, but I can't be sure. Also, if there are any books on the matter, I'd be happy to do my own research. I'm currently reading "Rapid GUI Programming" but not sure if this topic is covered in the later chapters just yet.
So I guess my question is, where do I handle the customObject class, in the mainWindow class, or in some other place? If so, where?
I apologize if this question is NOT clear. I promise to update as necessary to help work through this.
Here's come simplified code examples:
class customClass(object): # this is actually in a separate file but for argv sake
def __init__(self):
self.userInput = ""
self.file1 = ""
self.file2 = ""
self.otherstuff...
def setUserInput(self, uinput):
self.userInput = uinput
def dostuffwithdata(self):
# method to execute subprocess command
class MainWindow( QMainWindow ):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.customObject = customClass.customCmdClass()
self.cmddata = dict()
self.ui.toolButton.clicked.connect(self.getformdata)
def getformdata(self):
# recurses through QWidgets and grabs QLabels and QLineEdit.Text() and updates dict()
for w in widgets:
if w is qlabel:
k = w.text()
v = w.buddy().text()
self.cmddata.update({k: v})
""" all the above works fine. what doesn't work is this part"""
# at this point I want to send the collected data to the customObject for processing
def senddatatocustomObject(self):
self.customObject.setUserInput(self.cmddata) """but this says that 'self' isn't defined.
I know it has to be because of the object in an object, or something I'm doing wrong here.
**Update**: figured this out. PyCharm was freaking out about some sort of
perceived indentation error despite there not appearing to actually be one.
Was able to correct this. """
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
In an effort to close this out, I offer this answer to my previously posted question about where to put the "logic" and flow in my code.
Given that this is a graphical application without any back-end services, it makes the most sense to put most of the user-interaction logic and flow within the MainWindow object because that is essentially the control center of my program. When a user clicks or enters something, it is going to happen on the MainWindow, when a state changes, it happens (mostly) on the MainWindow or is directly tied to the MainWindow in some way. Therefore, it makes sense to include the majority of my method calls, user-input-flow logic, and other code, in the MainWindow class/object of my program.
My other classes and objects are there to capture state and to perform actions on different sets of data, but in most cases, these auxiliary classes/objects will/should be controlled by the MainWindow of my application.
This is certainly not the only way to write this application or others, but I believe this at least answers my previously posted question(s).

How To Replace Kivy Widgets On Callback?

I'm new to Python and Kivy, and I'm trying to create multipage display of letters of the braille alphabet, with the corresponding braille's letter picture present in every page. I really want to learn more about creating Kivy desktop apps. I really hope you can help me. What I'm trying to do is have a page look like this:
I know how images and buttons are placed and customized in terms of size and position in the KV file. However what I need to learn is how add_widget() and clear_widget() will factor in this. I have read the Kivy docs but they barely explain how I could achieve what I need. What I thought of doing is using the from kivy.uix.screenmanager import ScreenManager, Screen feature, and then just create 26 screens and route them via on_click in the kv file. But that's tedious and too manual. Here's my code so far:
class LetterAScreen(Screen):
pass
class LetterBScreen(Screen):
pass
class LetterCScreen(Screen):
pass
class LetterDScreen(Screen):
pass
class LetterEScreen(Screen):
pass
class LetterFScreen(Screen):
pass
class LetterGScreen(Screen):
pass
#.... so and so until Letter Z
sm = ScreenManager(transition=SwapTransition())
#LearnScreen - Alphabet
sm.add_widget(LetterAScreen(name='lettera'))
sm.add_widget(LetterBScreen(name='letterb'))
sm.add_widget(LetterCScreen(name='letterc'))
sm.add_widget(LetterDScreen(name='letterd'))
sm.add_widget(LetterEScreen(name='lettere'))
sm.add_widget(LetterFScreen(name='letterf'))
sm.add_widget(LetterGScreen(name='letterg'))
sm.add_widget(LetterHScreen(name='letterh'))
sm.add_widget(LetterIScreen(name='letteri'))
sm.add_widget(LetterJScreen(name='letterj'))
sm.add_widget(LetterKScreen(name='letterk'))
sm.add_widget(LetterLScreen(name='letterl'))
sm.add_widget(LetterMScreen(name='letterm'))
sm.add_widget(LetterNScreen(name='lettern'))
sm.add_widget(LetterOScreen(name='lettero'))
sm.add_widget(LetterPScreen(name='letterp'))
sm.add_widget(LetterQScreen(name='letterq'))
sm.add_widget(LetterRScreen(name='letterr'))
sm.add_widget(LetterSScreen(name='letters'))
sm.add_widget(LetterTScreen(name='lettert'))
sm.add_widget(LetterUScreen(name='letteru'))
sm.add_widget(LetterVScreen(name='letterv'))
sm.add_widget(LetterWScreen(name='letterw'))
sm.add_widget(LetterXScreen(name='letterx'))
sm.add_widget(LetterYScreen(name='lettery'))
sm.add_widget(LetterZScreen(name='letterz'))
I haven't gotten around the kv file because i'm clueless how this will pan out. What I need to do is create widgets or a function that will swap out the images of the current letter and display those of the next or previous ones when the next/button is clicked, without having to switch screens every single time. I'm really unfamiliar with how functions work in Kivy and Python. I hope you could help me. Thank you.
Here is a simple solution to your problem. I'll leave it to you to modify and make it look and work exactly how you want :)
Learning the kv language is INCREDIBLY helpful, easy, and it can be picked up quite quickly.
main.py
from kivy.app import App
class MainApp(App):
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def next_letter(self):
# Get a reference to the widget that shows the letters
# self.root refers to the root widget of the kv file -- in this case,
# the GridLayout
current_letter_widget = self.root.ids['the_letter_label']
# Get the letter currently shown
current_letter = current_letter_widget.text
# Find the next letter in the alphabet
next_letter_index = self.alphabet.find(current_letter) + 1
next_letter = self.alphabet[next_letter_index]
# Set the new letter in the widget that shows the letters
current_letter_widget.text = next_letter
MainApp().run()
main.kv
GridLayout: # This is the `root` widget of the main app class
cols: 1
Label:
text: "g"
id: the_letter_label # Setting an id for a widget lets you refer to it later
Button:
text: "Previous"
Button:
text: "Next"
on_release:
# the keyword `app` references the main app class, so we can call
# the `next_letter` function
app.next_letter()
I'm happy to address specific questions if you have them.

kivy find all TextInputs

I was trying to register all TextInputs and Spinner in my GUI.
Each of these does have a variable gid.
The problem with the root_widget.walk() method is that I have different tabs and it does not load the widgets that have not been displayed yet.
However this is not my biggest problem.
The thing is that the walk()method does only show Widgets and not TextInputs nor Spinner.
My question now is: How do you traverse EVERY object (TextInputs/Spinner etc.) including those that have not been displayed yet (in a different tab)
I am very happy for any kind of help or advice.
Greetings, Finn
The walk() widget method does visit all child widgets (including TextInput and Spinner. If you are using TabbedPanel as your root widget, then the following will register all your widgets (if it is called by a binding to on_draw):
registered = False
def on_draw(*args):
global registered
if registered:
return # just to avoid running this many times
registered = True
app = App.get_running_app()
for tab in root_widget.tab_list: # assumes root_widget is a TabbedPanel
if tab.content is not None:
for widget in tab.content.walk():
app.register_widget(widget)
A similar construct could be done if you are using a ScreenManager as your root widget.

Kivy Framework - Most pythonic solution with TextInput/Label Updating?

I've been working with Kivy and Python 3 and I've run across a problem. I have 2 widgets in a BoxLayout, one a TextInput widget and one a Label widget. When some text is entered into the TextInput widget and the enter key is pressed, I would like Label.text to update to reflect TextInput.text.
I've put together a solution that works. Here is the code (question after the break):
from kivy.app import App
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class TexttestApp(App):
def on_enter(self,textin):
self.lab.text = textin.text #is this incorrect?
def build(self):
window = BoxLayout()
self.lab = Label(text="Inital Label") #is this incorrect?
text = TextInput(multiline=False)
text.bind(on_text_validate=self.on_enter)
window.add_widget(text)
window.add_widget(self.lab)
return window
My questions are as follows:
Is assigning the Label widget to an instance variable a bad programming practice? From a software engineering point of view, is this bad/confusing? Or should I be assigning all of my widgets to the instance of the TexttestApp class? (i.e. self.text, self.window, etc). The code right now looks disorganized to me, but I can't figure out another way of solving the problem.
Thanks in advance. This is my first attempt at using bind() to attach a function to a keyboard event.
This all looks fine to me. I suppose in principle I could nitpick stuff, but there's really nothing very important in such a small code snippet, since you aren't doing anything really wrong. The stuff you comment is fine, in general terms, and there's no rule against storing stuff as attributes of your app although there may be better or more convenient alternatives (as below).
From a kivy point of view, the biggest thing is probably...use kv language! In this case, you could have a file texttest.kv with
BoxLayout:
TextInput:
multiline: False
on_text_validate: the_label.text = self.text
Label:
id: the_label
text: "Initial Label"
This would replace both methods of your app class. It's quite similar to your example in length, since it's very simple, but I'd say its already a little clearer - and kv rapidly becomes much clearer and less verbose as things become more complicated, since it takes care of a lot of bindings automatically.
This example happens to also avoid binding to your own function to change the label text, since it can all be done in a line of kv, but your way isn't wrong and it might still be appropriate to call a method or function in the python file if the task is more complex.

How to create an own type for a setting in Kivy?

I'd like to extend a Kivy settings panel to pick a color for some lines / backgrounds etc. for my app.
I've added a new type for a ColorPicker to a Kivy settings panel and it is almost working. But I have the problem that the value of the currently picked color is not displayed in the settings panel. (missing text see red circle in below screenshot)
I think it's a simple problem but I haven't found a fix yet. My Class has the name SettingColorPicker (base class SettingItem) and if I rename it to SettingString and remove the original SettingString class than it works as expected. I don't know why?!
I registered the new type like this:
s = Settings()
s.register_type('colorpicker', SettingColorPicker)
The rest of my code is pretty simillar to Class SettingPath code. I've only created a color picker instead of the FileChooser.
Where does the label at this position gets it's value? I haven't found it in the source code https://github.com/kivy/kivy/blob/master/kivy/uix/settings.py
I'd also like to change the color of the text in that label to the currently selected color. How can I do this? But that's probably easy if I unterstand where the label is created.
And here is how it looks like if I click on the text 'BG color' (it's the ColorPicker that's included in Kivy):
You can find the source code here:
https://gist.github.com/AWolf81/421976e65099d3e58a32
You can run it directly to see the described problem.
OK, as expected the fix is pretty simple.
I've searched the Kivy files for SettingString and there I've found a kv file in C:\Kivy-1.8.0-py2.7-win32\kivy\kivy\data\style.kv:
731: <SettingString>:
732: Label:
733: text: root.value or ''
That's why it worked with the renamed class name. Just the KV definition was missing for the new type.
Adding the following lines fixed the issue:
from kivy.lang import Builder
Builder.load_string(
'''
<SettingColorPicker>:
Label:
text: root.value or ''
''')
I added these lines to the file where I've created the Class SettingColorPicker. You could probably add it to the 'style.kv' file directly. That should also work but I haven't tested it.
Coloring is also simple just use the root.value for the label color. Please notice that you have to convert it to a rgba tuple with kivy.utils.get_color_from_hex(root.value).

Categories

Resources