【问题标题】:QSpinBox with Unsigned Int for Hex Input带无符号整数的 QSpinBox 用于十六进制输入
【发布时间】:2014-12-22 06:29:32
【问题描述】:

这里有很多关于 QSpinBox 使用 int 作为其数据类型的限制的问题。人们通常希望显示更大的数字。就我而言,我希望能够以十六进制显示一个无符号的 32 位整数。这意味着我希望我的范围是 [0x0, 0xFFFFFFFF]。一个普通的 QSpinBox 可以达到的最大值是 0x7FFFFFFF。在这里回答我自己的问题,我想出的解决方案是通过重新实现相关的显示和验证函数来简单地强制将 int 视为无符号 int。

【问题讨论】:

    标签: c++ qt hex unsigned-integer qspinbox


    【解决方案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_casts 违反了strict alasing rule。所以代码包含UB
    • 他们可能只是static_cast&lt;&gt;
    【解决方案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 "0b{0: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 类:

      1. 您可以设置前缀。
      2. 您可以设置最大范围(以防INT_MAX &lt; maxRange &lt; 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");
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-04
        • 2019-10-31
        • 1970-01-01
        • 2021-03-07
        • 2013-05-28
        • 2011-10-07
        • 2011-02-15
        • 1970-01-01
        相关资源
        最近更新 更多