I'm putting together a widget in PyQt's Qt Designer, but struggling to make elements that should stay as close to each other as possible do so after applying a grid layout.
For example, in this layout, I've set the question mark (which acts as the tooltip) to be 2 px from the right hand edge of each of the input elements:
When I apply a grid layout, these spaces massively (and inconsistently across all 3 lines) increase:
I've tried applying a horizontal layout to the rows of elements, but even this causes the elements to spread out more horizontally in an inconsistent manner.
Am I missing something obvious? Is there a way to force elements to stay a certain distance for neighbouring elements?
EDIT / UPDATE
Thanks to three_pineapples' suggestion, I've put some horizontal-spacers in (although I'm not sure if I've done it in the way he intended?)
This has solved it to some extent, but:
the gap between elements and the question mark is now consistent, but 6 px on all rows!
the gap between other elements is not consistent (the gap between the QLineEdit and the File Browse button is 12 px, not the 5 that I intended here)
the height of the spacers are the same height as the rows, but QtDesigner seems to have added in a weird orphaned row above the file select row. Although I'm sure it's nothing to be concerned about I do prefer to understand how things work and why it's doing it...
Have you tried calling setSpacing(0) on the QGridLayout to reduce the spacing between items? (there's also setHorizontalSpacing(x) and setVerticalSpacing(y) if you prefer). Also, setContentsMargins(0, 0, 0, 0) would eliminate the outer margins on the grid, which I think is not what you're looking for here but might be related or useful for someone stumbling upon this.
Snippet:
from PyQt4 import QtGui
class MyClass(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
p1 = QtGui.QPushButton('One', self)
p2 = QtGui.QPushButton('Two', self)
p3 = QtGui.QPushButton('Three', self)
p4 = QtGui.QPushButton('Four', self)
grid = QtGui.QGridLayout(self)
grid.addWidget(p1, 0, 0)
grid.addWidget(p2, 0, 1)
grid.addWidget(p3, 1, 0)
grid.addWidget(p4, 1, 1)
grid.setContentsMargins(0, 0, 0, 0) # this seems to be the outer padding of the grid
grid.setHorizontalSpacing(0) # I think this is what you're looking for
grid.setVerticalSpacing(0)
# grid.setSpacing(0) # this would set vertical & horizontal spacing at once
self.setLayout(grid)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MyClass()
window.show()
sys.exit(app.exec_())
I'm not used to using QtDesigner but there should be ways of setting these properties there, if you prefer.
Related
I'm making a program and I need a image and an entry field. However when I try to put them in the same column it defaults the to the top left no matter the value. I tried column as various values and it either goes to the top left if they are the same or one goes to the center-ish and one is on the left. is there something I'm missing. Any help would be greatly appreciated.
from tkinter import *
import tkinter as tk
from PIL import Image,ImageTk
pg3 = Tk()
img1 = ImageTk.PhotoImage(Image.open("download.png"))
pg3.attributes("-fullscreen", True)
tk.Label(pg3, image = img1, anchor = "c").grid(row=0, column = 1)
e1 = tk.Entry(pg3)
e1.grid(row=1, column=1)
pg3.mainloop()
Use pack.
BaseWidget.pack() automatically centers all your elements.
P.S. If you want to horizontally center a few elements, you can use a Frame to grid the elements and then pack the frame.
Another way will be using pg3.columnconfigure(1, weight=1). It is an alias of grid_columnconfigure.
def grid_columnconfigure(self, index, cnf={}, **kw):
"""Configure column INDEX of a grid.
Valid resources are minsize (minimum size of the column),
weight (how much does additional space propagate to this column)
and pad (how much space to let additionally)."""
So you can set the weight to 1 to center it.
Context
I have put in place column freezing in a wx.grid.Grid, using FreezeTo method.
def __init__(self, parent):
# relevant lines
self.grid = wx.grid.Grid(self.sbox_grid, size=(1000, 800))
self.grid.CreateGrid(self.row_number, self.col_number)
self.grid.FreezeTo(0, self.frozen_column_number)
The freezing by itself works well, as soon as I keep the standard label renderer (*).
The first few columns I have frozen always stay visible, and moving the horizontal scrollbar by hand is also ok.
(*) I was initially using the GridWithLabelRenderersMixin of wx.lib.mixins.gridlabelrenderer, but it totally breaks consistency between column label width and column width. Anyway I can deal with the standard renderer, so it is not really a problem.
I faced several issues, now all solved and detailed below.
Capture the cell position for frozen columns: cells or labels (SOLVED)
For cells, the window can be captured with GetFrozenColGridWindow.
So mouseover can be done simply with:
if widget == self.grid.GetFrozenColGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever your want with row, col
For labels, the window exists but is NOT accessible with a method.
With a GetChildren on the grid, I have found that it is the last of the list (corresponding to the latest defined).
So it is not very reliable, but a relatively good placeholder for the missing GetGridFrozenColLabelWindow method.
wlist = self.grid.GetChildren()
frozen_col_label_window = wlist[-1]
if widget == frozen_col_label_window:
x = event.GetX()
y = event.GetY()
col = self.grid.XToCol(x, y)
# do stuff with col
Mouse position from non-frozen columns (labels or cells) is shifted (SOLVED)
The effective position for non-frozen columns labels or cells is shifted from the total width of all the frozen columns.
This one is easily handled by a shift in position, computed before calls to YToRow or XToCol methods.
The following code shows the position corrections:
class Report(wx.Panel):
def _freeze_x_shit(self):
"""Returns the horizontal position offset induced by columns freeze"""
offset = 0
for col in range(self.frozen_column_number):
offset += self.grid.GetColSize(col)
return offset
def on_mouse_over(self, event):
widget = event.GetEventObject()
# grid header
if widget == self.grid.GetGridColLabelWindow():
x = event.GetX()
y = event.GetY()
x += self._freeze_x_shit() # <-- position correction here
col = self.grid.XToCol(x, y)
# do whatever grid processing using col value
# grid cells
elif widget == self.grid.GetGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
x += self._freeze_x_shit() # <-- and also here
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever grid cell processing using row and col values
event.Skip()
HOME keyboard key not working as intended (SOLVED)
I generally use the HOME key to immediately go at the utmost left of the grid, and the END key to go far right. This is the normal behavior with a non-frozen grid.
The END key does its jobs, but not the HOME key.
When pushing HOME on any grid cell, I got two effects:
the selected cell becomes the first column: this is OK
but the scrollbar position is not changed at all: I would expect the scroll position to be fully left
I have corrected it by a simple remapping using EVT_KEY_DOWN event:
def __init__(self, parent):
self.grid.Bind(wx.EVT_KEY_DOWN, self.on_key_event)
def on_key_event(self, event):
"""Remap the HOME key so it scrolls the grid to the left, as it did without the frozen columns
:param event: wx.EVT_KEY_DOWN event on the grid
:return:
"""
key_code = event.GetKeyCode()
if key_code == wx.WXK_HOME:
self.grid.Scroll(0, -1)
event.Skip()
my 3 issues concerning column Freeze in a grid are now solved. I have edited my initial post with my solutions.
I'm trying to write code to detect the color of a particular area of an image.
So far I have come across is using OpenCV, we can do this, But still haven't found any particular tutorial to help with this.
I want to do this with javascript, but I can also use python OpenCV to get the results.
can anyone please help me with sharing any useful link or can explain how can I achieve detecting the color of the particular area in the image.
For eg.
The box in red will show a different color. I need to figure out which color it is showing.
What I have tried:
I have tried OpenCV canny images, though I am successful to get area separated with canny images, how to detect the color of that particular canny area is still a challenge.
Also, I tried it with inRange method from OpenCV which works perfect
# find the colors within the specified boundaries and apply
# the mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
# show the images
cv2.imshow("images", np.hstack([image, output]))
It works well and extracts the color area from the image But is there any callback which responds if the image has particular color so that it can be all done automatically?
So I am assuming here that, you already know the location of the rect which is going to be dynamically changed and need to find out the single most dominant color in the desired ROI. There are a lot of ways to do the same, one is by getting the average, of all the pixels in the ROI, other is to count all the distinct pixel values in the given ROI, with some tolerance difference.
Method 1:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227) # left, top, bottom, right
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
print cv2.mean(cropped_img)
>>> (53.430516018839604, 41.05708814243569, 244.54991977640907, 0.0)
Method 2:
To find out the various dominant clusters in the given image you can use cv2.kmeans() as:
import cv2
import numpy as np
img = cv2.imread("path/to/img.jpg")
region_of_interest = (356, 88, 495, 227)
cropped_img = img[region_of_interest[1]:region_of_interest[3], region_of_interest[0]:region_of_interest[2]]
Z = cropped_img.reshape((-1, 3))
Z = np.float32(Z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 4
ret, label, center = cv2.kmeans(Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Sort all the colors, as per their frequencies, as:
print center[sorted(range(K), key=lambda x: np.count_nonzero(label == [x]), reverse=True)[0]]
>>> [ 52.96525192 40.93861389 245.02325439]
#Prateek... nice to have the question narrowed down to the core. The code you provided does not address this issue at hand and remains just a question. I'll hint you towards a direction but you have to code it yourself.
steps that guide you towards a scripting result:
1) In your script add two (past & current) pixellists to store values (pixeltype + occurance).
2) Introduce a while-loop with an action true/stop statement (link to "3") for looping purpose because then it becomes a dynamic process.
3) Write a GUI with a flashy warning banner.
4) compare the pixellist with current_pixellist for serious state change (threshhold).
5) If the delta state change at "4" meets threshold throw the alert ("3").
When you've got written the code and enjoyed the trouble of tracking the tracebacks... then edit your question, update it with the code and reshape your question (i can help wiht that if you want). Then we can pick it up from there. Does that sound like a plan?
I am not sure why you need callback in this situation, but maybe this is what you mean?
def test_color(image, lower, upper):
mask = cv2.inRange(image, lower, upper)
return np.any(mask == 255)
Explanations:
cv2.inRange() will return 255 when pixel is in range (lower, upper), 0 otherwise (see docs)
Use np.any() to check if any element in the mask is actually 255
I'm trying to locate a few images on a grid, but when I try to do something like
panel = Label(root, image = img)
panel.grid(row = a, column = b)
where a,b are values that should bring the image to the center of the grid, I always get the image on the top left corner, because empty columns/rows don't fill the space. So, using a friend's advice, I added a blank image and did something like
a = 0
b = 0
for img in ls2: # ls2 is a list containing instances of the blank image
a += 1
if a == 11:
a = 1
b += 1
panel = Label(root, image = img)
panel.grid(row = a, column = b)
Now, when I'm trying to locate a new image on row = x, column = y it goes as I wanted and this is my current solution. My question is, is this a good way to enable me use the whole grid?
No, it's not a good way. There is no need to create invisible widgets. It's hard to say what the good way is, however, because "use the whole grid" is somewhat vague.
If by "use the whole grid" you mean want to create a grid where all rows are the same height, and all of the columns are the same width, and the grid fills its containing window, the right solution is to create uniform rows columns using rowconfigure and columnconfigure.
For example, if you want to create a 4x4 grid of equal sized cells, you could do something like this:
import tkinter as tk
root = tk.Tk()
for row in range(4):
root.grid_rowconfigure(row, uniform="default", weight=1)
for column in range(4):
root.grid_columnconfigure(column, uniform="default", weight=1)
label_2_2 = tk.Label(root, text="row 2, column 2", borderwidth=2, relief="groove")
label_1_0 = tk.Label(root, text="row1, column 0", borderwidth=2, relief="groove")
label_1_0.grid(row=1, column=0)
label_2_2.grid(row=2, column=2)
root.mainloop()
The uniform option takes any arbitrary string. Every row (or column) with the same value will have the same dimensions.
The weight option tells grid how to allocate any extra space. By giving each row and each column an identical non-zero weight, all extra space will be apportioned equally, causing the rows and columns to grow or shrink to fit the window.
Just found the place geometry manager. It is more explicit and lets me control the exact locations of my images inside the widget.
panel.place(x=100, y=100) gives pixel precision location which was what I looked for in the first place.
Ive just started using tkinter and have this so far:
from tkinter import *
BoardValue = ["-","-","-","-","-","-","-","-","-"]
window = Tk()
window.title("Noughts And Crosses")
window.geometry("250x250")
for b in BoardValue[0:3]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
for b in BoardValue[3:6]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
for b in BoardValue[6:9]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
window.mainloop()
I want 3 buttons on 3 rows, how can I do this?
There are two common solutions: one using grid, and one using pack with subframes for each row. There are other solutions but these are the most common. Of these two, when creating a grid of widgets, grid is the most natural solution.
Note: in both of the examples below, you can create the buttons in three separate loops if you want, but since the buttons are identical I chose to use a single loop to make the code a little easier to maintain (for example, if you decide to change the relief, you only have to change it in one line of code rather than three).
Using grid
You can use the grid geometry manager to align things in rows and columns. Since you can compute the row and column with simple math, you can reduce all of that code down to something like this:
for i, b in enumerate(BoardValue):
row = int(i/3)
col = i%3
btn = Button(window, text=b, relief=GROOVE, width=2)
btn.grid(row=row, column=col, sticky="nsew")
You may want to add a weight to the rows and columns if you want them to grow and shrink equally when you resize the window.
grid has an advantage over pack when building a matrix like this, since each cell is guaranteed to be the same size.
Using pack
If you know that each button will be the same width, you can use pack in combination with subframes. Pack the subframes along the top, and the buttons along the left or right. Again, you can use a little math to know when a new row is starting.
It would look something like this:
for i, b in enumerate(BoardValue):
if i%3 == 0:
row_frame = Frame(window)
row_frame.pack(side="top")
btn = Button(row_frame, text=b, relief=GROOVE, width=2)
btn.pack(side="left")
Using pack may not be the right choice if buttons could be different sizes, since there are no actual columns using this method.
The simple answer is to put the buttons in frames so you can control the packing order. E.g.;
i=0
for f in range(3):
frame[f] = Frame(window).pack(side=TOP)
for b in range(3):
btn[i] = Button(frame[f], text=i, relief=GROOVE, width=2).pack(side=LEFT)
i+=1
I've put the button & frame objects into lists in case you need to access the button objects later; you were overwriting your handles in the original code.