Change QPushButton behaviour if hotkey is held ? - pyside

Is it possible to change the appearance and function of a QPushButton , such as hover , if a hotkey is held ?
I’m after a solution whereby if I held CTRL down , then hovered , then pressed, it would have a different outcome to hover and press
I’m using Pyside currently and this application is for maya

Here's an example subclassing QPushButton's enter and leave events. It will change stylesheets when ctrl is held down, and will also execute different function than when ctrl is not pressed:
from PySide2 import QtCore, QtGui, QtWidgets
class CustomButton(QtWidgets.QPushButton):
def __init__(self, label, parent=None):
super(CustomButton, self).__init__(label, parent)
self.entered = False # Track when the cursor enters this widget.
self.normal_style = "QPushButton {background-color:red;}"
self.alt_style = "QPushButton {background-color:blue;}"
self.setStyleSheet(self.normal_style)
self.clicked.connect(self.click_event)
def enterEvent(self, event):
self.entered = True
self.set_style()
def leaveEvent(self, event):
self.entered = False
self.setStyleSheet(self.normal_style)
def set_style(self):
if self.entered and self.parent().is_ctrl_down:
self.setStyleSheet(self.alt_style)
else:
self.setStyleSheet(self.normal_style)
def func_1(self):
print "1"
def func_2(self):
print "2"
def click_event(self):
if self.entered and self.parent().is_ctrl_down:
self.func_2()
else:
self.func_1()
class Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.is_ctrl_down = False # Track when ctrl is held down.
self.my_button = CustomButton("Hello World!", parent=self)
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.my_button)
self.setLayout(self.main_layout)
self.resize(400, 400)
self.setWindowTitle("Button behaviour example")
def keyPressEvent(self, event):
ctrl_state = event.modifiers() == QtCore.Qt.CTRL
if ctrl_state != self.is_ctrl_down:
self.is_ctrl_down = ctrl_state
self.my_button.set_style()
def keyReleaseEvent(self, event):
self.is_ctrl_down = False
self.my_button.set_style()
tool = Window()
tool.show()
I tested this on Maya 2018, so it's in PySide2. If you're using an older version with PySide you just need minor tweaks to make this example work.

Related

How to update the Wxpython statusbar with run-time data streaming in another *.py file

