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

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Вариант размещения виджетов в тексте QTextEdit.  (Прочитано 1481 раз)
heremummortis
Новичок

Offline Offline

Сообщений: 4


Просмотр профиля
« : Ноябрь 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).
Записан
Albegx
Новичок

Offline Offline

Сообщений: 2


Всем привет!


Просмотр профиля WWW
« Ответ #1 : Март 15, 2020, 03:24 »

Отлично!!!!!
Записан

Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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