I've got several classes so I prefer not to paste any code here (if that's possible :P)
The problem:
I've created a class which creates a frame, and this frame contains a panel.
In another class I've stored all my settings.
On the panel are several sizers and among other attributes, it has a grid.
This grid is build from sequence 1 on the x axis and sequence y on the y axis.
To create my panel, I've divided my code into sections (like buildLeft() buildRight() buildTopRight() and so on which are linked to a main sizer in the buildFrame() method).
My grid is created in the buildTopRight() section of this class. It creates the grid by retrieving the values for sequence1 and sequence2 from the settings object and creates a grid of the length of this sequence accordingly. After this is done, the grid is bound to the sizer for the topRight section.
I also have a dropdown list (wx.Choice). If i select another option from this list, I want to remove an item from my sequence 1 and sequence 2.
The code to do this already works, and the data in my settings object changes accordingly.
However, I'm not able to reload the matrix, since if i call the buildTopRight() method again, the matrix is recreated and cropped to the topleft side of my screen, while leaving the old matrix in place.
Please help.
On request, this is the code for building my panel:
# import modules
import wx
import wx.grid
import matrixSettings as ms
# Panel class
class ResultatenPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id, style=wx.BORDER_SUNKEN)
# link settings object
self.matSet = self.GetGrandParent().ms
# build the main sizers
self.sizerMain = wx.BoxSizer(wx.HORIZONTAL)
self.sizerMenu = wx.BoxSizer(wx.VERTICAL)
self.sizerRight = wx.BoxSizer(wx.VERTICAL)
self.sizerTopRight = wx.BoxSizer(wx.HORIZONTAL)
self.sizerBotRight = wx.BoxSizer(wx.HORIZONTAL)
# make individual parts
self.buildMenu()
self.buildTopRight()
self.buildBotRight()
self.buildRight()
# build total frame
self.buildFrame()
build right code (includes top and bottom right bits):
def buildRight(self):
self.sizerRight.Add(self.sizerTopRight, 5)
self.sizerRight.Add(self.sizerBotRight, 2)
the code to build the frame:
def buildFrame(self):
self.sizerMain.Add(self.sizerMenu, 1)
self.sizerMain.Add(self.sizerRight, 5, wx.EXPAND)
self.SetSizer(self.sizerMain)
top right code:
def buildTopRight(self):
self.grid = wx.grid.Grid(self)
print "buildTopRight called"
if self.matSet.getAlgoritme() == "Needleman-Wunsch":
self.matSet.setSeq1("-" + self.matSet.getSeq1())
self.matSet.setSeq2("-" + self.matSet.getSeq2())
# set grid
self.grid.CreateGrid(len(self.matSet.getSeq2()), len(self.matSet.getSeq1()))
self.grid.SetRowLabelSize(25)
self.grid.DisableDragColSize()
self.grid.DisableDragRowSize()
# set the grid proportions accurately
for x in range(0, len(self.matSet.getSeq1())):
# set the grid proportions accurately
for y in range(0, len(self.matSet.getSeq2())):
self.grid.SetRowSize(y, 25)
self.grid.SetRowLabelValue(y, self.matSet.getSeq2()[y].upper())
self.grid.SetCellValue(y, x, "0")
self.grid.SetReadOnly(y, x, True)
self.grid.SetColSize(x, 25)
self.grid.SetColLabelValue(x, self.matSet.getSeq1()[x].upper())
newly added:
self.sizerTopRight.Clear()
self.sizerTopRight.Add(self.grid, 1)
self.Update()
self.Layout()
I know this is a not very memory friendly solution.
You should hide the current grid and create a new one:
self.sizerTopRight.Hide(self.grid)
self.sizerTopRight.Add(self.new_grid, 1)
self.sizerTopRight.Show(self.new_grid)
self.Layout()
Related
I am trying to implement an interactive dashboard in Bokeh with a "play" function that loops through all value pairs for two indicators selected by widgets.
Screen cap of dashboard
While the loop works, the dashboard resets the axis values for each step of the loop. So what I need is to set axis values based on the widget.value selected. To this end, I have built a data frame "ranges" that has the name of the indicator as index and the min/max value for each indicator as columns.
The updates for controls work thusly (x_axis,etc. are the names of the widgets):
controls = [x_axis, y_axis, start_yr, end_yr, years]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
The update function is supposed to update the ranges upon change in the controls like this:
def update():
p.x_range = Range1d(start = ranges.loc[x_axis.value,"Min"],
end = ranges.loc[x_axis.value,"Max"])
p.y_range = Range1d(start = ranges.loc[y_axis.value,"Min"],
end = ranges.loc[y_axis.value,"Max"])
What should happen: Whenever I change the value of the widget, the ranges should update, but other than that, they should remain constant
What does happen: The ranges are set based on the value of the widget initially set and don't change on update.
I've tried to find examples trying to achieve something similar but no luck.
This is a working example:
import numpy as np
from bokeh.plotting import figure
from bokeh.models import Range1d
from bokeh.io import curdoc
x = np.linspace(0, 100, 1000)
y = np.sin(x)
p = figure(x_range=(0, 100))
p.circle(x, y)
def cb():
# this works:
p.x_range.start += 1
p.x_range.end += 1
# this also works:
#p.x_range = Range1d(p.x_range.start+1, p.x_range.end+1)
curdoc().add_periodic_callback(cb, 200)
curdoc().add_root(p)
Can I change the scrollregion on a Python turtle canvas? I want the drawing to move with it, not just the coordinates to shift. The appearance I'm going for is side-scroller like, where the screen's display region moves to center the turtle onscreen.
I've tried using turtle.setworldcoordinates(llx, lly, urx, ury), but, from the documentation, "This performs a screen.reset()". I've also looked at this SO question , but this involves scroll bars, will not center the turtle easily, and has a limited canvas space. What I'm looking for is something that:
Moves the display region to center the turtle
Also moves the drawing
Has an infinite scroll region
does not display scroll bars
can be called quickly with a function
My best guess would be to be able to have an infinite scrolled canvas somehow, then hide the scroll bars and set them according to turtle position.
Is this possible in Python 2.7? I don't mind if it uses tkinter as well.
EDIT: 6-3-15
I found the canvas.xview and canvas.yview functions, but they don't seem to work once I define screen = turtle.TurtleScreen(canvas), and TurtleScreen has no xview or yview functions. I can't seem to make this work.
Then I found turtle.ScrolledCanvas(). This seems ideal except it has no methods for setting scroll manually from the program. Can I set the scroll manually on a turtle.ScrolledCanvas()???
The position of a canvas can be changed without a reset using canvas.place() method. It will move the turtle and the drawings too, so the turtle needs to be relocated after every move.
The next code moves the canvas with Left and Right arrows and draws a circle with Space, while keeping the turtle in the center. No ScrolledCanvas needed, just a very large standard canvas:
import turtle
import Tkinter as tk
def keypress(event):
global xx, canvas, t, speed
ev = event.keysym
if ev == 'Left':
xx += speed
else:
xx -= speed
canvas.place(x=xx)
t.setposition((-canvas.winfo_width() / 4) - (xx + 250), 0)
return None
def drawCircle(_):
global t
t.pendown()
t.fillcolor(0, 0, 1.0)
t.fill(True)
t.circle(100)
t.fill(False)
t.fillcolor(0, 1, 0)
t.penup()
# Set the main window
window = tk.Tk()
window.geometry('500x500')
window.resizable(False, False)
# Create the canvas. Width is larger than window
canvas = turtle.Canvas(window, width=2000, height=500)
xx = -500
canvas.place(x=xx, y=0)
# Bring the turtle
t = turtle.RawTurtle(canvas)
t.shape('turtle') # nicer look
t.speed(0)
t.penup()
t.setposition((-canvas.winfo_width() / 4) - (xx + 250), 0)
# key binding
window.bind('<KeyPress-Left>', keypress)
window.bind('<KeyPress-Right>', keypress)
window.bind('<KeyPress-space>', drawCircle)
drawCircle(None)
speed = 3 # scrolling speed
window.mainloop()
Having a real infinite scrolling would require to redraw every item in the canvas every time with the required offset, instead of actually moving or scrolling the canvas. Functions like create_image() can give the illusion of movement with static backgrounds, but it resets the drawings.
Total pyqt novice here. Trying to automatically modify the widget to display different options when user selects certain option in combobox 2. IE if user selects 'Cliff Erosion' or 'Dune Erosion', I want the widget to refresh with additional comboboxes. Likewise, if they select back to 'Rising Tides' or 'Coastal Flooding' I'd like the widget to go back to original call. How do I refresh the widget?
from PyQt4.QtGui import *
# Create window
class Window(QWidget):
#This block adds features into the window init
def __init__(self):
QWidget.__init__(self)
self.setWindowTitle('Monterey Bay Sea Level Rise')
self.resize(300, 240)
self.addWidgets1()
def addWidgets1(self):
#Add drop-down list for selecting forecast year
self.year_lbl = QLabel("1. Select Forecast Year", self)
self.year_lbl.move(5,0)
year = QComboBox(self)
year.addItem('2030')
year.addItem('2060')
year.addItem('2100')
year.move(5,20)
#Add drop-down list for selecting hazard
self.hazard_lbl = QLabel("2. Select Coastal Hazard", self)
self.hazard_lbl.move(5,50)
hazard = QComboBox(self)
hazard.addItem('Rising Tides')
hazard.addItem('Coastal Storm Flooding')
hazard.addItem('Cliff Erosion')
hazard.addItem('Dune Erosion')
hazard.activated[str].connect(self.addWidget2)
hazard.move(5,70)
#Add drop-down list for inputing model intensity (s1,s2,s3)
self.intensity_lbl = QLabel("3. Select Intensity", self)
self.intensity_lbl.move(5,100)
intensity = QComboBox(self)
intensity.addItem('Low')
intensity.addItem('Mid')
intensity.addItem('High')
intensity.move(5,120)
def addWidget2(self,text):
#if hazard is cliff erosion or dune erosion we want to update the widget
#... to include wstorm,long_term AND no_change,stormier
if text == 'Cliff Erosion' or text == 'Dune Erosion':
print 'Hi'
self.type_lbl = QLabel("3. Select type of changes", self)
self.type_lbl.move(5,150)
types = QComboBox(self)
types.addItem('Long-term')
types.addItem('Storm induced')
types.move(5,180)
self.storm_lbl = QLabel("4. Select for stormier", self)
self.storm_lbl.move(5,150)
storm = QComboBox(self)
storm.addItem('No Change')
storm.addItem('Stormier')
storm.move(5,180)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
#window.resize(100, 60)
window.show()
sys.exit(app.exec_())
The normal way of adding widgets to a Qt application is to use layouts. They will calculate the preferred size and location of your widgets and update this when needed, e.g. when widgets are added or when the window is resized (note what happens when you make your window smaller during execution and compare it with my solution below). I'm certain that it's possible to do all the moving and resizing yourself, but QLayouts are really the way to go and I strongly recommend you use them too.
There are a few types of QLayouts but in your case I would use a QFormLayout. To my surprise the QFormLayout does have an addRow method but no corresponding removeRow. However I find that just showing/hiding the combo boxes when needed does the trick as well. I've adapted your example below.
Finally, even just showing or hiding the last two combo boxes will cause the layout to slightly move the first three. This is caused by the fact that the 4th label is the longest of them all. I find that annoying. Perhaps a better solution is to enable/disable the combo boxes when needed. This has the additional benefit of showing the user that these options even exist. See also the code below. A second alternative may be to use a QGridLayout (instead of a QFormLayout) and use setColumnMinimumWidth to set the first column to a size that will hold all possible labels.
from PyQt4 import QtGui
# Create window
class Window(QtGui.QWidget):
#This block adds features into the window init
def __init__(self):
QtGui.QWidget.__init__(self)
self.setWindowTitle('Monterey Bay Sea Level Rise')
self.resize(300, 240)
self.addWidgets1()
def addWidgets1(self):
self.layout = QtGui.QFormLayout()
self.setLayout(self.layout)
#Add drop-down list for selecting forecast year
# You don't need to set to parent of the widgets to self anymore, the
# layout will set the parent automatically when you add the widgets
self.year_lbl = QtGui.QLabel("1. Select Forecast Year")
# self.year_lbl.move(5,0) # Can be removed. The layout takes care of it.
year = QtGui.QComboBox()
year.addItem('2030')
year.addItem('2060')
year.addItem('2100')
self.layout.addRow(self.year_lbl, year)
#Add drop-down list for selecting hazard
self.hazard_lbl = QtGui.QLabel("2. Select Coastal Hazard")
self.hazard = QtGui.QComboBox()
self.hazard.addItem('Rising Tides')
self.hazard.addItem('Coastal Storm Flooding')
self.hazard.addItem('Cliff Erosion')
self.hazard.addItem('Dune Erosion')
self.hazard.activated[str].connect(self.updateComboboxes)
self.layout.addRow(self.hazard_lbl, self.hazard)
#Add drop-down list for inputing model intensity (s1,s2,s3)
self.intensity_lbl = QtGui.QLabel("3. Select Intensity")
intensity = QtGui.QComboBox()
intensity.addItem('Low')
intensity.addItem('Mid')
intensity.addItem('High')
self.layout.addRow(self.intensity_lbl, intensity)
self.types_lbl = QtGui.QLabel("3. Select type of changes")
self.types = QtGui.QComboBox()
self.types.addItem('Long-term')
self.types.addItem('Storm induced')
self.layout.addRow(self.types_lbl, self.types)
self.storm_lbl = QtGui.QLabel("4. Select for stormier")
self.storm = QtGui.QComboBox()
self.storm.addItem('No Change')
self.storm.addItem('Stormier')
self.layout.addRow(self.storm_lbl, self.storm)
# show initial state
self.updateComboboxes()
def updateComboboxes(self, text=None):
#if hazard is cliff erosion or dune erosion we want to update the widget
#... to include wstorm,long_term AND no_change,stormier
if text is None:
text = self.hazard.currentText()
usable = (text == 'Cliff Erosion' or text == 'Dune Erosion')
if True: # change to False to use enabling/disabling widgets
# May cause other widgets to be relocated
self.types_lbl.setVisible(usable)
self.types.setVisible(usable)
self.storm_lbl.setVisible(usable)
self.storm.setVisible(usable)
else:
# This option doesn't relocate widgets
# Also may give additional clue to the uses that this exsits
self.types_lbl.setEnabled(usable)
self.types.setEnabled(usable)
self.storm_lbl.setEnabled(usable)
self.storm.setEnabled(usable)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
#window.resize(100, 60)
window.show()
sys.exit(app.exec_())
In my project, I have 256 tiny PushButtons in one 16x16 grid layout. (Yeah that took forever.) Editing and running my program now is very laggy. Also, for some strange reason, Qt will not let me enable any of the buttons, but other buttons to the side work just fine?
Is there any easy way to determine which square of the grid was clicked without having a bunch of buttons? (Like following the cursor over an image maybe?)
Also, when each "square" of the grid is clicked, it becomes the "selection" and it needs to be the only "square" selected. (Think about it like a huge chess board)
Here is a pic: http://gyazo.com/988cdbb59b3d1f1873c41bf91b1408fd
Later on, I will need to do this again for a 54x54 size grid (2916 buttons) and I REALLY don't want to do it button by button.
Thanks for your time, I hope you understand my question :)
You can do this easy way, I've explained almost everything in code, but if you have any questions about it feel free to ask, and please, accept this answer if it solved your problem :)
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class DrawImage(QMainWindow):
def __init__(self, parent=None):
super(QMainWindow, self).__init__(parent)
self.setWindowTitle('Select Window')
#you can set grid size here ... 8x8, 16x16 , for bigger numbers (54x54) be sure your image is big enough, because QWidget can't be smaller then ~20 pixels
self.gridSize = 16
mainWidget = QWidget()
self.setCentralWidget(mainWidget)
self.scene = QGraphicsScene()
view = QGraphicsView(self.scene)
layout = QVBoxLayout()
layout.addWidget(view)
mainWidget.setLayout(layout)
self.image = QImage('image.JPG')# put your image name here, image (suppose to be grid) must be at the same folder or put full path
pixmapItem = QGraphicsPixmapItem(QPixmap(self.image), None, self.scene)
pixmapItem.mousePressEvent = self.pixelSelect
def pixelSelect( self, event ):
#add whatever you want to this widget,any functionality or you can add image for example, I've simply colored it
wdg = QWidget()
layout = QVBoxLayout()
palette = QPalette(wdg.palette())
palette.setBrush(QPalette.Background, QColor(200,255,255))
wdg.setPalette(palette)
wdg.setLayout(layout)
self.scene.addWidget(wdg)
#calculate size and position for added widget
imageSize = self.image.size()
width = imageSize.width()
height = imageSize.height()
#size
wgWidth = float(width)/self.gridSize
wgHeight = float(height)/self.gridSize
wdg.setFixedSize(wgWidth,wgHeight)
#position
wgXpos = int(event.pos().x()/wgWidth) * wgWidth
wgYpos = int(event.pos().y()/wgHeight) * wgHeight
wdg.move(wgXpos, wgYpos)
#which square is clicked?
print "square at row ", int(event.pos().y()/wgHeight)+1,", column ",int(event.pos().x()/wgWidth)+1, "is clicked"
def main():
app = QtGui.QApplication(sys.argv)
form = DrawImage()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Also, if you want to display simple square image over your grid image, look at question/solution I had: QGraphicsPixmapItem won't show over the other QGraphicsPixmapItem
If you don't really need the appearance of buttons, I would create a QWidget subclass that implements a custom paintEvent and renders a grid of needed size (taking the widget size into account). Then, in the mouse events (up,down,move etc.) you can calculate which grid item was clicked with a simple formula. You can render cells with different colors to indicate selection or highlighting.
P.S.:I would really like to post some code from my implementations (i have done this two or three times) but the source codes are at my old company :)
You just create your own QGridLayout in order to be able to add the buttons easily.
I posted an answer to another question, showing you how to fill a custom made QGridLayout with a bunch of widgets sequentially. The buttons are added according to the maximum count of columns you specified. (Note: It's just a very rough example but enough to start from)
In your example you would create the custom grid layout with 16 columns and simply add your buttons.
To find out which button has been pressed (and to make connecting easier) you can use QSignalMapper.
For investigating the lag you could check the amount of (GDI-/User-) handles of your application (using ProcessExplorer for example). The handle count shouldn't be above 10.000.
I don't know why you can't enable the push buttons.
I am building the GUI for a boardgame for my software engineering class. I am using the TKinter toolkit on Python 2.7 (windows). I am stuck right now because I cant seem to find a way to ignore/forget a certain ordering of buttons. Essentially, I am trying to create a grid of buttons that would represent my game board. And right now, I have a game board that has a total of 49 buttons on a 7x7 grid.
So far this is what I have been able to do:
Instantiate all my button objects where columns = x and rows = y. This easily build a grid of x*y
I then place each button into a list (lets call this list1)
I want to use my list of button objects to ignore/forget/delete (for lack of a better description) certain buttons. I am thinking I can create a 2nd list (list2) of the indexes of the button objects that I want to use grid_forget on and then compare my two lists and only keep the ones that are not in list2. Unfortunately, this doesnt work out the way I want it to. Here is the code:
gameboard = ttk.Labelframe(root, padding = (8,8,8,8), text = "Gameboard",
relief = "sunken")
#forgetButtons will not be displayed on the game board b/c they do not have a
#label (they are not a: room, hallway, starting space)
forgetButtons = [0,1,3,5,6,7,13,14,16,18,21,30,32,41,42,43,45,46,47,48]
#this list tracks all the buttons on the gameboard
myButtons=[]
count = 0
for x in range(7): #build a 7x7 grid of buttons (49 buttons total)
for y in range(7):
btn = Button(gameboard, width=7, height=4)
myButtons.append(btn)
btn.grid(column=x, row=y, padx = 3, pady = 3)
#do some comparison here between the two lists
#to weed out the buttons found in forgetButtons
#**or maybe it should not be done here?**
btn.config(text="Room%d\none\ntwo\nfour\nfive" % x)
You don't need grid_forget these widgets if you just don't create them.
import itertools
import Tkinter as tk
root = tk.Tk()
forgetButtons = [0,1,3,5,6,7,13,14,16,18,21,30,32,41,42,43,45,46,47,48]
myButtons = []
for x, y in itertools.product(range(7), repeat=2):
if not x*7 + y in forgetButtons:
btn = tk.Button(root, width=7, height=4, text="Room%d\none\ntwo\nfour\nfive" % x)
btn.grid(column=x, row=y, padx=3, pady=3)
myButtons.append(btn)
root.mainloop()
I don't know the order to calculate the position for forgetButtons (usually the first index represents the row and the second one the column), but you can easily switch it.