PYQT QSplitter issue - pyqt

I use QSplitter and I found out that the minumum width of a widget in
the splitter is 32 pixels (and 23 pixels in height). Does anybody body knows how
to change this default. In other words, you can't drag the splitter so that one of the
widgets (assume that there are 2 widgets in the spllitter) in the spllitter will be less
than 32 pixels in width.
The code:
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.resize(400,400)
m = QtGui.QSplitter(self)
m.resize(200, 100)
x = QtGui.QPushButton(m)
x.setGeometry(0, 0, 100, 100)
y = QtGui.QPushButton(m)
y.setGeometry(0, 100, 100, 100)
m.setSizes([20, 180])
# this will show you that the width of x is 32 (it should be 20!)
print x.width()

Note: I'm using Python 3.6.2 and PyQt5, though the logic in the example stays the same and can be understood even if you're using other versions of Python and PyQt.
Look at what is said here:
If you specify a size of 0, the widget will be invisible. The size policies of the widgets are preserved. That is, a value smaller than the minimal size hint of the respective widget will be replaced by the value of the hint.
One of the options to solve your problem is to call x.setMinimumWidth() with a small value, like:
x.setMinimumWidth(1)
However, if you'll try it yourself, you'll see that
it is a dirty hack as it actually leaves the widget here, just makes it very narrow and
though now you can drag the splitter, the initial width of the widget is still "32" instead of "20".
x.setMinimumWidth(0)
also doesn't work as expected: its minimal width is actually zero by default (as this widget has no contents, I guess), but it doesn't help you to make splitter item less than 32 pixels wide unless you collapse it.
By the way, set
m.setCollapsible(0, False)
m.setCollapsible(1, False)
if you want splitter to stop collapsing its two children widgets. More details here.
The solution I've found is to overload sizeHint() method of the widget you want to include into the splitter, as in example below (look at the ButtonWrapper class and what is output like now).
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#Python 3.6.2 and PyQt5 are used in this example
from PyQt5.QtWidgets import (
QPushButton,
QSplitter,
QWidget,
QApplication,
)
import sys
class ButtonWrapper(QPushButton):
def sizeHint(self):
return self.minimumSize()
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(400, 400)
m = QSplitter(self)
m.resize(200, 100)
x = ButtonWrapper(self)
x.setGeometry(0, 0, 100, 100)
y = QPushButton(self)
y.setGeometry(0, 100, 100, 100)
m.addWidget(x)
m.addWidget(y)
m.setSizes([20, 180])
#Now it really shows "20" as expected
print(x.width())
#minimumWidth() is zero by default for empty QPushButton
print(x.minimumWidth())
#Result of our overloaded sizeHint() method
print(x.sizeHint().width())
print(x.minimumSizeHint().width())
self.setWindowTitle('Example')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
I'm not sure if this is the right way to do stuff, but I've spent lots of time trying to solve my own problem connected to this, and haven't seen anything satisfying yet so far. I'll really appreciate it if someone knows a better actually working & clear workaround.

Related

I'm recreating snake in Python, I've set the base up but i'm at a road block for how to refresh/update the screen

from tkinter import *
root = Tk()
root.geometry('400x400')
root.title('Snake')
#root['background'] = ''
canvas = Canvas(root,width = 400,height = 400,bg = '#FF0000')
canvas.pack()
Heading ###class used to setup snake with methods.
class Snake:
def __init__(self):
self.width = 10
self.height = 10
self.snake_length = [1]
def draw_cell(self,pos_x,pos_y):
# pos_x, pos_y will be divided by 20 so we times by 20
canvas.create_rectangle(pos_x*20,pos_y*20,pos_x*20+20,pos_y*20+20)
def draw_snake(self):
for i in range(0,len(self.snake_length)):
self.draw_cell(10,10)
def movement(self):
def grid():
for i in range(0,20):
for j in range(0,20):
canvas.create_rectangle(i*20,j*20,i*20+20,j*20+20,fill = '#ff6347'
##starting up everything also checking things
grid()
a = Snake()
a.draw_snake()
root.mainloop()
Im not very good at python, so any extra help with silly mistakes would be helpful :)
The function/method you are looking for would redraw the grid and then draw your snake.
Since you already created the functions/methods to do this, the actual implementation of this should be easy as that:
def update():
grid()
a.draw_snake()
If you want to control the refresh rate (which I would recommend since otherwise, your frame will update as often as your update function can be called) you can use the after() method from the tkinter module. You place it just before your mainloop call. In this example, the variable fps would represent the desired frames per second.
root.after(1000/fps, update)
If you are further interested in the after method(), you will find a pretty good explanation here.

GTK3, SVG, and Cairo widget gets a gtk-missing-image icon overlay

