问题
There are a variety of questions written here about QSpinBox's limitation of using an int as its datatype. Often people want to display larger numbers. In my case, I want to be able to show an unsigned 32bit integer in hexadecimal. This means I'd like my range to be [0x0, 0xFFFFFFFF]. The largest a normal QSpinBox can go is 0x7FFFFFFF. Answering my own question here, the solution I came up with is to simply force the int to be treated like an unsigned int, by reimplementing the relevant display and validation functions.
回答1:
The result is pretty simple, and it works well. Sharing here in case anyone else can benefit from this. It has a 32bit mode and a 16bit mode.
class HexSpinBox : public QSpinBox
{
public:
HexSpinBox(bool only16Bits, QWidget *parent = 0) : QSpinBox(parent), m_only16Bits(only16Bits)
{
setPrefix("0x");
setDisplayIntegerBase(16);
if (only16Bits)
setRange(0, 0xFFFF);
else
setRange(INT_MIN, INT_MAX);
}
unsigned int hexValue() const
{
return u(value());
}
void setHexValue(unsigned int value)
{
setValue(i(value));
}
protected:
QString textFromValue(int value) const
{
return QString::number(u(value), 16).toUpper();
}
int valueFromText(const QString &text) const
{
return i(text.toUInt(0, 16));
}
QValidator::State validate(QString &input, int &pos) const
{
QString copy(input);
if (copy.startsWith("0x"))
copy.remove(0, 2);
pos -= copy.size() - copy.trimmed().size();
copy = copy.trimmed();
if (copy.isEmpty())
return QValidator::Intermediate;
input = QString("0x") + copy.toUpper();
bool okay;
unsigned int val = copy.toUInt(&okay, 16);
if (!okay || (m_only16Bits && val > 0xFFFF))
return QValidator::Invalid;
return QValidator::Acceptable;
}
private:
bool m_only16Bits;
inline unsigned int u(int i) const
{
return *reinterpret_cast<unsigned int *>(&i);
}
inline int i(unsigned int u) const
{
return *reinterpret_cast<int *>(&u);
}
};
回答2:
If you don't need full 32 bits you can do it very simply like this:
#pragma once
#include <QSpinBox>
class PaddedSpinBox : public QSpinBox
{
public:
PaddedSpinBox(QWidget *parent = 0) : QSpinBox(parent)
{
}
protected:
QString textFromValue(int value) const override
{
// Pad to the width of maximum().
int width = QString::number(maximum(), displayIntegerBase()).size();
return QString("%1").arg(value, width, displayIntegerBase(), QChar('0')).toUpper();
}
};
In the form designer (or whatever) then you just set:
prefix
:0x
displayIntegerBase
: 16maximum
: 255 (or whatever)
If you need full 32 bits you will have to employ casting tricks, or maybe just use a line edit.
回答3:
I came up with the same problem but using PyQt so I could not avoid the range checking that Qt was doing in C under the hood.
The workaround was to use a QDoulbeSpinbox and to cast the value to an int in textFromValue.
Here is my code (it also implements a right click menu to change the display base):
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future_builtins import *
import re
import sys
from PyQt4.QtCore import (QRegExp, Qt)
from PyQt4.QtGui import (QApplication, QRegExpValidator, QDoubleSpinBox)
from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT
from PyQt4 import QtCore, QtGui
# Regex adapted from Mark Pilgrim's "Dive Into Python" book
class QHexSpinBox(QDoubleSpinBox):
def __init__(self, parent=None):
super(QHexSpinBox, self).__init__(parent)
self.mode = 'dec'
self.setContextMenuPolicy(Qt.CustomContextMenu);
regex = QRegExp("[x0-9A-Fa-f]{1,8}")
regex.setCaseSensitivity(Qt.CaseInsensitive)
self.hexvalidator = QRegExpValidator(regex, self)
regex = QRegExp("[0-9]{1,10}")
regex.setCaseSensitivity(Qt.CaseInsensitive)
self.decvalidator = QRegExpValidator(regex, self)
regex = QRegExp("[b0-1]{1,64}")
regex.setCaseSensitivity(Qt.CaseInsensitive)
self.binvalidator = QRegExpValidator(regex, self)
self.setRange(1, 999999)
self.connect(self,SIGNAL("customContextMenuRequested(QPoint)"),
self,SLOT("contextMenuRequested(QPoint)"))
@pyqtSlot(QtCore.QPoint)
def contextMenuRequested(self,point):
menu = QtGui.QMenu()
hex = menu.addAction("Hex")
dec = menu.addAction("Dec")
bin = menu.addAction("Bin")
self.connect(hex,SIGNAL("triggered()"),
self,SLOT("hex()"))
self.connect(dec,SIGNAL("triggered()"),
self,SLOT("dec()"))
self.connect(bin,SIGNAL("triggered()"),
self,SLOT("bin()"))
menu.exec_(self.mapToGlobal(point))
@pyqtSlot()
def hex(self):
self.mode = 'hex'
self.setValue(self.value())
@pyqtSlot()
def dec(self):
self.mode = 'dec'
self.setValue(self.value())
@pyqtSlot()
def bin(self):
self.mode = 'bin'
self.setValue(self.value())
def validate(self, text, pos):
if self.mode == 'hex':
return self.hexvalidator.validate(text, pos)
if self.mode == 'dec':
return self.decvalidator.validate(text, pos)
if self.mode == 'bin':
return self.binvalidator.validate(text, pos)
def valueFromText(self, text):
if self.mode == 'hex':
return int(unicode(text), 16)
elif self.mode == 'dec':
return int(unicode(text))
elif self.mode == 'bin':
return int(unicode(text), 2)
def textFromValue(self, value):
value = int(value)
if self.mode == 'hex':
return hex(value)
elif self.mode == 'dec':
return str(value)
elif self.mode =='bin':
return "0b{0:b}".format(value)
回答4:
I know this is an old answer but came here from google. Here is my solution with pyside 1.2.4 based somewhat off of Techniquab's solution but doesn't have the integer overflow issue:
from PySide import QtCore, QtGui
from numpy import base_repr
from PySide.QtGui import QRegExpValidator
class QBaseSpinBox(QtGui.QAbstractSpinBox):
valueChanged = QtCore.Signal(int)
_value = 0
default_value = 0
base = 10
def __init__(self, parent=None):
self.setRange(None, None)
QtGui.QAbstractSpinBox.__init__(self, parent)
self.set_base(self.base)
self.lineEdit().setValidator(QRegExpValidator(self))
self.default_value = self.value()
self.lineEdit().textChanged.connect(self.textChanged)
self.lineEdit().setContextMenuPolicy(QtCore.Qt.CustomContextMenu);
self.lineEdit().customContextMenuRequested.connect(self.contextMenuRequested)
@QtCore.Slot()
def contextMenuRequested(self, point):
menu = self.lineEdit().createStandardContextMenu() #QtGui.QMenu()
actionDefault = menu.addAction("&Set Default Value of %s" % self.textFromValue(self.default_value),
shortcut=QtCore.Qt.CTRL | QtCore.Qt.Key_D) #QtGui.QKeySequence("Ctrl+D")))
menu.insertSeparator(actionDefault)
actionDefault.triggered.connect(self.menuActionDefault_triggered)
menu.exec_(self.mapToGlobal(point))
@QtCore.Slot()
def menuActionDefault_triggered(self):
self.setValue(self.default_value)
def value(self):
return self._value
def setValue(self, value):
if self.validate(value) == QtGui.QValidator.Invalid:
self.setValue(self._value)
return
changed = False
if self._value != value:
changed = True
self._value = value
self.lineEdit().setText(self.textFromValue(value))
if changed:
self.valueChanged.emit(self._value)
@QtCore.Slot()
def stepBy(self, value):
self.setValue(self._value + value)
QtGui.QAbstractSpinBox.stepBy(self, self._value)
def stepEnabled(self):
return QtGui.QAbstractSpinBox.StepDownEnabled | QtGui.QAbstractSpinBox.StepUpEnabled
@QtCore.Slot()
def textChanged(self, text):
try:
self.setValue(int(text, self.base))
except:
self.setValue(self._value)
def setRange(self, _min, _max):
self.minimum = _min if _min != None else 0
self.maximum = _max if _max != None else 0xFFFFFFFFFFFFFFFF
def validate(self, input):
if not input:
return QtGui.QValidator.Intermediate
try:
try:
value = int(input, self.base)
except TypeError:
value = input
if not (self.minimum <= input <= self.maximum):
raise Exception()
except Exception as ex:
return QtGui.QValidator.Invalid
return QtGui.QValidator.Acceptable
def valueFromText(self, text):
return int(text, self.base)
def textFromValue(self, value):
return base_repr(value, self.base).upper()
def set_default_value(self, value):
self.default_value = int(value)
#self.setValue(self.default_value)
self.set_base(self.base) # Redo the tooltip
def set_base(self, base):
self.base = base
min = self.textFromValue(self.minimum)
max = self.textFromValue(self.maximum)
default = self.textFromValue(self.default_value)
self.lineEdit().setToolTip("Base %d\nRange: %s-%s\nDefault Value: %s" % (self.base, min, max, default))
回答5:
Thanks @ZX2C4 for the answer. I some modified the class HexSpinBox:
- you may set prefix.
- you may set max range (in case
INT_MAX < maxRange < UINT_MAX
there are bugs). - you may disable fill fields
0
. - width of fields count auto.
hexspinbox.h
#ifndef HEXSPINBOX_H
#define HEXSPINBOX_H
#include <QSpinBox>
class HexSpinBox : public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = nullptr);
unsigned int hexValue() const { return u(value()); }
void setHexValue(unsigned int value) { setValue(i(value)); }
void setRange(unsigned int max);
bool fillField() const { return m_fillField; }
void setFillField(bool fillFieldWidth) { m_fillField = fillFieldWidth; }
protected:
QString textFromValue(int value) const;
int valueFromText(const QString &text) const;
QValidator::State validate(QString &input, int &pos) const;
private:
unsigned int m_maxRange = UINT_MAX;
bool m_fillField = true;
inline unsigned int u(int i) const { return *reinterpret_cast<unsigned int *>(&i); }
inline int i(unsigned int u) const { return *reinterpret_cast<int *>(&u); }
};
#endif // HEXSPINBOX_H
hexspinbox.cpp
#include "hexspinbox.h"
HexSpinBox::HexSpinBox(QWidget *parent) : QSpinBox(parent), m_maxRange(maximum())
{
setDisplayIntegerBase(16);
}
void HexSpinBox::setRange(unsigned int max)
{
m_maxRange = max;
if (m_maxRange <= INT_MAX) {
QSpinBox::setRange(0, int(m_maxRange));
} else {
QSpinBox::setRange(INT_MIN, INT_MAX);
}
}
QString HexSpinBox::textFromValue(int value) const
{
int fillField = 0;
if (m_fillField) {
uint m = m_maxRange;
while (m) {
m >>= 4;
++fillField;
}
}
return QString("%1").arg(u(value), fillField, 16, QLatin1Char('0')).toUpper();
}
int HexSpinBox::valueFromText(const QString &text) const
{
return i(text.toUInt(nullptr, 16));
}
QValidator::State HexSpinBox::validate(QString &input, int &pos) const
{
QString copy(input);
QString pref = prefix();
if (copy.startsWith(pref))
copy.remove(pref);
pos -= copy.size() - copy.trimmed().size();
copy = copy.trimmed();
if (copy.isEmpty())
return QValidator::Intermediate;
input = pref + copy.toUpper();
bool okay;
unsigned int val = copy.toUInt(&okay, 16);
if (!okay || val > m_maxRange)
return QValidator::Invalid;
return QValidator::Acceptable;
}
You can use the class for the range [0x0, 0xFFFFFFFF] :
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow) {
ui->setupUi(this);
ui->hexspinbox->setRange(UINT_MAX); // or 0xFF =)
ui->hexspinbox->setPrefix("0x");
}
来源:https://stackoverflow.com/questions/26581444/qspinbox-with-unsigned-int-for-hex-input