How to set non-selectable default text on QComboBox? - c++

Using a regular QComboBox populated with items, if currentIndex is set to -1, the widget is empty. It would be very useful to instead have an initial descriptive text visible in the combo box(e.g. "--Select Country--", "--Choose Topic--", etc.) which is not shown in the dropdown list.
I couldn't find anything in the documentation, nor any previous questions with answers.

It doesn't appear that case was anticipated in the Combo Box API. But with the underlying model flexibility it seems you should be able to add your --Select Country-- as a first "legitimate" item, and then keep it from being user selectable:
QStandardItemModel* model =
qobject_cast<QStandardItemModel*>(comboBox->model());
QModelIndex firstIndex = model->index(0, comboBox->modelColumn(),
comboBox->rootModelIndex());
QStandardItem* firstItem = model->itemFromIndex(firstIndex);
firstItem->setSelectable(false);
Depending on what precise behavior you want, you might want to use setEnabled instead. Or I'd personally prefer it if it was just a different color item that I could set it back to:
Qt, How do I change the text color of one item of a QComboBox? (C++)
(I don't like it when I click on something and then get trapped to where I can't get back where I was, even if it's a nothing-selected-yet-state!)

One way you can do something similar is to set a placeholder:
comboBox->setPlaceholderText(QStringLiteral("--Select Country--"));
comboBox->setCurrentIndex(-1);
This way you have a default that cannot be selected.

Leaving my solution here from PyQt5. Create a proxy model and shift all the rows down one, and return a default value at row 0.
class NullRowProxyModel(QAbstractProxyModel):
"""Creates an empty row at the top for null selections on combo boxes
"""
def __init__(self, src, text='---', parent=None):
super(NullRowProxyModel, self).__init__(parent)
self._text = text
self.setSourceModel(src)
def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
if self.sourceModel():
return self.sourceModel().index(proxyIndex.row()-1, proxyIndex.column())
else:
return QModelIndex()
def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
return self.index(sourceIndex.row()+1, sourceIndex.column())
def data(self, proxyIndex: QModelIndex, role=Qt.DisplayRole) -> typing.Any:
if proxyIndex.row() == 0 and role == Qt.DisplayRole:
return self._text
elif proxyIndex.row() == 0 and role == Qt.EditRole:
return None
else:
return super(NullRowProxyModel, self).data(proxyIndex, role)
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
return self.createIndex(row, column)
def parent(self, child: QModelIndex) -> QModelIndex:
return QModelIndex()
def rowCount(self, parent: QModelIndex = ...) -> int:
return self.sourceModel().rowCount()+1 if self.sourceModel() else 0
def columnCount(self, parent: QModelIndex = ...) -> int:
return self.sourceModel().columnCount() if self.sourceModel() else 0
def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
if not self.sourceModel():
return None
if orientation == Qt.Vertical:
return self.sourceModel().headerData(section-1, orientation, role)
else:
return self.sourceModel().headerData(section, orientation, role)

Related

unable to return updated widget inside ValueListenableBuilder

I need to update list inside ListView widget, so I set a valuenotifierbuilder like this:
final ValueNotifier<ModelRegis> modelFoto =
ValueNotifier<ModelRegis>(ModelRegis());
and the structure of the model is like this
class ModelRegis {
String loadFoto;
bool adaFoto;
int idFotoUser, index;
List<Widget> independentFoto;
ModelRegis(
{this.loadFoto = "done",
this.adaFoto = false,
this.idFotoUser,
this.index,
this.independentFoto});
}
here is how I try to update the widget using ValueListenableBuilder
ValueListenableBuilder(
valueListenable: modelFoto,
builder: (_, val, __) {
List<Widget> listFuture = [];
if (val.adaFoto) {
Widget dummyList =
Text("after click");
List temp = val.independentFoto;
temp[val.index] = dummyList;
modelFoto.value = ModelRegis(
loadFoto: modelFoto
.value.loadFoto,
adaFoto:
modelFoto.value.adaFoto,
idFotoUser: modelFoto
.value.idFotoUser,
index:
modelFoto.value.index,
independentFoto: temp);
// modelFoto.notifyListeners();
print("2. $temp");
print(
"2. ${val.independentFoto}");
// modelFoto.notifyListeners();
return ListView(
physics:
const AlwaysScrollableScrollPhysics(),
shrinkWrap: false,
children:
val.independentFoto);
}
I have tried to print val.independentFoto and the result is like this
[Text("after click"), FutureBuilder<dynamic>, FutureBuilder<dynamic>, FutureBuilder<dynamic>, FutureBuilder<dynamic>]
but the return result in ListView is always not update, so it will return a List that consist of widget like this
[FutureBuilder<dynamic>, FutureBuilder<dynamic>, FutureBuilder<dynamic>, FutureBuilder<dynamic>, FutureBuilder<dynamic>]
the return result doesn't update widget to Text("after click") at first index
is there a right way to update list of widget in ValueListenableBuilder?

How to access Qt::DisplayRole and specify columns in TableView

The QFileSystemModel has the following data function:
Variant QFileSystemModel::data(const QModelIndex &index, int role) const
{
Q_D(const QFileSystemModel);
if (!index.isValid() || index.model() != this)
return QVariant();
switch (role) {
case Qt::EditRole:
case Qt::DisplayRole:
switch (index.column()) {
case 0: return d->displayName(index);
case 1: return d->size(index);
case 2: return d->type(index);
case 3: return d->time(index);
I wonder how I can access the DisplayRole and specify the column I want in a QML TableViewColumn.
I want to use it in
TableView {
model: fileSystemModel
TableViewColumn {
role: //what comes here?
}
}
If you want to access within a delegate you have to use styleData.index that returns the QModelIndex and pass it the value of the role, in this case Qt::DisplayRole that according to the docs is 0:
view.model.data(styleData.index, 0)
if you know the row, column and QModelIndex of parent:
view.model.data(view.model.index(row, colum, ix_parent), 0)
If you plan to reuse the model several times, you could consider sub-classing QFileSystemModel and add a custom role:
class FileSystemModel : public QFileSystemModel
{
public:
explicit FileSystemModel(QObject *parent = nullptr) : QFileSystemModel(parent) {}
enum Roles {
FileSizeRole = Qt::UserRole + 1
};
QVariant data(const QModelIndex &index, int role) const
{
switch (role) {
case FileSizeRole:
return QFileSystemModel::data(this->index(index.row(), 1, index.parent()),
Qt::DisplayRole);
default:
return QFileSystemModel::data(index, role);
}
}
QHash<int, QByteArray> roleNames() const
{
auto result = QFileSystemModel::roleNames();
result.insert(FileSizeRole, "fileSize");
return result;
}
};
This way, you can simply refer to the role by its name:
TreeView {
model: fsModel
anchors.fill: parent
TableViewColumn {
role: "display"
}
TableViewColumn {
role: "fileSize"
}
}
QFileSystemModel inherits from QAbstractItemModel, which has a method called roleNames(), that returns a QHash with the names of the default Roles (e.g. DysplayRole, DecorationRole, EditRole etc..) see:https://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames. To be accurate, QFileSystemModel defines its own roles on top of the QAbstracItemModel ones. see: https://doc.qt.io/qt-5/qfilesystemmodel.html#Roles-enum
So if you didn't define any custom role, then you can simply refer to the display role with it's default name (display) in your QML file . Like this:
TableView {
model: fileSystemModel
TableViewColumn {
role: "display"
}
}
That said, if you define custom roles, you have to override that roleNames() method, to give names to the new roles you defined. In that case, in order to keep consistency with the parent class, you should call first QAbstractItemModel::roleNames() method (in your case QFileSystemModel::roleNames()), and then set the new rolenames in the returned QHash. Here is an example for a login item where I defined host, username and password roles:
QHash<int, QByteArray> LoginModel::roleNames() const
{
QHash<int,QByteArray> names = QAbstractItemModel::roleNames();
names[HostRole] = "host";
names[UsernameRole] = "username";
names[PasswordRole] = "password";
return names;
}
You can also simply use model.display or just display to get DisplayRole from any model.

Dialog's ShowModal() won't send EVT_PAINT

wx version: 2.8.12.1
I'm trying to build a decored Dialog (Background, Buttons, Bitmap Border, etc) but on ShowModal() the Paint event is not issued.
It works with Show() super seeding wx.PopupTransientWindow, but not with Show() or ShowModal() on wx.Dialog
If you run the example, open the Dialog and click either of the two buttons, you'll get on the terminal:
send refresh
Clicked OK/CANCEL
"Paint Event" or "Draw Function" won't be printed.
"send refresh" indicates that a manual Refresh() has been issued; and that should issue an wx.EVT_PAINT.
If OnPaint is binded to wx.EVT_SHOW the functions will be called, the wx.BufferedPaintDC will be correctly set, but it won't change anything visible.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
def GetThemeTools(borderWidth, backgrounColour):
return {
'Pens': {
'DarkRectSolidBorder': wx.Pen( wx.Colour(67, 67, 67), borderWidth),
},
'Brushes': {
'Background': wx.Brush(backgrounColour),
'RectBoxFilling': wx.Brush( wx.Colour(119,120,119) ),
},
'ForegroundColor': wx.Colour(241,241,241),
'BackgroundColor': 'WHITE',
'Fonts': {
'Brief': wx.Font( pointSize=12,
family=wx.FONTFAMILY_SWISS,
style=wx.FONTSTYLE_NORMAL,
weight=wx.FONTWEIGHT_NORMAL,
encoding=wx.FONTENCODING_UTF8
),
},
}
class ConfirmDialog(wx.Dialog):
def __init__(self, parent, text="",
margin=10, borderRadio=10):
wx.Dialog.__init__(self, parent, style=wx.STAY_ON_TOP)
# Get data to show
self.parent = parent
self._margin = margin
self._borderRadio = borderRadio
self._text = text
self._font = None
self.initializeTools()
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SHOW, self._sendRefresh)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self._setWidgets()
self._layout()
wx.CallAfter(self.Refresh)
def _sendRefresh(self, e):
if self.IsShown():
print("send refresh")
self.Refresh()
def _setWidgets(self):
self.panel = wx.Panel(self)
self.message = wx.StaticText(self.panel, label=self.GetText())
self.message.SetForegroundColour(self.tools['ForegroundColor'])
self.message.SetBackgroundColour(self.tools['BackgroundColor'])
self.cancelBtn = wx.Button(self.panel, id=wx.ID_CANCEL, label="Cancel")
self.okBtn = wx.Button(self.panel, id=wx.ID_OK, label="Ok")
def _layout(self):
self.vSizer = wx.BoxSizer(wx.VERTICAL)
self.buttonsSizer = wx.BoxSizer(wx.HORIZONTAL)
self.buttonsSizer.Add(self.okBtn)
self.buttonsSizer.AddSpacer(20)
self.buttonsSizer.Add(self.cancelBtn)
self.vSizer.Add(self.message, 0, wx.CENTER|wx.BOTTOM, 5)
self.vSizer.Add(self.buttonsSizer, 0, wx.CENTER|wx.TOP, 5)
self.panel.SetSizer(self.vSizer)
self.vSizer.Fit(self.panel)
def SetMargin(self, margin):
self._margin = margin
self.Refresh()
def GetMargin(self):
return self._margin
def SetBorderRadio(self, borderRadio):
self._borderRadio = borderRadio
self.Refresh()
def GetBorderRadio(self):
return self._borderRadio
def SetText(self, text):
self._text = text
self.Refresh()
def GetText(self):
return self._text
def GetFont(self):
if not self._font:
self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
return self._font
def initializeTools(self):
self.borderWidth = 6
backColour = self.GetBackgroundColour()
self.tools = GetThemeTools(self.borderWidth, backColour)
self._font = self.tools['Fonts']['Brief']
def OnPaint(self, event):
print("Paint Event")
dc = wx.BufferedPaintDC(self)
self.Draw(dc)
event.Skip()
def Draw(self, dc):
print("Draw Function")
margin = self.GetMargin()
borderRadio = self.GetBorderRadio()
text = self.GetText()
self.Layout()
dc.SetBackground(self.tools['Brushes']['Background'])
dc.SetFont(self.GetFont())
dc.SetTextBackground(self.tools['BackgroundColor'])
dc.Clear()
(H, W) = self.GetSize()
# Draw Border
dc.SetPen(self.tools['Pens']['DarkRectSolidBorder'])
dc.SetBrush(self.tools['Brushes']['RectBoxFilling'])
dc.DrawRoundedRectangle( 0, 0, H, W, borderRadio)
def OnEraseBackground(self, event):
pass
class AppFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Custom Dialog Test")
panel = wx.Panel(self)
frameSizer = wx.BoxSizer(wx.VERTICAL)
panelSizer = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(panel, label="Show Dialog")
btn.Bind(wx.EVT_BUTTON, self.ShowDlg)
panelSizer.Add(btn, 0, wx.ALL, 20)
panel.SetSizer(panelSizer)
self.SetSizer(frameSizer)
panelSizer.Fit(panel)
self.Layout()
self.Show()
def ShowDlg(self, event):
dia = ConfirmDialog(self, "Custom Dialog\nTest\nThird Line")
if dia.ShowModal() == wx.ID_OK:
print("Clicked OK")
else:
print("Clicked CANCEL")
app = wx.App(False)
frame = AppFrame()
app.MainLoop()
You've bound your paint event handler to the dialog, but the dialog has a panel that completely covers the dialog. Since the dialog's surface is never exposed (because it is under the panel) then it never needs painted, so it never recieves a paint event from the system.
In most cases you do not need a panel as the top element in a dialog, so you may want to remove it and replace all the self.panel in that part of the code with just self.
Following the hint on Drawing To Panel Inside of Frame:
I got wrong the bind of the Paint Event
self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
self.panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
For some reason I still don't fully get (examples at self.Bind vs self.button.Bind) why the panel won't Skip() the Paint Event; leading to self never getting aware of the Paint Event.
And the parenthood of the canvas:
dc = wx.BufferedPaintDC(self.panel)
After playing a little bit subclassing from wx.PopupWindow (which resulted in a badly sized Window that I couldn't properly center in the frame) I realized that the DC was being rendered below the panel. It makes sence since self.panel is a child of self, so only self was being painted.
Not an Error but on the Paint Event, it seems the panel or dialog gets back to their default size. If the panel is fit, the dialog is resized, and the window is centered again in the Paint Event, then new Paint Events will be raised and a flicker will appear. Since this is an parameterized static dialog, the dimensions can be fixed with this at the end of self._layout():
self.panel.Fit()
minSize = self.panel.GetSize()
self.SetMinSize(minSize)
self.SetMaxSize(minSize)

Data only populates in last UITextField in UITableViewCell

I have a tableview that has names and text fields in the accessoryView position. With the following UITableViewDelegate method, I am calling onEdit and it calls a UIPickerView. The problem is, if I have more then 1 cell, it only populates the data in the last uitextfield no matter which textfield I click on. How do I allow data to go into the text field I am editing?
def tableView(tableView, cellForRowAtIndexPath: indexPath)
#reuseIdentifier ||= "CELL_IDENTIFIER"
cell = tableView.dequeueReusableCellWithIdentifier(#reuseIdentifier) || begin
UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:#reuseIdentifier)
end
picker = UIPickerView.alloc.initWithFrame([[0, self.view.frame.size.height / 2 + 50], [self.view.frame.size.width, self.view.frame.size.height]])
picker.delegate = self
picker.showsSelectionIndicator = true
#picker_field = UITextField.alloc.initWithFrame([[200,20],[150,40]])
#picker_field.placeholder = "Choose Location.."
#picker_field.delegate = self
#picker_field.inputView = picker
cell.textLabel.text = #list[indexPath.row]
cell.accessoryView = #picker_field
cell.selectedBackgroundView.backgroundColor = UIColor.redColor
#cell.selectionStyle = UITableViewCellSelectionStyleNone
cell
end
def pickerView(pickerView, numberOfRowsInComponent: component)
#choices_count
end
def pickerView(pickerView, titleForRow: row, forComponent: component)
#choices[row]
end
def pickerView(pickerView, didSelectRow: row, inComponent: component)
#picker_field.text = #choices[row]
#picker_field.resignFirstResponder
end
The solution came from putting more focus on the #picker_field becoming the firstResponder. Once I put an if statement stating the firstResponder it populates everytime.
def pickerView(pickerView, didSelectRow: row, inComponent: component)
if #picker_field = firstResponder
#picker_field.text = #choices[row]
#picker_field.resignFirstResponder
end
end

Returning flags via source model makes my items inactive (greyed out)

In my proxy model with base QSortFilterProxyModel in the flags virtual method:
Qt::ItemFlags File_List_Proxy::flags(const QModelIndex& index) const
{
if(index.isValid())
{
return QAbstractItemModel::flags(index) |
Qt::ItemIsUserCheckable |
Qt::ItemIsSelectable;
}
else
{
return Qt::NoItemFlags;
}
}
If the function looks like the above (IDENTICAL to model version...just copied and pasted) then items are displayed correctly. But if I change def of this method to use sourceModel():
Qt::ItemFlags File_List_Proxy::flags(const QModelIndex& index) const
{
return sourceModel()->flags(index);
}
...then items on my listView are inactive. Why?
What happens if you map the index to source model index ?
Qt::ItemFlags File_List_Proxy::flags(const QModelIndex& index) const
{
return sourceModel()->flags(this->mapToSource(index));
}
Because to my point of view the index is not related to the same model, so it's invalid