I want to add images to my buttons. I have all my styling in a separate file.
According to Qt docs https://doc.qt.io/qt-5/stylesheet-syntax.html#selector-types I should be able to access a specific button using the following syntax
QPushButton#my_button_name {}
but it does not seem to work.
Here is an example from my code:
# script with widgets and layouts
class Tab1():
self.button1 = QPushButton()
# styling script
def button_style():
return '''QPushButton#button1 {font:15px;}'''
I am misunderstanding something? I tried adding 'self.' to the name in the styling script but it does not work either.
Name selectors for stylesheets must be used by setting the objectName property; python attribute naming is completely useless for this as Qt doesn't know anything about it.
In order to correctly apply the stylesheet as you want to, you need to set the object name:
# ...
self.button1 = QPushButton()
self.button1.setObjectName('button1')
self.setStyleSheet('QPushButton#button1 {font:15px;}')
Related
I am searching for a way to have a QTreeView that contains hierarchical items which themselfs have a layout that is propperly drawn.
I tried to inherit from both QStandartItem and QWidget (to have a layout) but the second i set the layout on the widget part of this class the programm is shutting down when it tries to render.
class modPackItem(qtg.QStandardItem,qtw.QWidget):
def __init__(self,txt:str='',image_path:str='./assets/defaultModPack.jpg'):
super().__init__()
fnt = qtg.QFont('Calibri',12)
fnt.setBold(True)
self.setEditable(False)
self.setForeground(qtg.QColor(0,0,0))
self.setFont(fnt)
self.setText(txt)
self.horLayout = qtw.QHBoxLayout()
self.horLayout.addWidget(qtw.QLabel("test"))
#self.setLayout(self.horLayout) #this breaks the rendering
modPack_image = qtg.QImage(image_path)
self.setData(modPack_image.scaled(64,64,qtc.Qt.AspectRatioMode.KeepAspectRatioByExpanding),qtc.Qt.ItemDataRole.DecorationRole)
Is there a possible way to have all items in the QTreeView contain layouts (For example with multiple texts[description,tag-words,etc]).
Note: I also considered switching to a simple List of widgets which have children containing the hierarchical items. But that would increase complexity of my app-structure a lot and therefore i would like to avoid that.
Edit: To clearify what i want to do:
I want to build a mod(pack) manager in the style of the technic-launcher for minecraft mods but instead for any kind of game in any kind of infrastructure(steam, local instal,etc). By clicking different buttons i add new "modpacks" or "mods" (optimally custom QStandartItem with Layout for all the data) in an hierarchical fashion (therefore treeview). Adding the items and the steam-subsrciption or filecopy logic is no problem but i would like to see all infos (Name,descritpion, custom tags) on the overview (like in the example pic). I know i could bind the QStandartItem selection method to a new popup showing all infos but that would be inconvinient.
Edit2: On terms of implementation i just add the QStandartItem-object as an additional row to the root-node before setting the model. I allready tested adding new objects to the rootnode by clicking on a button and that worked fine. Just setting the layout in the object crashes the application at start.
class SteamModManager_Dialog(qtw.QDialog):
window: Ui_SteamModManagerFrame
treeModel: qtg.QStandardItemModel
rootNode: qtg.QStandardItem
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.window = Ui_SteamModManagerFrame()
self.window.setupUi(self)
self.window.label_footer.setText("")
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
On the behalf of #musicamente here the solution that worked out for me:
I created a widget in the designer (as usual, not posting the full ui code here).
Then i implemented the following code into the Dialog:
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
modPackWidget = qtw.QWidget()
ui = Ui_modPackWidget()
ui.setupUi(modPackWidget)
self.window.tView_modPacks.setIndexWidget(self.treeModel.index(0,0),modPackWidget)
This code resulted setting the custom widget to the treeview item. Here the final look:
I want to make my tkinter Text to be only an output and not an input. Through some research I've found that text.config(state="disabled") disables user input, but it still allows for selecting text, which I do not want.
How can I get my Text widget to be unselectable and unwritable?
The simplest way is to replace the default text bindings that support selection so that they do nothing. There are a couple ways to do this: using binding tags you can remove all default bindings, or you can remove the bindings to only a subset of default bindings.
Removing all default bindings
All bindings on widgets -- including the default bindings -- are associated with binding tags (also called "bindtags"). The binding tag for the the text widget is "Text", and all default bindings for the text widget are associated with this tag. If you remove that binding tag, you remove all Text-specific bindings.
The default binding tags for any widget is a tuple of the string representation of the widget, the internal widget class (in this case, "Text"), the internal name of the toplevel window (in this case, root), and the special tag "all".
In the following example we change the binding tags so that "Text" is not included, effectively removing all default bindings on the text widget:
import tkinter as tk
root = tk.Tk()
text = tk.Text(root)
text.bindtags((str(text), str(root), "all"))
Removing specific bindings
If you prefer to keep some of the default bindings, you can replace just the ones that you don't want. You do that by creating your own bindings, and having those bindings return the string "break". This special return value tells tkinter to stop processing the event any further.
For example, to prevent a double-click from selecting the word under the cursor you could do this:
text.bind("<Double-1>", lambda event: "break")
The downside to this approach is that you have to figure out what all of the bindings are that are related to the selection mechanism. On the other hand, it gives you complete control over what each key or button press does.
A read-only, unselectable text widget.
class Textarea(tkinter.Text):
def __init__(self, master, **kw):
super().__init__(master, **kw)
# disable text alteration
self.configure(state="disabled")
# untag any selection from beginning to end
def unselect(event):
self.tag_remove("sel", "1.0", "end")
# catch different ways selections could be made and unselect before copying or cutting
good = ["<ButtonRelease-1>", "<Leave>", "<Control-c>", "<Control-C>", "<Control-x>", "<Control-X>"]
better = good + ["<Shift-Left>", "<Shift-Up>", "<Shift-Right>", "<Shift-Down>", "<Shift-Home>", "<Shift-End>", "<Shift-Next>", "<Shift-Prior>"]
excessive = better + ["<Shift-KP_1>", "<Shift-KP_2>", "<Shift-KP_3>", "<Shift-KP_4>", "<Shift-KP_6>", "<Shift-KP_7>", "<Shift-KP_8>", "<Shift-KP_9>"]
for sequence in better:
self.bind(sequence, unselect, add="+")
# remove the appearance of selection
self.configure(selectforeground=self.cget("foreground"), selectbackground=self.cget("background"))
# disallow export of selection in case anything gets through
self.configure(exportselection=False)
Tested on python 3.8.2
I believe you will have to replace it with another widget that such as a Label or LabelFrame to accomplish this. As well you could use a from tkinter import messagebox and have the text you want pop up in another window (like an info window or error message window). I think that as far as the Text widget goes, setting the state to disabled is the best you can do for your purposes unfortunately and users will be able to copy that text despite being unable to edit it.
Here is the simplest method to prevent text from being selected/highlighted when you just want the Text widget to be an ordinary log that is disabled and unselectable.
When I had the issue I figured I just needed to set some Text configuration property (highlightbackground, highlightcolor or selectbackground) to "Black". Nothing worked. The Text widget employs tags that can be used to mark up the Text within the control. Configuration for user defined tags as well as the special tag "sel" have a number of settings including foreground (color) and background (color).
tag_config("sel", background="black")
Too easy right? That doesn't work either.
Turns out that the highlight is actually a bitmap overlaid on the text. This is controlled by the bgstipple (bitmap) configuration for the tag. The documentation indicates that there are a number of system bitmaps (shades of grey) that can be used however it is also possible to specify your own. The bitmap needs to be an xbm and it's easy to create your own as it's a text file.
Put the following in a file named transparent.xbm.
#define trans_width 2
#define trans_height 2
static unsigned char trans_bits[] = {
0x00, 0x00
};
Here it is...
class TextLog(tk.Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tag_config("sel", bgstipple="#transparent.xbm",
foreground="white")
self.config(state="disabled")
def write_log(self, text="", clear=False)
self.configure(state='normal')
if clear is True:
self.delete("1.0","end")
self.insert(tk.END, text)
self.see(tk.END)
self.config(state="disabled")
Depending on where the .xbm is relative to the module using the TextLog you may need to prefix it with a path "#path/to/transparent.xbm"
I want to change the label of the 'Apply' button of a gtk.Assistant to 'Start'. I can't find the corresponding gtk.Button widget in the Assistant instance.
Here's some basic code for a two-page Assistant:
import gtk
a = gtk.Assistant()
page = gtk.CheckButton("Something optional")
a.append_page(page)
a.set_page_type(page, gtk.ASSISTANT_PAGE_CONTENT)
a.set_page_title(page, "Make decisions")
a.set_page_complete(page, True)
page = gtk.Label("Alright, let's build some foo.")
a.append_page(page)
a.set_page_type(page, gtk.ASSISTANT_PAGE_CONFIRM)
a.set_page_title(page, "Confirm")
a.set_page_complete(page, True)
a.connect('delete-event', gtk.main_quit)
a.connect('close', gtk.main_quit)
a.show_all()
gtk.main()
On the final page you'll see the 'Apply' button. I want to change that text to 'Start'.
gtk.Assistant.children() and .get_children() return the list of page widgets.
gtk.Assistant.get_child() returns None.
gtk.Assistant.get_action_area() isn't a method.
Here's a link to the documentation.: http://www.pygtk.org/docs/pygtk/class-gtkassistant.html
How do I find the gtk.Button I'm interested in?
I managed to find a solution while experimenting with workarounds.
gtk.Assistant overrides the gtk.Container.get_children() method with something that returns the list of pages, but it is still in fact the parent of a gtk.HBox() which contains the buttons for 'Next', 'Apply', 'Cancel', etc.
The method gtk.Assistant.add_action_widget() adds a widget to the so-called "action area". It turns out this is the HBox containing the relevant buttons. The following function will produce a reference to the HBox:
def get_buttons_hbox(assistant):
# temporarily add a widget to the action area and get its parent
label = gtk.Label('')
assistant.add_action_widget(label)
hbox = label.get_parent()
hbox.remove(label)
return hbox
Then the buttons are retrieved using get_buttons_hbox(a).get_children().
for child in get_buttons_hbox(a).get_children():
print child.get_label()
This prints:
gtk-goto-last
gtk-go-back
gtk-go-forward
gtk-apply
gtk-cancel
gtk-close
So the following code solves the problem (using get_buttons_hbox() defined above):
for child in get_buttons_hbox(a).get_children():
label = child.get_label()
if label == 'gtk-apply':
child.set_label('Start')
I'm not sure this will be possible with pygtk. If you switch to GObject Introspection with python you can set a fully custom action area. From the Gtk3 GtkAssistant documentation:
If you have a case that doesn't quite fit in GtkAssistants way of
handling buttons, you can use the GTK_ASSISTANT_PAGE_CUSTOM page type
and handle buttons yourself.
and
GTK_ASSISTANT_PAGE_CUSTOM Used for when other page types are not
appropriate. No buttons will be shown, and the application must add
its own buttons through gtk_assistant_add_action_widget().
I have a window containing multiple QRowWidgets, which are custom widgets defined by me. These QRowWidgets contain QLineEdits and other standard widgets. To show or hide certain parts of a QRowWidget, I overdefined the focusInEvent() methodes of all the widgets within it. It works perfectly, when I click on the QRowWidget, the hidden elements appear.
The weird thing is that the blinking cursor line hovewer doesn't appear in the QLineEdits within the custom widgets. I can select them both by a mouse click or with Tab, and a glow effect indicates that the QLineEdit is selected in it, I can select a text in it, or start typing at any location wherever I clicked, but the cursor never appears and it's quite annoying.
My 1st thought was that it is a bug on Mac, but I have the same experience on SuSe Linux.
I'm using python 2.7 and PyQt4.
This is in the __init__() of the QRowWidget:
for i in self.findChildren(QWidget):
i.focusInEvent = self.focusInEvent
And then this is the own focusInEvent():
def focusInEvent(self, event):
if self.pself.focusedLine:
self.pself.focusedLine.setStyleSheet("color: #666;")
self.pself.focusedLine.desc.hide()
self.pself.focusedLine.closebutton.hide()
self.setStyleSheet("color: #000;")
self.desc.show()
self.closebutton.show()
self.pself.focusedLine = self
I suspect you do not make a call to the original focusInEvent() when you override it. Your function should look something like:
def focusInEvent(self,...):
QParent.focusInEvent(self,...)
# the rest of your code
where QParent is the nearest base class for your widgets is.
Either that, or make sure you call focusInEvent() on your QLineEdit widgets as part of your function.
Given the comments, it sounds like you are dynamically reassigning the focusInEvent function on the insantiatations in your custom widget. I would either make a derived class for each of the widgets you use that just overrides focusInEvent as above, or include a line like
type(self).focusInEvent(self,..)
in you function.
self.button = gtk.Button(stock=gtk.STOCK_DELETE)
Only Shows:
Delete
The Python equivalent for setting the property without having to change any system config files is:
settings = gtk.settings_get_default()
settings.props.gtk_button_images = True
This should follow a call to window.show() and, obviously, precede the gtk.main() loop.
This is a recent change in GTK - the developers wanted icons not to appear on buttons. On Linux, this can be changed by editing the gconf key
/desktop/gnome/interface/buttons_have_icons
On windows, I think (I haven't actually tried this) that you need to set a value in your gtkrc file (for me it's in C:\Program Files\Gtk+\etc\gtkrc) and use a theme that supports icons (I think the default one doesn't).
You can also add gtk-button-images = 1 to your ~/.gtkrc-2.0 file after setting the theme which may over ride the option from gconf.
EDIT in answer to your comment:
Just like this answer, but in Python: In Gtk, how do I make a Button with just a stock icon?
For python, it's just
image = gtk.Image()
# (from http://www.pygtk.org/docs/pygtk/gtk-stock-items.html)
image.set_from_stock(gtk.STOCK_**)
button = gtk.Button()
button.set_image(image)
button.set_label("")
I had to do this to get it to work from Python without changing my config file. When I called set_image(), no image was being displayed.
image = gtk.Image()
image.set_from_stock(gtk.STOCK_**, gtk.ICON_SIZE_BUTTON)
button = gtk.Button()
button.add(image)
button.show()
If you work with pygobject, the new syntax is:
image.set_from_stock(gtk.STOCK_**, Gtk.IconSize.BUTTON)
I had the same issue in GTKmm on Windows. The "MS-Windows" theme disables images on stock buttons and the theme has priority over settings in gtkrc (so putting gtk-button-images = true in gtkrc didn't help). What I did is to modify the GTK settings runtime, and the images appeared as expected. :) Here's the code in C++:
Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();
/* force using icons on stock buttons: */
settings->property_gtk_button_images() = true;
It should be placed after the first window is constructed.
in Gtk3 gtk.STOCK method has been deprecated from v3.10.
Deprecated since version 3.10: Use Gtk.Button.new_with_label ()
instead.
In the case it doesn't help since it points to the custom label solution (new_with_label) If you want to use STOCK stuff you still can do so with new methods Gtk.Button.new_from_icon_name(icon_name, size) and Gtk.Button.new_with_mnemonic(label) which will create new buttons with stock icon and label respectively.
Example new button with a "stock" icon:
button = Gtk.Button.new_from_icon_name ("edit-paste", Gtk.IconSize.SMALL_TOOLBAR)
Example new button with a "stock" label:
button = Gtk.Button.new_with_mnemonic("_Open")
NOTE: on serious code creating a constant variable instead of using the string straight is a better option :)
References:
Gtk.Button
static new_with_mnemonic(label)
new_from_icon_name(icon_name, size)
Freedesktops Naming Convention
You can show explicitly the button image, justly, Gtk+ developers do not recommend doing this because it's overrides the Gtk+ user configuration.
So...
button.get_image().show()