Panel + Param: FileInput widget and #param.depends interaction - python

I can't seem to figure out the syntax for triggering a function upon someone using a FileInput widget in a Parameterized class.
I understand that FileInput isn't a param itself, but I looked at the code for it and the value attribute is a generic param.Parameter, so I thought this would work. I also tried just depending on file (#param.depends('file')).
class MyFile(param.Parameterized):
file = pn.widgets.FileInput() # should be a param.?
file_data = None
#param.depends('file.value')
def process_file(self):
print('processing file')
self.file_data = self.file.value
my_file = MyFile()
Then after using the file widget, I would expect my_file.file_data to have the same contents of self.file.value.
panel_output
Appreciate any input or if anyone can point me to appropriate docs. Thanks!
https://github.com/pyviz/panel/issues/711

You are right, in this case your 'file' variable needs to be a param, not a panel widget.
All possible options there are for setting available params are here:
https://param.pyviz.org/Reference_Manual/param.html
So in your case I used param.FileSelector():
import param
import panel as pn
pn.extension()
class MyFile(param.Parameterized):
file = param.FileSelector() # changed this into a param
file_data = None
#param.depends('file', watch=True) # removed .value and added watch=True
def process_file(self):
print('processing file')
self.file_data = self.file # removed .value since this is a param so it's not needed
my_file = MyFile()
This FileSelector is however a box to type the filename yourself. This question is related to this and gives some more explanation: Get a different (non default) widget when using param in parameterized class (holoviz param panel) So you need to change this FileSelector still to the FileInput widget, by overwriting it like this:
pn.Param(
my_file.param['file'],
widgets={'file': pn.widgets.FileInput}
)
Please note that I also added watch=True. This makes sure that changes get picked up, when your 'file' param has changes. There's a bit more explanation of this in the following question: How do i automatically update a dropdown selection widget when another selection widget is changed? (Python panel pyviz)
Can you let me know if this helped?

Related

GTK+: How can I add a Gtk.CheckButton to a Gtk.FileChooserDialog?

I want to present a folder chooser to users, and allow them to specify whether that folder should be processed recursively. I tried
do_recursion = False
def enable_recurse(widget, data=None):
nonlocal do_recursion
do_recursion = widget.get_active()
choose_file_dialog = Gtk.FileChooserDialog(use_header_bar=use_header_bar,
title=_(da_title), # _( invokes GLib.dgettext
action=Gtk.FileChooserAction.SELECT_FOLDER)
choose_file_dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL)
choose_file_dialog.add_button("_OK", Gtk.ResponseType.OK)
check_box_1 = Gtk.CheckButton("_RECURSE")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.add(check_box_1)
But that fails, and generates the warning:
Gtk-WARNING **: 14:03:31.139: Attempting to add a widget with type GtkCheckButton to a GtkFileChooserDialog, but as a GtkBin subclass a GtkFileChooserDialog can only contain one widget at a time; it already contains a widget of type GtkBox
What is a correct way to do this?
As noted above, an answer is to use set_extra_widget instead of add
check_box_1 = Gtk.CheckButton(label="Recurse source directory")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.set_extra_widget(check_box_1)
But I do not like the placement of the checkbox in the lower left corner, so I hope someone has a better answer.

Using tkinter's button and entry widgets to update instance variable