my project has many python files,and right now the problem is when I clicked button in Main-UI interface,it will invoke some function in another python file(sub-program),and i need all the running status in sub-program will also be updated in main-UI,how do i accomplish this?
The thing i can try so far i known is using socket,but i want to know do you guys have any other good ideas on this?
The code like this:
1. Main-UI:
import wx,time
from threading import Thread
from path import basicTest
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
def __init__(self, data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
class TestThread(Thread):
def __init__(self, wxObject):
Thread.__init__(self)
self.wxObject = wxObject
self.start()
def run(self):
this masked sub-script can run,but what i want to do is to replace it with invoking from another python file
'''
for i in range(6):
time.sleep(1)
wx.PostEvent(self.wxObject, ResultEvent(i))
'''
data = basicTest().run(10)
wx.PostEvent(self.wxObject, ResultEvent(data))
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tester")
panel = wx.Panel(self, wx.ID_ANY)
self.btn = wx.Button(panel, label="Start Test")
self.statusbar = self.CreateStatusBar()
self.btn.Bind(wx.EVT_BUTTON, self.onButton)
EVT_RESULT(self, self.updateStatus)
def onButton(self, event):
TestThread(self)
btn = event.GetEventObject()
btn.Disable()
def updateStatus(self, msg):
t = msg.data
self.statusbar.SetStatusText("Sequence %i running.." %t)
self.btn.Enable()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
sub-script:
import time
class basicTest():
def run(self,inter):
for i in range(inter):
return i
time.sleep(1)
As list above and also i known that the main UI only updated when the sub-script finished,it's not run-time refresh from another sub,can anyone help me, very appreciate
I would just leave the long running code in its thread. Then you can have wxPython start the thread. In your thread's run method, just use one of wxPython's thread-safe methods to call your UI. I would recommend wx.CallAfter or 'wx.PostEvent'.
Once you have that done, then you just execute the necessary method in your main UI.

How to limit number of Toplevel windows in tkinter

I have a bit of code that creates a a top level window upon the user
pressing a button.
However i would like to limit the number Top level windows to one, so the user couldn't Spam the button and open fifty windows.
import tkinter as tk
class app():
def __init__(self,master):
self.master = master
master.configure(background = '#002e3d')
master.title('Test!')
master.geometry = master.geometry('660x550+200+200')
master.resizable(width = False,height = False)
self.button = tk.Button(master,text = 'Test'command = self.searchmenu)
self.button.pack()
def searchmenu(self):
Demo()
class Demo():
def __init__(self):
self.top = tk.Toplevel()
self.top.title('Search!')
def main():
root = tk.Tk()
window = app(root)
root.mainloop()
If you make a reference to the Demo object you create (which I'd recommend regardless) this becomes very trivial task:
class app():
def __init__(self,master):
...
self.popup = None
def searchmenu(self):
if self.popup is None:
self.popup = Demo()
Although once the created window is destroyed this doesn't allow to reopen it, so you may want to also check if the top still exists with winfo_exists():
def searchmenu(self):
if self.popup is None or not self.popup.top.winfo_exists():
self.popup = Demo()
EDIT: if the popup is already open then pushing the button should lift it to the top of the window stack:
def searchmenu(self):
if self.popup is None or not self.popup.top.winfo_exists():
self.popup = Demo()
else:
self.popup.top.lift(self.master)

How can I update the textctrl content in GUI?

I am trying to write my first wxpython GUI program,in my program,I want to get anothor software window's title,if the title change,clear the old title and show the new title in GUI,I test in cmd,it can get the title in a loop,but I don't konw how to set a event in GUI to update the title.
my code:
def getinfos():
tempWindowName=win32gui.GetWindowText (find_window())
while True:
titles=[]
if (tempWindowName==win32gui.GetWindowText (find_window())):
pass
else:
tempWindowName=win32gui.GetWindowText (find_window())
titles.append(tempWindowName)
return title[0]
time.sleep(1000)
and the GUI code:
import controller2
import time
########################################################################
class InfoPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
try:
self.titleResults = controller2.getinfos()
except:
self.titleResults = 'no data'
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.titlecontent = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.TE_RICH|wx.TE_LEFT|wx.TE_WORDWRAP|wx.NO_BORDER)
self.titlecontent.SetBackgroundColour('white')
self.settitle()
mainSizer.Add(self.yejicontent, 2.5, wx.ALL|wx.EXPAND, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def settitle(self):
self.titlecontent.SetValue("%s"%self.titleResults)
########################################################################
class InfoFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="title",size=(500, 880))
panel = InfoPanel(self)
style= self.GetWindowStyle()
self.SetWindowStyle(style|wx.STAY_ON_TOP)
class MyApp(wx.App):
def OnInit(self):
self.infoFrame=InfoFrame()
self.SetTopWindow(self.infoFrame)
self.infoFrame.Show(True)
return True
#----------------------------------------------------------------------
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
Thanks for your time and appreciate for any advise.
Put the getinfos function/method into a thread. When the title changes, have the thread use wx.CallAfter or wx.PostEvent (both of which are thread-safe) to tell the GUI to update. If you don't put it into a thread, you're GUI will be very unresponsive.
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
Pubsub rocks, but won't work in this case if you're running that getinfos function in your wxPython main loop as it will block it. You could use pubsub in the thread in combination with those threadsafe methods I mentioned though.
You can send a custom wx event or setup pubsub.

Why wxframe isn't raised from a function called with global gtk binder?

Ok, why this simple app dosn't work.
I've spent one day investigating this and got nothing.
import wx, os
import gtk
import keybinder
class FrameWithHotKey(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
keybinder.bind("<Ctrl>period", self.toggle_shown)
def toggle_shown(self):
# windowNow id
if self.IsShown():
self.Hide()
else:
self.Show()
self.Raise()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = FrameWithHotKey(None)
app.MainLoop()
I don't know why, but sometimes (especially when I raise apps by clicking on them on panel) raising dosen't work and I got flash icon instead of raised window.
UPDATE
Ok, i return to the topic and notice these..
above example works for me.. strange
i isolated strange behaviour which below code shows.. it's something related with wnck lib. So if my app window is deactivated by open new window (left click on window - test1) then raise works perfect, but if other window (replace 'opera' with any of Yours) is activated with wnck(by right click - test2) then actvation fails
import logging
import subprocess
import time
import wnck
import wx
logging.basicConfig(level=logging.DEBUG)
class MyFrame(wx.Frame):
def __init__(self, parent, title=''):
wx.Frame.__init__(self, parent, title=title)
self.Centre()
self.Bind(wx.EVT_LEFT_DOWN, self.test1)
self.Bind(wx.EVT_RIGHT_DOWN, self.raise_window)
def test1(self, evt):
logging.debug('losing..')
subprocess.Popen(['xterm'])
time.sleep(1)
self.Raise()
logging.debug('lost')
def lose_focus_by_wnck(self):
screen = wnck.screen_get_default()
import gtk
while gtk.events_pending():
gtk.main_iteration(False)
wins = screen.get_windows()
logging.debug('wins: {0}'.format(wins))
for win in wins:
app_name = win.get_application().get_name()
logging.debug('app: {0}'.format(app_name))
if 'opera' in app_name.lower():
win_id = win.get_xid()
break
else:
win_id = None
return win_id
def test2(self, evt):
logging.debug('losing..')
win_id = self.lose_focus_by_wnck()
win = wnck.window_get(win_id)
TIMESTAMP = 0
win.activate(TIMESTAMP)
logging.debug('lost')
time.sleep(1)
self.Raise()
logging.debug('raised')
if name == 'main':
app = wx.PySimpleApp(redirect=False)
frame = MyFrame(None)
frame.Show()
app.MainLoop()
Does anybody understand this behaviour instead of very helpful wtf like i feel? :)
What is keybinder? Are you using an AcceleratorTable? See http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/ for more info. I don't think you can mix pyGtk with wxPython.

model/view QCompleter in a QLineEdit

ubuntu 10.04, KDE 4.4.5
python 2.6.4
qt 4.6.2
pyqt 4.6.2
I'm trying to create a QCompleter, which works fine if I just build the QLineEdit.
However if I drop the QLineEdit into a QMainWindow, the QCompleter no longer works.
Here is the LineEdit class
# LineEdit class
import sys
from PyQt4 import QtCore, QtGui
class LineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.completer = QtGui.QCompleter(self)
self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
self.pFilterModel = QtGui.QSortFilterProxyModel(self)
self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setPopup(self.view())
self.setCompleter(self.completer)
self.textEdited[unicode].connect(self.pFilterModel.setFilterFixedString)
def setModel(self, model):
self.pFilterModel.setSourceModel(model)
self.completer.setModel(self.pFilterModel)
def setModelColumn( self, column ):
self.completer.setCompletionColumn(column)
self.pFilterModel.setFilterKeyColumn(column)
def view(self):
return self.completer.popup()
def index( self ):
return self.currentIndex()
The QCompleter works if I build LinEdit this way
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
model = QtGui.QStandardItemModel()
for i,word in enumerate(['test', 'blah', 'heh', 'yep']):
item = QtGui.QStandardItem(word)
model.setItem(i, 0, item)
lineEdit = LineEdit()
lineEdit.setModel(model)
lineEdit.setModelColumn(0)
lineEdit.show()
sys.exit(app.exec_())
This compiles fine, but no longer shows the QCompleter
if __name__ == '__main__':
class Example(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.centralWidget = QtGui.QWidget(self)
self.layout = QtGui.QVBoxLayout(self.centralWidget)
# can I push this inside the LineEdit class instead?
model = QtGui.QStandardItemModel()
for i, word in enumerate(['test', 'blah', 'heh', 'yep', 'hello', 'hi']):
item = QtGui.QStandardItem(word)
model.setItem(i, 0, item)
# Make a LineEdit instance
self.lineEdit = LineEdit(parent=self.centralWidget)
self.lineEdit.setModel(model)
self.lineEdit.setModelColumn(0)
self.layout.addWidget(self.lineEdit)
self.setCentralWidget(self.centralWidget)
app = QtGui.QApplication(sys.argv)
QtWin = Example()
QtWin.show()
sys.exit(app.exec_())
turned out to be quite simple really, hopefully this will help anyone else using PyQt's QCompleter for auto-completion
import sys
from PyQt4 import QtCore, QtGui
class LineEdit(QtGui.QLineEdit):
def __init__(self, parent, completerContents):
super(LineEdit, self).__init__(parent)
self.completerList = QtCore.QStringList()
for content in completerContents:
self.completerList.append(QtCore.QString(content))
self.completer = QtGui.QCompleter(self.completerList, self)
self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.setCompleter(self.completer)
if __name__ == '__main__':
class Example(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.centralWidget = QtGui.QWidget(self)
self.layout = QtGui.QVBoxLayout(self.centralWidget)
# Example LineEdit Call
self.lineEdit = LineEdit(parent=self.centralWidget, completerContents=('test', 'blah', 'heh', 'yep', 'hello', 'hi'))
self.layout.addWidget(self.lineEdit)
self.setCentralWidget(self.centralWidget)
app = QtGui.QApplication(sys.argv)
QtWin = Example()
QtWin.show()
sys.exit(app.exec_())
There is possible 2 reasons of such a behavior in the second case:
Your completer has no completion model in the second case
Your LineEdit has set other completer
Don't know if U can to debug this and set breakpoint on QLineEdit::setCompleter in python.

Resources