I have a custom widget that I am porting from Python 2 with GTK2 to Python3 with GTK3. I can get it working just fine using a toy example as shown below:
The button is there to change the widget from green to orange to red. Don't ask more.
However, as soon as I include it in a glade file, get this garbage:
Which has the gtk-missing-image icon superimposed on my widget.
The code is a mess with much inheritance and "clever" things hence why there is no code quoted. Either a hint as to why that could be or better yet, a good example of a SVG widget with the ability to change the colours would be welcome.
There's more!
Here is a toy code. It works fine in my toy program (widget SVG appears) but when I load it via glade, I get a gtk-missing-image instead.
class Lamp(gtk.Image):
"""A new lamp?"""
__gtype_name__ = "Lamp"
off_colour = gobject.property(type=str, default="grey40")
on_colour = gobject.property(type=str, default="#00ff00")
fault_colour = gobject.property(type=str, default="#ff0000")
def __init__(self):
super(Lamp, self).__init__()
self._logger = logging.getLogger(self.__class__.__name__)
self._logger.info("Initialised NEW Lamp.")
dir_path = os.path.dirname(os.path.realpath(__file__))
self.path = os.path.join(dir_path, "Lamp.svg")
self.width = 128
self.height = 128
self.preserve_aspect_ratio = True
self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
self.path,
self.width,
self.height,
self.preserve_aspect_ratio)
self.set_from_pixbuf(self.pixbuf)
self.connect("draw", self.on_draw)
def on_draw(self, widget, event):
"""When the "draw" event fires, this is run."""
self._logger.debug("%s called.", inspect.currentframe().f_code.co_name)
# … What does here?

In python3 tkinter, the wigdet frame doesn't show in interface

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

How to prevent widget's size changing in Kivy?

I've been playing with this code and I've found some (probably) strange thing: when I add parent to my class, size changes to [100, 100] (see comments):
from random import random, randint
import kivy
kivy.require('1.8.0')
from kivy.config import Config
Config.set('graphics', 'fullscreen', '0')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Line, Ellipse, Triangle, Rectangle
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(random(), 1, 1, mode='hsv')
touch.ud['line'] = Line(points=(self.width - touch.x, self.height - touch.y))
print(self.width, self.height) # It works OK if I return painter below, but it's not if I return parent.
def on_touch_move(self, touch):
touch.ud['line'].points += [self.width - touch.x, self.height - touch.y]
class Example(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
print(painter.size) # Shows [100, 100] anyway.
parent.add_widget(painter)
return parent # If I return painter, everything works as I want (except painter.size still shows [100, 100]).
if __name__ == '__main__':
Example().run()
Why does that happen? And how should I make it right?
(100, 100) is the default size for widgets. In this case your painter has that size because you never set it to anything else.
Even if its parent were a layout class that would automatically move and resize the painter, its size would still read (100, 100) at this point because the layout hasn't had time to run yet. You shouldn't generally worry about the pixel values at this point - if you need something else to depend on them, use a binding to update the other thing automatically when the first one changes. This is made particularly easy by kv language.

Qt formlayout not expanding qplaintextedit vertically

I'm confused why a QPlainTextEdit widget will not resize vertically when added to a QFormLayout. In the code below the text field correctly scales up horizontally, but does not scale up vertically.
Can anyone explain this behavior and offer a solution? I've tried all the tricks I know to no avail.
from PyQt4 import QtGui
class Diag(QtGui.QDialog):
def __init__(self, parent, *args, **kwargs):
QtGui.QDialog.__init__(self, parent)
layout = QtGui.QFormLayout(self)
widg = QtGui.QPlainTextEdit(self)
layout.addRow('Entry', widg)
if __name__ == '__main__': #pragma: no cover
app = QtGui.QApplication([])
window = Diag(None)
window.show()
app.exec_()
Here is an example of the QPlainTextEdit widget not resizing vertically:
This is on Windows 7 using PyQt 4.5.2 and Python 32-bit 2.6.
Thanks.
It seems that, by default, a QFormLayout will only resize the height of its fields according to their sizeHint.
To change this behaviour, adjust the vertical stretch as appropriate:
policy = widg.sizePolicy()
policy.setVerticalStretch(1)
widg.setSizePolicy(policy)
You should set the object in the last row of formlayout (see QPlainTextEdit), its vertical Stretch factor should not be 0.
This works for me:
it is small at the time of calculating the initial size of the dialog widget and can grow with the dialog once it is already visible
class q2text(QTextEdit):
def showEvent(self, ev):
self.updateGeometry()
return super().showEvent(ev)
def sizeHint(self):
if self.isVisible():
return QSize(99999, 99999)
else:
return super().sizeHint()

Resources