create a gtk.window under a gtk.widget - python

I wanna show a gtk.Window under a gtk.widget.
But I don't know how to retrieve the gtk.widget's coordinates for my gtk.window.
Anyone knows ?
Thanks.

You can use the "window" attribute of the gtk.Widget to get the gtk.gdk.Window associated with it. Then look at the get_origin() method to get the screen coordinates.
These coordinates are for the top-level window, I believe (I could be wrong about that, but my code below seems to support that). You can use the get_allocation() method to get the coordinates of a widget relative to its parent.
I got the idea from here. Be warned though: some window managers ignore any initial settings for window position. You might want to look at this post for more info, but I haven't personally checked it out.
Were you intending to create another top-level window? Or a popup window?
#!/usr/bin/env python
import sys
import pygtk
import gtk
class Base:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.on_destroy)
self.box = gtk.VButtonBox()
self.buttons = [
gtk.Button("Test 1"),
gtk.Button("Test 2"),
gtk.Button("Test 3")
]
for button in self.buttons:
self.box.add(button)
button.connect("clicked", self.show_coords)
button.show()
self.window.add(self.box)
self.box.show()
self.window.show()
def show_coords(self, widget, data=None):
print "Window coords:"
print self.window.get_window().get_origin()
print "Button coords:"
print widget.get_allocation()
def on_destroy(self, widget, data=None):
gtk.main_quit()
def main(self):
gtk.main()
if __name__ == "__main__":
base = Base()
base.main()

Related

Menubar sometimes does not become un-greyed when QFileDialog closes

OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.

When changing screens with QStackedWidget, how to call a function in the changed screen?

I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.

QTooltip with absolute position that still moves relative to parent