I am trying to implement a little application that logs in a user and the user after log in can add/update/delete contents to/of a textfile. Here's a rough sketch of the code I have so far:
class admin():
def __init__(self):
self.app = Tk()
.
self.name=StringVar()
update = Button(self.app,....,command=self.update)
.
.
.
def update():
#Function to take different entries using Entry widget of tkinter in another window
anotherapp = Tk()
nameentry = Entry(anotherapp,textvariable = self.name)
submitbutton = Button(anotherapp,....,command=submit)
.
.
def submit():
#Opens a file and adds entries to a textfile.
namevar = self.name.get()
# code to append to file
To explain the above, I have a class admin. Creating an instance of this class would open a window with buttons that say create, update, delete and so on. On clicking one of the buttons, the respective functions (defined in the same class) would be called (I use lambda: in case the function has arguments, but so far, it doesn't).
So in the code I've mentioned, say I click on the Update button, it should call the update function which opens another window and takes the text that has to be updated in the text file (via the Entry widget). So according to the code it'll update the value of name. On clicking submit, the function submit uses .get() to get the string value of name, and appends it to the text file.
The code executes with no error but it DOES NOT read the input from the user. Blank lines get appended to my textfile when I click on the submit button.
Now I don't understand why this isn't working. The name variable is defined in init and can be updated by the functions of the same class. I have tried a lot of things to make this work, including adding parameters in the button commands, defining name elsewhere, etc. Even though I've solved the error, I get the same result: the file gets appended with blank lines. I've also tried to make name a class variable but that doesn't work since it is declared using StringVar() which needs it to be part of a tkinter window. I think I've also tried nesting submit function inside the update function, but I don't know why that didn't work out or if I hadn't implemented it correctly.
I don't know if it has to do with the working of tkinter's StringVar() and .get() function.
I can't think of any other way to implement the situation I have at hand. I am open to taking suggestions on changing the structure of the code, as long as it is not something major major and manages to achieve the functionality that I've described.
I am sorry if I've missed something basic, cause I've only started trying out OOP in python recently. And thanks in advance for any help.

Trying to populate a window dynamically in Maya using separate py files to essentially build modules

So I'm trying build a window in Maya, that will have contents that will be populated dynamically. My folder structure is this:
/scripts/modularMenu/ <-- which contains:
init.py
modMenu.py
and a /modules/ folder
in the modules folder I have:
modList.py
mod1.py
mod2.py
mod3.py etc. etc.
In modMenu.py I tell Maya to draw the window, but also run the function that populates it based on the contents of the modules folder, with the goal being to create new modules that are, if tagged correctly, populated in the window.
import maya.cmds as cmds
import sys, os.path
from functools import partial
import modules.modList as mList
def modMenu():
if (cmds.window('modMenu', exists=True)):
cmds.deleteUI('modMenu')
else:
window = cmds.window( 'modMenu', title="Mod Menu v1.1", iconName='mRig', widthHeight=(400, 800))
cmds.scrollLayout(width=400, cr=True)
cmds.columnLayout(adj=True )
#This all needs to be generated Dynamically.
mList.populateWindow()
cmds.showWindow( window )
In modList.py I have a list of categories and a function to populate the window.
typeList = ['Type One', 'Type Two', Type Three']
def populateWindow():
for type in typeList:
cmds.frameLayout(label = type, collapsable = True, borderStyle = 'etchedIn')
cmds.text(label = type, al = 'center', height = 15)
#Need to then go through each module and import the rest of the form here, each form will have to have a tag to determine if it
#needs to go in this Category or not. Perhaps the equivalent of...
#for each mod.py in /modules folder if their tag == type then add
cmds.setParent( '..' )
What I'm trying to figure out next is one, how to safely import the contents of each separate mod1.py, mod2.py, etc etc into this modList.py file and two how to tag each separate mod.py file so its placed within the menu system correctly. Ideally I'd like to include one identical function in each mod.py file and a string that correct tags it, that I could call in the modList.py file, but I'm not sure how to correctly import from those mod files en masse to successfully call that function. Any help would be welcome.
On one level, this is pretty simple. You can always add gui elements to a Maya layout if you have a string reference to it, using the setParent() command to tell Maya where the new stuff goes.
In a case like this you just need to pass the shared layout to a bunch of functions --- it doesn't matter where they come froms -- and have each of them call 'setParent` to make the layout active and add to it. Here's an example of how it would go, using separate functions instead of separate modules -- it would not make a difference if these different functions had different module origins.
def button_section(parent):
cmds.columnLayout(adj=True)
cmds.frameLayout(label='buttons')
cmds.columnLayout(adj=True)
cmds.rowLayout(nc=2, cw = (200,200))
cmds.button(label = 'red', backgroundColor=(1,0.5,0.5), width=100)
cmds.button(label = 'blue', backgroundColor =(0.5, 0.5, 1), width=100)
def text_section(parent):
cmds.separator()
cmds.text(label = 'time:')
cmds.text(label = 'It is currently ' + str(datetime.datetime.now()))
cmds.text(label = 'main layout is ' + parent)
cmds.separator()
def object_section(parent):
cmds.columnLayout(adj=True)
cmds.frameLayout(label = 'scene')
cmds.columnLayout(adj=True, rs = 12, columnAttach = ('both', 8) )
for object in cmds.ls(type='transform'):
select_the_thing = lambda b: cmds.select(object)
cmds.button(label = object, c = select_the_thing)
def create_window(*sections):
window = cmds.window(title = 'example')
main_layout = cmds.columnLayout(adj=True)
for each_section in sections:
cmds.setParent(main_layout)
each_section(main_layout)
cmds.setParent(main_layout)
cmds.columnLayout(adj=1, columnAttach = ('both', 8))
cmds.separator()
cmds.text(label = 'here is a footer')
cmds.showWindow(window)
create_window(button_section, text_section, object_section)
If you're not familiar with the syntax the create_window function with a * takes any number of arguments. In this case it's just taking the three individual section functions. You could, however write it to just take a list of functions. In any case the logic is the same -- just setParent back to the main layout and you'll be able to add new stuff to the layout.
In this example I passed the name of the main layout into each of the different layout functions. This is useful so you can do things like get the width of a layout element that owns you, or work your way up to a higher level recursively.
In general the thing you'll have to watch out for here is designing this so that the different sections really are independent of each other. Things will get complicated fast if a button in section A needs to know the state of a checkbox in section B. However this shows you the basics of how to compose layouts in Maya.
I'd be very careful about trying to populate the menu based on the contents of your module folder -- if you delete a module but don't remember to delete the pyc file that is produced by it, you can end up with a phantom section of UI that you don't expect. It would be better to just organize the code as conventional modules and then have a simple script that asked for the modules explicitly. Then you can know exactly what to expect on a local install.
1 - you will have to use exec() or "import mod, mod.doIt()", but what is "safely" will rely on your checks
2 - Im not sure to understand. Do you want to reorder mod by number ?
if not, I supposed you can do a json to store the order or maybe store some metadata

tkinter apply method not found in module

Below you can see the code I have for a method in my GUI Class. I have been trying to create an option menu from a list however I am getting an error. It says that tkinter has no module 'apply'. In all the examples I can find people use Tkinter instead of tkinter, so has there been a change in the apply method from python 2.x to 3.x?
I have tried writing all of the following:
tk.apply, tk.Apply, apply. But nothing seems to work.
import tkinter as tk
class GUI:
def UploadHomeworkScreen(self):
self.masternew = tk.Tk()
self.framenew = tk.Frame(self.masternew)
self.HomeworkFileEntry = tk.Entry(self.framenew)
self.ClassVariable = tk.StringVar(self.masternew)
self.ClassVariable.set(Client.ListOfClasses[0])
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
self.SubmitButton = tk.Button(self.framenew, text = "Submit", command = self.SubmitHomework)
self.HomeworkFileEntry.pack(pady = 30, padx = 10)
self.ClassChoice.pack()
self.SubmitButton.pack()
self.framenew.pack()
self.masternew.mainloop()
I am open to creating the option menu another way if it is possible.
Thanks.
Note: apply() doesn't exist in Python 3. Any guide that uses it(notably, the tkinterbook on effbot.org) is horribly out of date.
Per tkinter's definition for class OptionMenu(Menubutton):
The OptionMenu is initialized as such:
def __init__(self, master, variable, value, *values, **kwargs):
"""Construct an optionmenu widget with the parent MASTER, with
the resource textvariable set to VARIABLE, the initially selected
value VALUE, the other menu values VALUES and an additional
keyword argument command."""
Taking that into account, your code line:
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
Should be changed to:
self.ClassChoice = tk.OptionMenu(self.framenew, self.ClassVariable, *Client.ListOfClasses)
Note the asterisk before Client.ListOfClasses. This is to pass in the menu VALUES needed by the OptionMenu as a list, per https://docs.python.org/3.7/tutorial/controlflow.html#unpacking-argument-lists.
The apply function has been removed in Python 3.[1] It was not a tkinter specific function. To fix this, use the first parameter of the apply function as the function name, like so:
Your code:
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
New:
self.ClassChoice = tk.OptionMenu((self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
Or as the documentation for 2to3 says, "apply(function, *args, **kwargs) is converted to function(*args, **kwargs)."

Configuring values of combobox in a tablelist using tablelist wrapper in python

Basically my problem is in configuring a combobox in a tablelist written in python (not tcl directly). I have prepared an example that could demonstrate the problem, but before that lets see the required steps to run it:
Copy the tablelist wrapper from here and save it as 'tablelist.py', then put it at the example code directory.
Download "tklib-0.5" from here and copy "tablelist" directory from "modules" directory to the directory of example code.
Here it is the code:
from tkinter import *
from tkinter import ttk
from tablelist import TableList
class Window (Frame):
def __init__(self):
# frame
Frame.__init__(self)
self.grid()
# tablelist
self.tableList = TableList(self,
columns = (0, "Parity"),
editstartcommand=self.editStartCmd
)
self.tableList.grid()
# configure column #0
self.tableList.columnconfigure(0, editable="yes", editwindow="ttk::combobox")
# insert an item
self.tableList.insert(END,('Even'))
def editStartCmd(self, table, row, col, text):
#
# must configure "values" option of Combobox here!
#
return
def main():
Window().mainloop()
if __name__ == '__main__':
main()
As you would see it results in a single column/cell window with an initial value (Even). By clicking on the cell the combobox will appear (because of using "editstartcommand") and it doesn't have any values (None). I know for editing cell's widgets one has to use "editwinpath" command to get pathname of temporary widget, but the method just returns a string of the address referring to combobox widget that is not callable.
I'd appreciate any help or possible solutions.
I know it's a bit weird that I'm responding to my own question, but I'm hopeful that it could be useful to others in the future. After reading tablelist scripts and examples in TCL, I was able to find my answer. The answer has two different parts, one is related to the wrapper (tablelist wrapper in python that you can find it here), and the other one resides in my example code. First of all the wrapper needs to be modified to include widgets' pathnames in its "configure" method of "TableList" class. Here it is the modified version:
def configure(self, pathname, cnf={}, **kw):
"""Queries or modifies the configuration options of the
widget.
If no option is specified, the command returns a list
describing all of the available options for def (see
Tk_ConfigureInfo for information on the format of this list).
If option is specified with no value, then the command
returns a list describing the one named option (this list
will be identical to the corresponding sublist of the value
returned if no option is specified). If one or more
option-value pairs are specified, then the command modifies
the given widget option(s) to have the given value(s); in
this case the return value is an empty string. option may
have any of the values accepted by the tablelist::tablelist
command.
"""
return self.tk.call((pathname, "configure") +
self._options(cnf, kw))
Second and last part is the obligation that the "editstartcommand" (in case of my example code "editStartCmd" method) has to return its "text" variable at the end of it. This trick would prevent entries from turning to "None" as you click on them. The correct form of example code is:
from tkinter import *
from tkinter import ttk
from tablelist import TableList
class Window (Frame):
def __init__(self):
# frame
Frame.__init__(self)
self.grid()
# tablelist
self.tableList = TableList(self,
columns = (0, "Parity"),
editstartcommand=self.editStartCmd
)
self.tableList.grid()
# configure column #0
self.tableList.columnconfigure(0, editable="yes", editwindow="ttk::combobox")
# insert an item
self.tableList.insert(END,('Even'))
def editStartCmd(self, table, row, col, text):
#
# must configure "values" option of Combobox here!
#
pathname = self.tableList.editwinpath()
self.tableList.configure(pathname, values=('Even','Odd'))
return text
def main():
Window().mainloop()
if __name__ == '__main__':
main()
That's it and I hope it was clear enough.

Categories

Resources