I created a button 'Add InputBox' that will add an input box once the
user presses it since i won't be knowing the number of InputBoxes the
user may require.
These InputBoxes should be arranged vertically such that each one added
will be created at a specific position right below the previous one
without considering the size_hint (even if it's one input box, it
should not cover the screen. If i told it to be at position (0, 10), it
should not span past that position).
I tried the BoxLayout whose first InputBox created covered the
whole screen. The second one takes up 50% of the screen. The 3rd one,
1/3 of the screen and so on.
I checked out the FloatLayout too with the code below:
class NextWindow(Screen):
def __init__(self, **kwargs):
super(NextWindow, self).__init__(**kwargs)
self.count_box = 1
self.layout = FloatLayout()
def addInputBox(self, obj):
inputBox = TextInput(multiline=False,size_hint=(0.2, 0.05),
pos_hint={'top': self.count_box})
if self.count_box < 150:
self.count_box = self.count_box + 10 #Changes the value of the position
self.layout.add_widget(inputBox) #so that the next InputBox is created
else: #under the previous one.
pass #When 14 InputBoxes have been created
At the press of the button
'Add InputBox', the value of self.count_box is changed so that the value of
'top' changes after each call so that the next InputBox goes under the previous one.
This did not take up the whole screen. It displayed just the first InputBox but subsequent ones are not shown on the screen when 'Add InputBox' is pressed.
This stems from a poor understanding of the property pos_hint. A way to carry it out is to ignore pos_hint and supply the actual positional property, 'pos'. The value of 'pos' is changed after every click of the 'Add Course' button.
Related
I am writing an application on kivy, and I have a problem in the button. The problem is that when you click on the button - it does not work, but there is click in different places (buttons) it will start working
This is my kv code:
Button:
id:add_task
text:"Add"
size_hint:(1,1)
background_color:(135/255,194/255,133/255)
background_normal:''
on_press: root.add_new_task()
And here I implement the change of color and text of the button when you click on it:
def add_new_task(self):
if self.ctrl_add_tast == 0:
self.add_task.text = "Cancel"
self.add_task.background_color = (219/255, 74/255, 74/255)
self.input_add_task.opacity = 1
self.input_add_task.size = (395,175)
self.ctrl_add_tast = 1
elif self.ctrl_add_tast == 1:
self.add_task.text = "Add"
self.add_task.background_color = (135/255, 194/255, 133/255)
self.input_add_task.opacity = 0
self.input_add_task.size = (0,0)
self.ctrl_add_tast = 0
When executing this code, a button appears. When clicked, it changes the color and text.
Everything works as it should, but you need to click on the button in different places to make it work (to be more precise, it is necessary to click on the left, lower and upper sides, about 20 pixels)
And I do not use the "padding" properties in this place
I am building a wx.ComboCtrl with a wx.ListCtrl attached. The reason for doing this is because I want to set the foreground colour of the choices (the colour shows the user the status of the item). I want these colours to show when the box is dropped down and when a user has made a selection.
The problem I run into is that on Linux (Ubuntu 20.04), after a selection was made, the background colour of the wx.ComboCtrl remains blue (and the foreground colour remains white), even if I move focus to another widget. It doesn't matter which colour I set for the text to be displayed on the ComboCtrl, it remains white text with a blue background. See screenshot.
I can only get it to show me the default (gray) background with my selected foreground colour if I move the focus to another window and then back to my own window.
In Windows this doesn't happen: after selecting an item, the background colour of the ComboCtrl is default (gray), however it does show a little dotted line around the selection. See screenshot.
Here is the modified demo code that I am using to reproduce the issue. The comments in the code are left overs from some of the things I tried.
#!/usr/bin/env python
import wx
import os
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# This class is used to provide an interface between a ComboCtrl and the
# ListCtrl that is used as the popoup for the combo widget.
class ListCtrlComboPopup(wx.ComboPopup):
def __init__(self):
wx.ComboPopup.__init__(self)
self.lc = None
def AddItem(self, txt, _colour):
self.lc.InsertItem(self.lc.GetItemCount(), txt)
_entry = self.lc.GetItem(self.lc.GetItemCount() - 1)
_entry.SetTextColour(_colour)
#_entry.SetItemTextColour(_colour)
self.lc.SetItem(_entry)
def OnMotion(self, evt):
item, flags = self.lc.HitTest(evt.GetPosition())
if item >= 0:
self.lc.Select(item)
self.curitem = item
def OnLeftDown(self, evt):
self.value = self.curitem
self.Dismiss()
# The following methods are those that are overridable from the
# ComboPopup base class. Most of them are not required, but all
# are shown here for demonstration purposes.
# This is called immediately after construction finishes. You can
# use self.GetCombo if needed to get to the ComboCtrl instance.
def Init(self):
self.value = -1
self.curitem = -1
# Create the popup child control. Return true for success.
def Create(self, parent):
self.lc = wx.ListCtrl(parent, style=wx.LC_SINGLE_SEL | wx.SIMPLE_BORDER | wx.LC_REPORT | wx.LC_NO_HEADER)
self.lc.InsertColumn(0, '')
self.lc.Bind(wx.EVT_MOTION, self.OnMotion)
self.lc.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
return True
# Return the widget that is to be used for the popup
def GetControl(self):
return self.lc
# Called just prior to displaying the popup, you can use it to
# 'select' the current item.
def SetStringValue(self, val):
idx = self.lc.FindItem(-1, val)
if idx != wx.NOT_FOUND:
self.lc.Select(idx)
# Return a string representation of the current item.
def GetStringValue(self):
if self.value >= 0:
return self.lc.GetItemText(self.value)
return ""
# Called immediately after the popup is shown
def OnPopup(self):
wx.ComboPopup.OnPopup(self)
# Called when popup is dismissed
def OnDismiss(self):
print (self.GetStringValue())
wx.ComboPopup.OnDismiss(self)
# This is called to custom paint in the combo control itself
# (ie. not the popup). Default implementation draws value as
# string.
def PaintComboControl(self, dc, rect):
wx.ComboPopup.PaintComboControl(self, dc, rect)
# Receives key events from the parent ComboCtrl. Events not
# handled should be skipped, as usual.
def OnComboKeyEvent(self, event):
wx.ComboPopup.OnComboKeyEvent(self, event)
# Implement if you need to support special action when user
# double-clicks on the parent wxComboCtrl.
def OnComboDoubleClick(self):
wx.ComboPopup.OnComboDoubleClick(self)
# Return final size of popup. Called on every popup, just prior to OnPopup.
# minWidth = preferred minimum width for window
# prefHeight = preferred height. Only applies if > 0,
# maxHeight = max height for window, as limited by screen size
# and should only be rounded down, if necessary.
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
return wx.ComboPopup.GetAdjustedSize(self, minWidth, prefHeight, maxHeight)
# Return true if you want delay the call to Create until the popup
# is shown for the first time. It is more efficient, but note that
# it is often more convenient to have the control created
# immediately.
# Default returns false.
def LazyCreate(self):
return wx.ComboPopup.LazyCreate(self)
#----------------------------------------------------------------------
class MyTestPanel(wx.Panel):
def __init__(self, parent, log):
self.log = log
wx.Panel.__init__(self, parent, -1)
txt = wx.TextCtrl(self, wx.ID_ANY, pos=(100,100))
comboCtrl = wx.ComboCtrl(self, wx.ID_ANY, "Third item", (10,10), size=(200,-1), style=wx.CB_READONLY)
popupCtrl = ListCtrlComboPopup()
# It is important to call SetPopupControl() as soon as possible
comboCtrl.SetPopupControl(popupCtrl)
# Populate using wx.ListView methods
popupCtrl.AddItem("First Item", [255, 127, 0])
popupCtrl.AddItem("Second Item", [192, 127, 45])
popupCtrl.AddItem("Third Item", [25, 223, 172])
#popupCtrl.GetAdjustedSize(100, 35, 100)
#comboCtrl.SetTextColour(_colour)
comboCtrl.SetForegroundColour(wx.Colour(235, 55, 55))
#----------------------------------------------------------------------
def runTest(frame, nb, log):
win = MyTestPanel(nb, log)
return win
#----------------------------------------------------------------------
overview = """<html><body>
<h2><center>wx.combo.ComboCtrl</center></h2>
A combo control is a generic combobox that allows a totally custom
popup. In addition it has other customization features. For instance,
position and size of the dropdown button can be changed.
</body></html>
"""
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
Question 1:
How can I make it so that once an item has been selected the appropriate text colour (the one I programmatically set) and default (gray) background colour is shown.
Question 2:
When dropping down the ComboCtrl, it is showing the ListCtrl, which has a single column only. You can see that the "Second item" on the list is not displayed entirely because the column is too narrow. How can I make it so that the column is always the same width as the widget itself, even when the ComboCtrl resizes (as a result of resizing the parent window)?
Question 3:
Not overly important, but while we are on the subject: is there a way to get rid of the little dotted box that is shown around the selected item when running this in Windows?
In advance, thank you very much for your thoughts and ideas on this.
Marc.
I'm looking for solution of merge discrete slider and QTableWidget (see attached screenshot). Slider is used as selecting pointer(instead of default selecting highlighter). How it can be implemented using Qt (PyQt)?
Small premise. Technically, according to StackOverflow standards, your question is not a very good one. I'll explain it at the end of this answer.
Getting what you are asking for is not easy, most importantly because sliders are not built for that purpose (and there are many UX reasons for which you should not do that, go to User Experience to ask about them).
The trick is to create a QSlider that has the table widget as a parent. Creating a widget with a parent ensures that the child widget will always be enclosed within the parent boundaries (this is only false for QMainWindow and QDialog descendants), as long as the widget is not added to the parent layout. This allows you to freely set its geometry (position and size).
In the following example I'm adding an internal QSlider, but the main issue about this widget is aligning it in such a way that its value positions are aligned with the table contents.
class GhostHeader(QtWidgets.QHeaderView):
'''
A "fake" vertical header that does not paint its sections
'''
def __init__(self, parent):
super().__init__(QtCore.Qt.Vertical, parent)
self.setSectionResizeMode(self.Fixed)
def paintEvent(self, event):
pass
class SliderTable(QtWidgets.QTableWidget):
def __init__(self, rows=0, columns=0, parent=None):
super().__init__(rows, columns, parent)
self.horizontalHeader().setStretchLastSection(True)
self.setHorizontalHeaderLabels(['Item table'])
self.setVerticalHeader(GhostHeader(self))
# create a slider that is a child of the table; there is no layout, but
# setting the table as its parent will cause it to be shown "within" it.
self.slider = QtWidgets.QSlider(QtCore.Qt.Vertical, self)
# by default, a slider has its maximum on the top, let's invert this
self.slider.setInvertedAppearance(True)
self.slider.setInvertedControls(True)
# show tick marks at each slider value, on both sides
self.slider.setTickInterval(1)
self.slider.setTickPosition(self.slider.TicksBothSides)
self.slider.setRange(0, max(0, self.rowCount() - 1))
# not necessary, but useful for wheel and click interaction
self.slider.setPageStep(1)
# disable focus on the slider
self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
self.slider.valueChanged.connect(self.selectRowFromSlider)
self.slider.valueChanged.connect(self.updateSlider)
self.verticalScrollBar().valueChanged.connect(self.updateSlider)
self.model().rowsInserted.connect(self.modelChanged)
self.model().rowsRemoved.connect(self.modelChanged)
def selectRowFromSlider(self, row):
if self.currentIndex().isValid():
column = self.currentIndex().column()
else:
column = 0
self.setCurrentIndex(self.model().index(row, column))
def modelChanged(self):
self.slider.setMaximum(max(0, self.rowCount() - 1))
self.updateSlider()
def updateSlider(self):
slider = self.slider
option = QtWidgets.QStyleOptionSlider()
slider.initStyleOption(option)
style = slider.style()
# get the available extent of the slider
available = style.pixelMetric(style.PM_SliderSpaceAvailable, option, slider)
# compute the space between the top of the slider and the position of
# the minimum value (0)
deltaTop = (slider.height() - available) // 2
# do the same for the maximum
deltaBottom = slider.height() - available - deltaTop
# the vertical center of the first item
top = self.visualRect(self.model().index(0, 0)).center().y()
# the vertical center of the last
bottom = self.visualRect(self.model().index(self.model().rowCount() - 1, 0)).y()
# get the slider width and adjust the size of the "ghost" vertical header
width = self.slider.sizeHint().width()
left = self.frameWidth() + 1
self.verticalHeader().setFixedWidth(width // 2 + left)
viewGeo = self.viewport().geometry()
headerHeight = viewGeo.top()
# create the rectangle for the slider geometry
rect = QtCore.QRect(0, headerHeight + top, width, headerHeight + bottom - top // 2)
# adjust to the values computed above
rect.adjust(0, -deltaTop + 1, 0, -deltaBottom)
# translate it so that its center will be between the vertical header and
# the table contents
rect.translate(left, 0)
self.slider.setGeometry(rect)
# set the mask, in case the item view is scrolled, so that the top of the
# slider won't be shown in the horizontal header
visible = self.rect().adjusted(0, viewGeo.top(), 0, 0)
mask = QtGui.QPainterPath()
topLeft = slider.mapFromParent(visible.topLeft())
bottomRight = slider.mapFromParent(visible.bottomRight() + QtCore.QPoint(1, 1))
mask.addRect(QtCore.QRectF(topLeft, bottomRight))
self.slider.setMask(QtGui.QRegion(mask.toFillPolygon(QtGui.QTransform()).toPolygon()))
def currentChanged(self, current, previous):
super().currentChanged(current, previous)
if current.isValid():
self.slider.setValue(current.row())
def resizeEvent(self, event):
# whenever the table is resized (even when first shown) call the base
# implementation (which is required for correct drawing of items and
# selections), then update the slider
super().resizeEvent(event)
self.updateSlider()
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.table = SliderTable()
self.table.setRowCount(4)
self.table.setColumnCount(1)
self.table.setHorizontalHeaderLabels(['Item table'])
layout.addWidget(self.table)
for row in range(self.table.rowCount()):
item = QtWidgets.QTableWidgetItem('item {}'.format(row + 1))
item.setTextAlignment(QtCore.Qt.AlignCenter)
self.table.setItem(row, 0, item)
Why this question is not that good?
Well, it's dangerously close to the "I don't know how to do this, can you do it for me?" limit. You should provide any minimal, reproducible example (it doesn't matter if it doesn't work, you should do some research and show your efforts), and the question is a bit vague, even after some clarifications in the comment sections.
Long story short: if it's too hard and you can't get it working, you probably still need some studying and exercise before you can achieve it. Be patient, study the documentation: luckily, Qt docs are usually well written, so it's just a matter of time.
Whenever i retrieve values from combobox / entry i always get the first value / returns null.
I can't seem to find a workaround. I think the problem seems to lie in using a fixed container? The only solution i can think of is to replace it with a grid layout but that means i will have to redo my entire code from the start.
creating the fixed container
class MainWindow(Gtk.Window):
def __init__(self): #constructor
Gtk.Window.__init__(self, title="dawadaw")
self.set_size_request(900, 700)
container = Gtk.Fixed()
self.add(container)
# Stack
main_area = Gtk.Stack()
#stackwindow1
sw1 = Gtk.Fixed()
Combobox and entry
list = Gtk.ComboBoxText()
list.append_text("string1")
list.append_text("string2")
list.append_text("string3")
list.set_active(0)
sw1.put(list, 180, 167)
Entry1 = Gtk.Entry()
Entry1.set_width_chars(10)
sw1.put(Entry1, 180, 297)
button to retrieve data
button = Gtk.Button(label="retrieve values")
button.connect("clicked", self.button_clicked,
Entry1.get_text(), sw1.get_active_text())
sw1.put(button, 370, 600)
main_area.add_titled(sw1, "Stack", "stack1")
Button_Clicked funtcion
def button_clicked(self, button, *data):
print(data)
I want to print the selected combobox value and the value i entered in the entry box. However, the combobox only returns the string1, regardless of what i select and the entry widget does not return anything.
I'm stuck and i think the problem lies with me using a fixed container. But i don't want to redo everything if i don't have to.
Help is greatly appreciated!
I've tried seek in the internet solving my problem. But it has no result at all.
So. Please look at this code sample:
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
btn = Button(text='Hello world')
btn.size_hint = (1, .3)
btn.pos_hint = {'top':1}
title = Label(text=('[color=ff3333]Hello world[/color]'),
font_size=str(12) + 'sp', markup=True)
self.add_widget(title)
self.add_widget(btn)
title.texture_update()
title.text_size = (Window.width, None)
title.height = title.texture_size[1]
with title.canvas:
Color(1., 1., 0)
Rectangle(size=title.size, pos=title.pos)
print(title.size)
print(title.pos)
print(title.texture_size)
And now look at image:
Can anyone tell me why print(title.pos) say (0,0), canvas draw rectangle at (0,0) but text appear at another position?
I've already overwhelmed with this...
Thank you.
You need to see differences between a Widget as a "container" like thing and its content - what's drawn on the canvas. A Widget needs to take some space and so it does.
The important defaults for a Widget are:
position [0, 0]
size_hint [1, 1]
size [100, 100]
What you did:
change text size to [800 (by default), None]
title.text_size = (Window.width, None)
set height of a Widget to take the same height as the rendered text has
title.height = title.texture_size[1]
These changes didn't do anything with the container, because you forgot a basic thing in this layout, which is:
FloatLayout honors the pos_hint and the size_hint properties of its children.
Therefore either put size_hint_y=None into kwargs or do title.size_hint_y=None before setting height for the first time. When size_hint is properly set/removed from the way, you can manipulate the container, which if used correctly:
title = Label(...)
title.size_hint=[None, None]
title.size = title.texture_size
makes the container encapsulate the rendered text. This makes the rendered text's position the same position which has the container (Widget).
Note: When encountering similar stuff, printing is nice, yet not as useful as using Inspector module, mainly for positioning/sizing/<any layout related thing> debugging:
python main.py -m inspector
the title Label is located inside of the FloatLayout Widget where title.canvas is drawing from. This is why the title.pos is (0,0). title.text is the actual position you are looking for, but the problem with that is title.text is a string and doesn't have a pos attribute :/