I'm building a custom combobox, from a (subclassed) QLineEdit and QListWidget for the dropdown menu
I'm setting the window flags to QTool so that its a floating window but doesnt steal focus from the lineedit (since the user needs to be able to input text to filter the list). This works fine but the list is now completely detached from the parent widget, so I can drag the top menu bar and move it away from the list which I don't want.
Is there a way to use QTool or QTooltip but keep it parented to a widget?
One other method would be setting the window flags to QPopup, in which case the popup closes when the top menu bar is clicked so cannot be dragged away. However with QPopup it steals focus from the line edit
Below is a simple example illustrating the issue:
from PySide2 import QtCore, QtWidgets, QtGui
import sys
class LineEditClickable(QtWidgets.QLineEdit):
"""Custom QLineEdit to detect clicked, focus and key events
Signals: clicked, focusOut, arrowUp, arrowDown
"""
clicked = QtCore.Signal(QtGui.QMouseEvent)
def __init__(self, value=''):
super(LineEditClickable, self).__init__(value)
# remove border on Mac
self.setAttribute(QtCore.Qt.WA_MacShowFocusRect, 0)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
def mousePressEvent(self, event):
"""Emit clicked signal"""
self.clicked.emit(event)
super(LineEditClickable, self).mousePressEvent(event)
class popup(QtWidgets.QWidget):
def __init__(self, parent = None, widget=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout(self)
self.list = QtWidgets.QListWidget()
layout.addWidget(self.list)
# adjust the margins or you will get an invisible, unintended border
layout.setContentsMargins(0, 0, 0, 0)
self.adjustSize()
# tag this widget as a popup
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool)
# self.setWindowFlags(QtCore.Qt.Popup)
def update(self, widget):
# calculate the botoom right point from the parents rectangle
point = widget.rect().bottomRight()
# map that point as a global position
global_point = widget.mapToGlobal(point)
# by default, a widget will be placed from its top-left corner, so
# we need to move it to the left based on the widgets width
self.move(global_point - QtCore.QPoint(self.width(), 0))
def show_popup(self, widget):
self.update(widget)
self.show()
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.le = LineEditClickable(self)
self.le.clicked.connect(self.handleOpenDialog)
self.le.move(250, 50)
self.resize(600, 200)
self.popup = popup(self, self.le)
self.popup.list.addItems(['one','two','three'])
def handleOpenDialog(self):
self.popup.show_popup(self.le)
self.popup.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())```
The basic answer to your question is to use the correct flags and focus options.
If you look at how QCompleter implements setPopup(), you'll see the following:
popup->setWindowFlag(Qt::Popup);
popup->setFocusPolicy(Qt::NoFocus);
[...]
popup->setFocusProxy(d->widget);
As you've already experienced, Tool is not a good option: while avoids stealing focus from the line edit, it also has issues with any mouse click that happens outside the UI.
If you still want to use Tool, you could update the widget position, by installing an event filter on the top level window of the line edit and intercept its move events, but it's not guaranteed that it works and totally depends on the platform you're using it. For example, on certain Linux window managers you only receive it when the mouse is released after dragging the window.
class popup(QtWidgets.QWidget):
_widget = None
_window = None
# ...
def show_popup(self, widget):
if self._window:
self._window.removeEventFilter(self)
self.update(widget)
self.show()
self._widget = widget
self._window = widget.window()
self._window.installEventFilter(self)
def hideEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def closeEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Move:
self.update(self._widget)
return super().eventFilter(source, event)
Frankly, I'd suggest you to use what Qt already provides you without trying to reinvent the wheel. In your case, use a QCompleter and reimplement what you need for it's popup.
Note that if you want to show all items when the line edit gets focus and there's no text yet, you could change the completion mode.
class LineEdit(QtWidgets.QLineEdit):
def __init__(self, *args, **kwargs):
# ...
self.textChanged.connect(self.showCompleter)
def showCompleter(self):
completer = self.completer()
if not completer:
return
if not self.text():
completer.setCompletionMode(completer.UnfilteredPopupCompletion)
else:
completer.setCompletionMode(completer.PopupCompletion)
completer.complete()
You might want to do the same also in the keyPressEvent override, after calling the base class implementation and ensuring that the popup is not yet visible.

How to get parent widget's name printed from child widget class?

I understand that question is simple, but I'm stuck anyway.
Is there any method to get parent layout widget name from inherited widget class?
I have a small piece of code here. So basically I need to get printed self.super_main_layout in the label field where "Push the button" printed now.
I have a function def print_foo which should do that. But I don't know how to get parent() name from inherited class. I need to get exactly this self.super_main_layout printed in label field.
I've tried to use self.parent() method but it doesn't work
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QApplication
class ButtonTest(QtWidgets.QWidget):
def __init__(self):
super(ButtonTest, self).__init__()
self.setFixedSize(300, 100)
self.layout = QtWidgets.QHBoxLayout()
self.setLayout(self.layout)
self.label = QtWidgets.QLabel("Push the Button")
self.button = QtWidgets.QPushButton("Button")
self.button.clicked.connect(self.print_foo)
self.layout.addWidget(self.label)
self.layout.addWidget(self.button)
def print_foo(self):
### ???????????
self.label.setText(str(self.parent().parent())) # ????
print(self.parent()) # ????
class MyApp(QtWidgets.QWidget):
def __init__(self):
super(MyApp, self).__init__()
self.W = ButtonTest()
self.setFixedSize(300,300)
self.super_main_layout = QtWidgets.QVBoxLayout()
self.setLayout(self.super_main_layout)
self.super_main_layout.addWidget(self.W)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())
So now it prints None in label field when you push Button.
But I want layout widget self.super_main_layout to be printed.
Is there any way to do that? I'm working on a bigger project right now. And I'm programming only inherited class with button in which I have to get different parent names when button is pressed.
Thank you very much.
ps. I'm quite new to this site and programming at all, so sorry for any mistakes. Thanks in advance!
The name of a variable is impossible in itself to obtain because the names of the variables are relative, for example if the code were the following:
# ...
self.setFixedSize(300,300)
self.super_main_layout = QtWidgets.QVBoxLayout()
foo_obj = self.super_main_layout
self.setLayout(foo_obj)
self.super_main_layout.addWidget(self.W)
What is the name of the layout: self.super_main_layout or foo_obj? As both variables refer to the same object.
What you can get is the object itself using the parent and then its layout:
def print_foo(self):
pw = self.parentWidget()
if pw is not None:
print(pw.layout())

showing another window/frame in wxPython

I am fairly new to programming and to python and wxpython. I have looked over this code for literally HOURS and I tried finding an answer everywhere online. I am having trouble getting a new window to show up after a menu item is clicked. Here is my code so far...
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
##wx.Frame.Maximize(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, NewWindow.new_frame, None, 1)
class NewWindow(wx.Frame):
def __init__(self,MainWindow,id):
wx.Frame.__init__(self, None, id, 'New Window', size=(600,400))
wx.Frame.CenterOnScreen(self)
self.Show(False)
def new_frame(self, event):
NewWindow.Show(True)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
When I try to run this code, I get this error message once I click on the menu item "New Window"
TypeError: unbound method new_frame() must be called with NewWindow instance as first argument (got CommandEvent instance instead)
Again, I am fairly new to programming. Any help is greatly appreciated and also, I know my code may not be the "cleanest" looking code around. Thanks in advance!
You don't seem to understand how classes work in Python. You try to call NewWindow.new_frame, but you never actually create an instance of that class.
The error message is because you are calling the method on the class instead of on an instance of the class. What you want to do is something like:
newWin = NewWindow(...) # replace ... with the appropriate parameters
newWin.Show(True)
You don't provide enough information in your example to know what the appropriate parameters are for the NewWindow call (e.g., you don't show where you create the main window), but the MainWindow and id parameters in NewWindow.__init__ aren't just there for looks: wxPython needs to know the parent window. You should look into the wxPython documentation to understand how to create a wxFrame.
Modifying your code to some extent i was able to show a new window when user clicks a New Window option,
Do check the stuff that i have modified a let me know if this is what you want??
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.test, None, 1)
def test(self, event):
self.new = NewWindow(parent=None, id=-1)
self.new.Show()
class NewWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self, parent, id, 'New Window', size=(400,300))
wx.Frame.CenterOnScreen(self)
#self.new.Show(False)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()

Categories