QCombo box to set layer (for Python QGIS plugin) - python

I'm attempting to create a function to declare a variable in terms of an item chosen in a QComboBox. It's for a plugin for QGIS 2.0 and 2.2. I'm getting a "list index out of range" error, but cannot see why. I'm wondering if my combobox.currentIndex() isn't giving me what I think it is. If this is the case, I wonder if I should find a way set the combo box's index to something by default before the program runs.
#connecting the combo boxes to function
def initGui(self):
QObject.connect(self.dlg.ui.indivCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)
QObject.connect(self.dlg.ui.grosCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)
QObject.connect(self.dlg.ui.resCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)
#function to set my layer parameter to the equal the item at index chosen
def layerChanged(self):
self.layerMap = QgsMapLayerRegistry.instance().mapLayers().values()
self.indivLayer = self.layerMap[self.dlg.ui.indivCombo.currentIndex()]
self.grosLayer = self.layerMap[self.dlg.ui.grosCombo.currentIndex()]
self.resLayer = self.layerMap[self.dlg.ui.resCombo.currentIndex()]
#populating combo box with layers in stack
def run(self):
# show the dialog
self.dlg.show()
for layer in self.iface.legendInterface().layers():
if layer.type() == QgsMapLayer.VectorLayer:
self.dlg.indivCombo.addItem(layer.name())
self.dlg.grosCombo.addItem(layer.name())
self.dlg.resCombo.addItem(layer.name())
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
if result == 1:
pass
I've now made some changes to the code thanks to the near-answer below. layerChanged() now uses an identifiers method and run() adds layers to combo box differently based on ideas from thread http://lists.osgeo.org/pipermail/qgis-developer/2010-November/011505.html. Both areas still give me issues however. "None type object has no attribute mapLayer" for the former and "Syntax error" for the latter.
def layerChanged(self, index):
#globals previously initialized as None
global registry, indivID, grosID, resID
registry = QgsMapLayerRegistry.instance()
indivID = self.dlg.ui.indivCombo.data(index).toPyObject()
grosID = self.dlg.ui.grosCombo.data(index).toPyObject()
resID = self.dlg.ui.resCombo.data(index).toPyObject()
self.indivLayer = registry.mapLayer(indivID)
self.grosLayer = registry.mapLayer(grosID)
self.resLayer = registry.mapLayer(resID)
def calculatelength(self):
global registry, resID
self.resLayer = registry.mapLayer(resID)
idx = self.resLayer.fieldNameIndex('Length')
#code continues
def run(self):
# show the dialog
self.dlg.show()
for layer in self.iface.legendInterface().layers():
if layer.type() == QgsMapLayer.VectorLayer:
self.dlg.ui.indivCombo.addItem(layer.name(),QVariant(layer.id())
self.dlg.ui.grosCombo.addItem(layer.name(),QVariant(layer.id())
self.dlg.ui.resCombo.addItem(layer.name(),QVariant(layer.id())
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
if result == 1:
pass
#AEPStats()

Taking example code you posted at face-value, I can see several problems.
Firstly, judging by the differences between the initGui and run methods, there may be two sets of combo-boxes in use. The signals are connected to self.dlg.ui.*Combo, whereas the items are added to self.dlg.*Combo.
Secondly, you seem to be populating the combo-boxes over and over again without clearing them beforehand.
Thirdly, you do not seem to be preserving a one-to-one relationship between the combo-box indexes and the list, because you are filtering the layers based on type.
And finally, the list of layers comes from the values of a map, so surely there is no guarantee that they will come out in the same order.
I would suggest you associate a layer id with each combo item, and then retrieve the layer via the mapLayer method. That is, add the combo items like this:
self.dlg.indivCombo.addItem(layer.name(), layer.id())
and then retrieve the layer like this:
def layerChanged(self, index):
registry = QgsMapLayerRegistry.instance()
identifier = self.dlg.ui.indivCombo.itemData(index)
self.indivLayer = registry.mapLayer(identifier)
NB: if you're using Python2, the combo data will be stored as a QVariant so you would need to extract the identifier like this:
identifier = self.dlg.ui.indivCombo.itemData(index).toString()
or this:
identifier = self.dlg.ui.indivCombo.itemData(index).toPyObject()

Thanks to help from #ekhumoro, this now works. Only changes made to answer's suggestions were in layerChanged():
def layerChanged(self):
registry = QgsMapLayerRegistry.instance()
identifier = str(self.dlg.ui.indivCombo.itemData(self.dlg.ui.indivCombo.currentIndex()))
self.indivLayer = registry.mapLayer(identifier)
This solves an issue of the index chosen getting mixed up and incorrect for the multiple combo boxes i have.

Related

How to bind different buttons to one self-defined method containing conditional statements

For school I have to code the Othello game in Python, but I'm a beginner coder and am struggling with various parts of my code. So basically, we have to make our own classes and use self-defined methods. I made my user interface in a subclass of Frame, where I constructed the buttons. The size of the board is supposed to be changeable.
I defined a list in this class and made a method that changes the board size and the positioning of the tokens for one button, but can't seem to figure out how to make this method applicable to different buttons.
I tried different things like:
#method for changing the board input positions
def klik_grootte(self, ea):
if ea.widget == self.four:
sz = 4
self.bord[1][1] = '2'
self.bord[1][2] = '1'
self.bord[2][1] = '1'
self.bord[2][2] = '2'
Reversi.drawboard(self)
elif ea.widget == self.six:
sz = 6
self.bord[2][2] = '2'
self.bord[2][3] = '1'
self.bord[3][2] = '1'
self.bord[3][3] = '2'
Reversi.drawboard(self)
#binding method to button
self.four.configure(command=self.ea.klik_grootte)
self.six.configure(command=self.ea.klik_grootte)`
This obviously doesn't work, but just so you get the idea.
I also thought it would be better to write this method in a different class but don't know how you would bind the buttons to self-defined methods in that case

How to get state of each checkbox generated through a loop, as soon as it is checked/unchecked in tkinter?

I'm trying add/remove an item (text of checkbox) to/from a list whenever a checkbox is checked/unchecked in tkinter.
My idea was to add a command to the checkbutton, like:
cb = Checkbutton(master,...,command=some_fun)
but I cannot think of a way to define the function. I was thinking the function should contain the widget attribute cget('text'), but the problem is I have many checkboxes made with the help of a loop.
I guess the question is: how can I reference the checkbox whose state got changed and is therefore calling the function some_fun?
The way I generated the checkboxes is:
cb_identities = []
for i in range(cb_max_num):
cb = Checkbutton(frame_data,bg="white")
cb_identities.append(cb)
And then I'm dynamically changing them depending on some radiobuttons:
def fun_chck(): #shows or hides checkbuttons based on radiobutton input
data = read_data(rb_var.get())
for i in range(cb_max_num):
cbname = (cb_identities[i])
if len(data)-1 < i:
cbname.grid_forget()
else:
cbname.config(text=data[i]) #I would place some_fun here, which gets text option of checked box
cbname.grid(row=i,column=1,sticky=W)
Update! I managed with the following code for anyone interested:
cb_var_init = [0] * cb_max_num #create the initial list of inactive checkbuttons, all 0
input_params=[] #list which needs to be populated/depopulated based on checkbutton state
def get_data(data): #populates a list with parameter from checked checkbuttons,
global cb_var_init
cb_var_list = list(map(lambda var: var.get(),list(cb_var.values())))
for i in range(len(data)):
if cb_var_list[i] > cb_var_init[i]:
input_params.append(data[i])
elif cb_var_list[i] < cb_var_init[i]:
input_params.remove(data[i])
cb_var_init = cb_var_list
return(input_params)
cb_var is a dictionary of IntVars, and data is a list of checkbuttons' names.
As for the command on each checkbutton, I used cbname.config(text=data[i],command=lambda: get_data(data)) as suggested in another topic for functions with arguments.
Now each time I check a checkbutton, I immediately get a list of parameters which should show in the next Frame, which is dynamically updated.

How to destroy comboboxes if they are available in python gui?

I have a function, which based on the count generates the comboboxes. I want to destroy any combobox which is available already whenever my count variable changes. I used winfo_exists to do this...but it throws an attribute error every time. Please help me with this.
Here is the code of that function:
def create(event):
count = combo.current()
print ("count")
print(count)
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
#create_combo.set("Sheet " + str(i))
create_combo[i].grid(column = i, row =4, padx=10,pady=10)
To delete the widgets which are created in loop, can be deleted by using the method available in this link
Python Tkinter :removing widgets that were created using a for loop
This worked for me... I dont understand why winfo_exists didn't work.
Anyway Thanks!!
list_of_owner_widgets = []
def create(event):
count = combo.current()
print(count)
for widget in list_of_owner_widgets:
widget.destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
list_of_owner_widgets.append(create_combo[i])
create_combo[i].grid(column = i, row =4, padx=10,pady=10)
If you wish to destroy a Python widget, be it a Checkbox in your case, you use the following code.
It is a lot easier to remove and show widgets using the .grid method!
Your code:
create_combo[i].destroy()
I assume (as I can see further down the code file) that you used the grid method. In which case I would simply change the code to:
create_combo[i].grid_forget()
Hope This Helps!
From your post:
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
And the error:
AttributeError: 'str' object has no attribute 'winfo_exists'
I can infer that:Your create_combo must be a list full of string(Instead of Combobox widget).
You could add print(create_combo) before the first for loop to check the value in create_combo.It must be a list full of string.
And it seems that your problem is not here,you should check the way how you create the create_combo.
lets assume create_combo = ['a','b','c']. So I am creating three comboboxes create_combo[0...2]. So name of the comboboxes(widgets) is a, b, c.
No,you couldn't.
If really want to get a list of comboboxes you create,you should use:
create_combo = []
for i in range(3):
t = ttk.Combobox(xxxxx)
t.grid(xxxxxx)
create_combo.append(t) # append it to your create_combo
And then,you could use:
for i in create_combo:
if i.winfo_exists(): # i should be a widget,not string
xxxxxxx # your job

Controlling dynamic properties in Shady according to video frames not time

I am trying to use Shady to present a sequence of image frames. I'm controlling the flow from another machine, so that I first instruct the machine running Shady to present the first frame, and later on to run the rest of the frames.
I create a World instance, and attach to it an animation callback function. Within this callback I listen for communications from the other machine (using UDP).
First I receive a command to load a given sequence (stored as a numpy array), and I do
def loadSequence(self, fname):
yy = np.load(fname)
pages = []
sz = yy.shape[0]
for j in range(yy.shape[1]/yy.shape[0]):
pages.append(yy[:, j*sz:(j+1)*sz])
deltax, deltay = (self.screen_px[0] - sz) / 2, (self.screen_px[1] - sz) / 2
if (self.sequence is None):
self.sequence = self.wind.Stimulus(pages, 'sequence', multipage=True, anchor=Shady.LOCATION.UPPER_LEFT, position=[deltax, deltay], visible=False)
else:
self.sequence.LoadPages(pages, visible=False)
When I receive the command to show the first frame, I then do:
def showFirstFrame(self, pars):
self.sequence.page = 0 if (pars[0] == 0) else (len(self.sequence.pages) - 1)
self.sequence.visible = True
But what do I do now to get the other frames to be be displayed? In the examples I see, s.page is set as a function of time, but I need to show all frames, regardless of time. So I was thinking of doing something along these lines:
def showOtherFrames(self, pars, ackClient):
direction, ack = pars[0], pars[2]
self.sequence.page = range(1, len(self.sequence.pages)) if (direction == 0) else range(len(self.sequence.pages)-2, -1, -1)
But this won't work. Alternatively I thought of defining a function that takes t as argument, but ignores it and uses instead a counter kept in a global variable, but I'd like to understand what is the proper way of doing this.
When you make s.page a dynamic property, the function assigned to it must take one argument (t), but you can still just use any variables in the space when defining that function, and not even use the time argument at all.
So, for example, you could do something as simple as:
w = Shady.World(...)
s = w.Stimulus(...)
s.page = lambda t: w.framesCompleted
which will set the page property to the current frame count. That sounds like it could be useful for your problem.
Your global-variable idea is one perfectly valid way to do this. Or, since it looks like you're defining things as methods of an instance of your own custom class, you could use instance methods as your animation callbacks and/or dynamic property values—then, instead of truly global variables, it makes sense to use attributes of self:
import Shady
class Foo(object):
def __init__(self, stimSources):
self.wind = Shady.World()
self.stim = self.wind.Stimulus(stimSources, multipage=True)
self.stim.page = self.determinePage # dynamic property assignment
def determinePage(self, t):
# Your logic here.
# Ignore `t` if you think that's appropriate.
# Use `self.wind.framesCompleted` if it's helpful.
# And/or use custom attributes of `self` if that's
# helpful (or, similarly, global variables if you must).
# But since this is called once per frame (whenever the
# frame happens to be) it could be as simple as:
return self.stim.page + 1
# ...which is indefinitely sustainable since page lookup
# will wrap around to the number of available pages.
# Let's demo this idea:
foo = Foo(Shady.PackagePath('examples/media/alien1/*.png'))
Shady.AutoFinish(foo.wind)
Equivalent to that simple example, you could have the statement self.stim.page += 1 (and whatever other logic) inside a more-general animation callback.
Another useful tool for frame-by-frame animation is support for python's generator functions, i.e. functions that include a yield statement. Worked examples are included in python -m Shady demo precision and python -m Shady demo dithering.
It can also be done in a StateMachine which is always my preferred answer to such things:
import Shady
class Foo(object):
def __init__(self, stimSources):
self.wind = Shady.World()
self.stim = self.wind.Stimulus(stimSources, multipage=True)
foo = Foo(Shady.PackagePath('examples/media/alien1/*.png'))
sm = Shady.StateMachine()
#sm.AddState
class PresentTenFrames(sm.State):
def ongoing(self): # called on every frame while the state is active
foo.stim.page += 1
if foo.stim.page > 9:
self.ChangeState()
#sm.AddState
class SelfDestruct(sm.State):
onset = foo.wind.Close
foo.wind.SetAnimationCallback(sm)
Shady.AutoFinish(foo.wind)

How to efficiently code for updating many variables in a script using Tkinter?

I am designing a Tkinter interface to be compatible with my datalogging script. Both are still WIP, but close to getting merged. This script is used for a Raspberry Pi to periodically make measurements using different sensors. In this case, of plants.
I have various Entry widgets to access my variables (input channels, measure interval and measurements per average value ), so i can change them without reprogramming the script itself.
3 entry widgets are associated with the textvariables:
gpio_light1_entry, interval_light1_entry, amount_light1_entry
The actual variables used by the datalogging script are:
gpio_light1, interval_light1, amount_light1
I want to define a function (That i will bind to a button.). That retrieves the entry window value (gpio_light1_entry.get()) and that updates the script variable.
Now I could just use:
gpio_light1 = gpio_light1_entry.get()
However I have at least 12 variables per plant. So coding it like this for some 12 times seems very inefficient to me.
I was thinking of using a for loop and lists.
settings_gpio1 = [gpio_light1, gpio_temp1, etc]
settings_gpio1_entry = [gpio_light1_entry, gpio_temp1_entry, etc]
But this had some problems:
1- It seems that changing a value in a list, does not change the variable used to construct the list.
2- I do not know how to make a "double" for loop to use both the _entry and non entry lists.
3- The _entry list needs a .get() function to retrieve the values, this function does not work on lists directly, but can be solved with a for loop.
Does anyone know a more efficient or easier way to reach my goal?
I advise you to create a class for each Gpio access:
The class it self should hold the values for accessing the hardware and displaying it.
If, for some reason, you already have the list of available gpios and entries, you can do some thing like this:
class Gpio:
def __init__(self,setting, entry):
self.setting = setting
self.entry = entry
def Get(self):
#Do whatever you need with self.entry
return 0
def __str__(self):
return self.setting
#staticmethod
def FromArray(names,settings, entries):
assert(len(settings) == len(entries) == len(names))
ret = {}
for i in range(len(settings)):
ret[names[i]] = Gpio(settings[i], entries[i])
return ret
Here I put a random example, I don't know what values you are going to use:
gpio_light1 = 33
gpio_light_entry = "hardware_address"
gpio_temp_entry = "0x80001234"
names = ["gpio_light1", "gpio_temp1"]
settings_gpio = [gpio_light1, "gpio_temp1"]
settings_gpio_entry = [gpio_light_entry, gpio_temp_entry]
access = Gpio.FromArray(names, settings_gpio, settings_gpio_entry)
print access["gpio_light1"].Get()

Categories