Russian Qt Forum
Апрель 29, 2024, 02:29 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало Форум WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  
  Просмотр сообщений
Страниц: [1]
1  Qt / Уроки и статьи / Вариант размещения виджетов в тексте QTextEdit. : Ноябрь 05, 2019, 21:16
При создании электронных тренажеров (тесты, различные интерактивные задания и т.д.) для школьников у меня возник вопрос, как можно реализовать более-менее универсальный просмотрщик заданий, в которых кроме текстовой части может быть графика и различные таблицы. Кроме того, сами задания могут совершенно различными, это могут быть задания на вставку пропущенных слов в тексте, задания на выбор ответов с выпадающими списками, таблицы, куда нужно вписать недостающую информацию и тд. На роль просмотрщика мне более всего подходил класс QTextEdit, но возник вопрос, как в текст можно внедрить виджеты (поля для ввода ответа, выпадающие списки, кнопки и тд.). В интернете по этому вопросу я ничего не нашел. Опишу, как я решил данную проблему. Возможно, кому-то данная информация пригодится.
Основная идея основана на использовании текстовых объектов. В местах текста, где должны размещаться виджеты, создаются изображения, которые помещаются в текст, а поверх них размещаются нужные нам виджеты.
Приведенный ниже пример показывает, как можно разместить QLineEdit в тексте.

1. Реализуем текстовый объект.

Заголовочный файл widgettextobject.h:

Код
C++ (Qt)
class WidgetTextObject : public QObject, public QTextObjectInterface
{
   Q_OBJECT
   Q_INTERFACES(QTextObjectInterface)
 
public:
   explicit WidgetTextObject(int objectType, QTextEdit *parent = nullptr);
   ~WidgetTextObject();
   void resize(int w, int h);
   QLineEdit *inputWidget();
   int objectType();
   QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &);
   void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format);
 
private:
   int _objectType;
      QTextEdit *_textEdit;
      QLineEdit *_inputWidget;

В конструктор мы передает переменную objectType (тип объекта – целое число, назначение переменной будет показано ниже) и указатель на объект (наш Text Edit, в котором будем размещать содержимое). Назначение переменной objectType будет показано ниже.
Функция resize(int w, int h) предназначена для изменения размеров виджета.
Функция QLineEdit *inputWidget() возвращает указатель на виджет, который будет размещен в тексте.
Функция int objectType() возвращает значение objectType.
Функция QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &) возвращает размеры изображения, на которое будем помещать виджет.
void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) используется для отрисовки изображения, поверх которого будет наложен виджет.

Файл реализации widgettextobject.cpp:

Код
C++ (Qt)
WidgetTextObject::WidgetTextObject(int objectType, QTextEdit *parent) : QObject(parent)
{
   _objectType = objectType;
   _textEdit = parent;
   _inputWidget = new QLineEdit(parent->viewport());
   _inputWidget->show();
}
 
WidgetTextObject::~WidgetTextObject()
{
   _inputWidget->deleteLater();
}
 
void WidgetTextObject::resize(int w, int h)
{
   _inputWidget->resize(w, h);
   _textEdit->viewport()->update();
}
 
QLineEdit *WidgetTextObject::inputWidget()
{
   return _inputWidget;
}
 
int WidgetTextObject::objectType()
{
   return _objectType;
}
 
QSizeF WidgetTextObject::intrinsicSize(QTextDocument */*doc*/, int /*posInDocument*/,
                                      const QTextFormat &/*format*/)
{
   QSize size(_inputWidget->width() + 6, _inputWidget->height());
   return QSizeF(size);
}
 
void WidgetTextObject::drawObject(QPainter *painter, const QRectF &rect,
                                 QTextDocument *doc, int posInDocument,
                                 const QTextFormat &format)
{
   QPixmap pix(int(rect.width()), int(rect.height()));
   pix.fill(QColor(255, 255, 255, 0));
 
   if(format.toCharFormat().verticalAlignment() == QTextCharFormat::AlignNormal)
   {
       painter->drawPixmap(int(rect.x()), int(rect.y()), pix);
       _inputWidget->move(int(rect.x()) + 3, int(rect.y()));
   }
   else
   {
       QTextCursor cursor(doc);
       cursor.setPosition(posInDocument);
 
       int cursorHeight = _textEdit->cursorRect(cursor).height();
       int y = int(rect.y() + _inputWidget->height() / 2 - cursorHeight / 2);
 
       painter->drawPixmap(int(rect.x()), y, pix);
       _inputWidget->move(int(rect.x()) + 3, y);
   }
}

При размещении виджета применяется два варианта вертикального выравнивания.

2. Создаем новый класс TextEditWithWidgets.

Заголовочный файл texteditwithwidgets.h
Код
C++ (Qt)
class TextEditWithWidgets : public QTextEdit
{
public:
   TextEditWithWidgets(QWidget *parent);
   void insertTextObject();
   void resizeWidget(int w, int h);
   void align(QTextCharFormat::VerticalAlignment alignment);
 
private:
   int _objectType = QTextFormat::UserObject;
   QVector <WidgetTextObject *> _objectsVector;
   int indexObject(int objectType);
 
private slots:
   void textChanged();
};
 
Файл реализации
#include "texteditwithwidgets.h"

Код
C++ (Qt)
TextEditWithWidgets::TextEditWithWidgets(QWidget *parent) :
   QTextEdit(parent)
{
   connect(this, &QTextEdit::textChanged, this, &TextEditWithWidgets::textChanged);
}
 
