2014-10-27 13 views
6

Ci sono una serie di domande scritte sulla limitazione di QSpinBox dell'uso di un int come suo tipo di dati. Spesso le persone vogliono mostrare numeri più grandi. Nel mio caso, voglio essere in grado di mostrare un numero intero a 32 bit senza segno in esadecimale. Ciò significa che mi piacerebbe che il mio intervallo fosse [0x0, 0xFFFFFFFF]. Il più grande QSpinBox normale può andare è 0x7FFFFFFF. Rispondendo alla mia domanda qui, la soluzione che ho trovato è quella di forzare semplicemente l'int a essere trattato come un int unsigned, reimplementando le relative funzioni di visualizzazione e validazione.QSpinBox con unsigned int per ingresso esadecimale

+0

Accetta la tua risposta. – nnb

risposta

6

Il risultato è piuttosto semplice e funziona bene. Condivisione qui nel caso in cui qualcun altro possa beneficiare di questo. Ha una modalità a 32 bit e una modalità a 16 bit.

Example of 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); 
    } 

}; 
+0

Il tuo 'reinterpret_cast's viola [strict alasing rule] (http://eel.is/c++draft/basic.lval#8). Quindi il codice contiene UB – yrHeTaTeJlb

1

mi si avvicinò con lo stesso problema, ma utilizzando PyQt quindi non ho potuto evitare la gamma controllando che Qt stava facendo in C sotto il cofano.

La soluzione alternativa era utilizzare un QDoulbeSpinbox e trasmettere il valore a un int in textFromValue.

Ecco il mio codice (implementa anche un menu del tasto destro per cambiare la base di visualizzazione):

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) 
+0

Interessante nota "laterale", questo non funziona su 'PySide' si otterrà un' OverflowError: Python int troppo grande per convertire in C lungo' Eccezione –

0

So che questa è una risposta vecchia, ma sono venuto qui da Google. Ecco la mia soluzione con PySide 1.2.4 basato un po 'fuori della soluzione di Techniquab ma non ha il problema integer overflow:

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)) 
1

Se non hanno bisogno di utilizzare tutti i 32 bit si può fare in modo molto semplice come questo:

#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(); 
    } 
}; 

Nella forma designer (o qualsiasi altra cosa) poi basta solo impostare:

  • prefix: 0x
  • displayIntegerBase: 16
  • maximum: 255 (o qualsiasi altra cosa)

Se è necessario utilizzare tutti i 32 bit si dovrà impiegare trucchi colata, o forse solo usare una linea di editing.