使用无符号整数的QSpinBox进行十六进制输入

10
这里有关于QSpinBox只能使用int作为其数据类型的各种问题。通常人们希望显示更大的数字。在我的情况下,我想要展示一个无符号32位整数的十六进制。这意味着我希望我的范围是[0x0, 0xFFFFFFFF]。一般的QSpinBox最大只能到0x7FFFFFFF。回答自己的问题,我想到的解决方法是通过重新实现相关的显示和验证函数,强制将int视为无符号int。
5个回答

9

这个结果相当简单,而且工作得很好。在这里分享给大家,以便其他人也可以受益。它有32位模式和16位模式。

HexSpinBox示例

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 违反了严格别名规则。所以代码包含未定义行为。 - yrHeTateJlb
它们无论如何都可以被static_cast<> - Timmmm

4
如果您不需要完整的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();
    }
};

在表单设计器(或类似的工具)中,您只需设置以下内容:
  • 前缀0x
  • 显示整数基数:16
  • 最大值:255(或其他数字)
如果需要使用完整的32位,您需要使用强制类型转换技巧,或者可能只需使用文本编辑框即可。

1

我遇到了同样的问题,但是我使用的是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 "0b{0:b}".format(value)

有趣的“副作用”是,这在PySide上不起作用,你会得到一个OverflowError: Python int too large to convert to C long异常。 - Tom Myddeltyn

1

我知道这是一个旧答案,但是我从谷歌来到了这里。这是我的解决方案,使用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),也不行。正常的SpinBox可以直接编辑。 - NZD

0

感谢 @ZX2C4 的回答。我对 HexSpinBox 类进行了一些修改:

  1. 您可以设置前缀。
  2. 您可以设置最大范围(如果 INT_MAX < maxRange < UINT_MAX,会出现错误)。
  3. 您可以禁用填充字段 0
  4. 字段宽度将自动计算。

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");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接