void TextEditWithWidgets::insertTextObject()
{
   _objectType++;
 
   WidgetTextObject *textObject = new WidgetTextObject(_objectType, this);
   _objectsVector << textObject;
 
   this->document()->documentLayout()->registerHandler(_objectType, textObject);
 
   QTextCharFormat charFormat;
   charFormat.setObjectType(_objectType);
 
   QTextCursor cursor = this->textCursor();
   cursor.insertText(QString(QChar::ObjectReplacementCharacter), charFormat);
   this->setTextCursor(cursor);
}
 
void TextEditWithWidgets::resizeWidget(int w, int h)
{
   QTextCursor cursor = this->textCursor();
   QTextCharFormat format = cursor.charFormat();
   if(format.objectType() >= QTextFormat::UserObject + 1)
   {
       int index = indexObject(format.objectType());
       if(index != -1)//---Объект найден
       {
           _objectsVector[index]->resize(w, h);
       }
   }
}
 
void TextEditWithWidgets::align(QTextCharFormat::VerticalAlignment alignment)
{
   QTextCursor cursor = this->textCursor();
   cursor.movePosition(QTextCursor::End);
   cursor.insertBlock();
   cursor.insertText("c");
 
   QTextBlock bl = this->document()->begin();
 
   while(bl.isValid())
   {
       QTextCursor cur(bl);
 
       cur.setPosition(bl.text().length(), QTextCursor::KeepAnchor);
       QTextCharFormat format;
       format.setVerticalAlignment(alignment);
       cur.mergeCharFormat(format);
       bl = bl.next();
   }
 
   cursor.select(QTextCursor::BlockUnderCursor);
   cursor.removeSelectedText();
}
 
int TextEditWithWidgets::indexObject(int objectType)
{
   int value = -1;
   for(int i = 0; i < _objectsVector.count(); i++)
   {
       if(_objectsVector[i]->objectType() == objectType)
       {
           value = i;
       }
   }
   return value;
}
 
void TextEditWithWidgets::textChanged()
{
   QVector <bool> tempVector;
   tempVector.resize(_objectsVector.size());
 
   QTextBlock block = this->document()->begin();
   while(block.isValid())
   {
       QTextBlock::iterator iterator;
       for(iterator = block.begin(); !(iterator.atEnd()); ++iterator)
       {
           QTextFragment currentFragment = iterator.fragment();
           if(currentFragment.isValid())
           {
               QTextCharFormat format = currentFragment.charFormat();
               if(format.objectType() >= QTextFormat::UserObject + 1)
               {
                   int index = indexObject(format.objectType());
                   if(index != -1)
                   {
                       tempVector[index] = true;
                   }
               }
           }
       }
       block = block.next();
   }
 
   for(int i = 0; i < tempVector.count(); i++)
   {
       if(!tempVector[i])
       {
           delete _objectsVector[i];
           _objectsVector[i] = nullptr;
           _objectsVector.remove(i);
           tempVector.remove(i);
       }
   }
}

Данный пример демонстрирует основную идею и конечно же нуждается в некоторой доработке (например, мне не очень нравится реализация слота textChanged()), возможно, кто-то предложит свои идеи или внесет поправки. Проект с комментариями прикладываю (тестировался в Qt 5.12.5).
2  Qt / ActiveX / QAxObject MS Word нумерованные списки. : Май 26, 2019, 18:44
Здравствуйте. Кто знает, как в Word с помощью QAxObject создавать нумерованные списки?
3  Qt / Общие вопросы / Re: Двойное подчеркивание текста в QTextEdit : Декабрь 04, 2018, 21:03
Частично нашел решение данного вопроса.

Файл .h

Код
C++ (Qt)
class QMyTextEdit : public QTextEdit
{
public:
   QMyTextEdit(QWidget *parent = nullptr);
 
   void drawDoubleLine(int startPos, int endPos);
 
private:
   QTextCursor cursor;
 
   QVector <int> startX;
   QVector <int> endX;
   QVector <int> y;
 
protected:
   void paintEvent(QPaintEvent * event);
};

Файл .cpp

Код
C++ (Qt)
QMyTextEdit::QMyTextEdit(QWidget *parent) :
   QTextEdit(parent)
{
   cursor = textCursor();
}
 
void QMyTextEdit::drawDoubleLine(int startPos, int endPos)
{
   cursor.setPosition(startPos, QTextCursor::MoveAnchor);
   QRect r = cursorRect(cursor);
   startX << r.x();
 
   cursor.setPosition(endPos, QTextCursor::KeepAnchor);
 
   y << r.bottom();
 
   QFontMetrics fm(cursor.charFormat().font());
 
   endX << r.x() + fm.width(cursor.selectedText());
 
   viewport()->repaint();
}
 
void QMyTextEdit::paintEvent(QPaintEvent *event)
{
   QPainter painter(viewport());
 
   QPen pen;
   pen.setColor(cursor.charFormat().foreground().color());
   pen.setWidth(1);
   painter.setPen(pen);
 
   int xLine1, xLine2, yLine;
   if(!startX.isEmpty())
   {
       for(int i = 0; i < startX.count(); i++)
       {
           xLine1 = startX[i];
           xLine2 = endX[i];
           yLine = y[i];
 
           painter.drawLine(xLine1, yLine, xLine2, yLine);
           painter.drawLine(xLine1, yLine + 2, xLine2, yLine + 2);
       }
   }
 
   QTextEdit::paintEvent(event);
}

Код самый простейший и написан на скорую руку, но может кому пригодится. Естественно, при сохранении текста в формате html в конечном тексте никакого двойного подчеркивания не будет, но думаю, можно реализовать и это
4  Qt / Общие вопросы / Двойное подчеркивание текста в QTextEdit : Ноябрь 19, 2018, 13:14
Здравствуйте. Скажите, возможно ли реализовать двойное подчеркивание текста в QTextEdit?
Страниц: [1]

Страница сгенерирована за 0.176 секунд. Запросов: 24.