带无符号整数的 QSpinBox 用于十六进制输入
Posted
技术标签:
【中文标题】带无符号整数的 QSpinBox 用于十六进制输入【英文标题】:QSpinBox with Unsigned Int for Hex Input 【发布时间】:2014-12-22 06:29:32 【问题描述】:这里有很多关于 QSpinBox 使用 int 作为其数据类型的限制的问题。人们通常希望显示更大的数字。就我而言,我希望能够以十六进制显示一个无符号的 32 位整数。这意味着我希望我的范围是 [0x0, 0xFFFFFFFF]。一个普通的 QSpinBox 可以达到的最大值是 0x7FFFFFFF。在这里回答我自己的问题,我想出的解决方案是通过重新实现相关的显示和验证函数来简单地强制将 int 视为无符号 int。
【问题讨论】:
【参考方案1】:结果很简单,而且效果很好。在这里分享,以防其他人可以从中受益。它有32位模式和16位模式。
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);
;
【讨论】:
您的reinterpret_cast
s 违反了strict alasing rule。所以代码包含UB
他们可能只是static_cast<>
。【参考方案2】:
如果您不需要完整的 32 位,您可以非常简单地这样做:
#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();
;
在表单设计器(或其他)中,您只需设置:
prefix
: 0x
displayIntegerBase
:16
maximum
: 255(或其他)
如果您需要完整的 32 位,则必须使用转换技巧,或者可能只使用行编辑。
【讨论】:
【参考方案3】:我遇到了同样的问题,但使用 PyQt,所以我无法避免 Qt 在 C 引擎下进行的范围检查。
解决方法是使用 QDoulbeSpinbox 并将值转换为 textFromValue 中的 int。
这是我的代码(它还实现了一个右键菜单来改变显示基础):
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 "0b0:b".format(value)
【讨论】:
有趣的“旁注”,这不适用于PySide
,你会得到OverflowError: Python int too large to convert to C long
异常【参考方案4】:
我知道这是一个旧答案,但来自谷歌。这是我的 pyside 1.2.4 解决方案,它在某种程度上基于 Techniquab 的解决方案,但没有整数溢出问题:
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))
【讨论】:
我使用了您的 spinbox 类,但无法编辑框中的值(pyside 1.2.4;python 2.7.12)。即使我将只读设置为 False:myspinbox.setReadOnly(False)。一个普通的旋转框是可以直接编辑的。【参考方案5】:感谢@ZX2C4 的回答。我修改了 HexSpinBox 类:
-
您可以设置前缀。
您可以设置最大范围(以防
INT_MAX < maxRange < UINT_MAX
存在错误)。
您可以禁用填写字段0
。
自动计算字段宽度。
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;
您可以使用范围 [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");
【讨论】:
以上是关于带无符号整数的 QSpinBox 用于十六进制输入的主要内容,如果未能解决你的问题,请参考以下文章
QT5-控件-QSpinBox和QDoubleSpinBox(用于通过控件调整整数和小数)
QT软件开发之基础控件--2.4.7 spinBox整数计数器
2021-10-22:颠倒二进制位。颠倒给定的 32 位无符号整数的二进制位。提示:请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不