Программирование на языке C++ в среде Qt Creator

Разработка приложений с графическим интерфейсом

Показывать лекцию целиком

15.1 Окна. Класс QMainWindow

Как уже отмечалось ранее, виджеты, родительский виджет для которых не задан, становятся окнами. Обычно для окон приложения используют следующие классы:

  • QMainWindow — окно приложения, которое может содержать меню, панели, строку статуса;
  • QDialog — диалоговое окно;
  • QWidget — простое, обычно немодальное окно;
  • Окно обычно имеет обрамление и заголовок. Текст для заголовка окна устанавливают с помощью метода QWidget::setWindowTitle(). Конструктор класса QWidget принимает дополнительный параметр, для типа окна — Qt::WindowFlags. С помощью этого параметра можно управлять типом обрамления, типом окна (для оконной системы). Например, можно создать окно без обрамления (это полезно в некоторых случаях для оформления, например, для окна загрузки программы) или деактивировать кнопки для минимизации и максимизации окна.

    Окно диалога — это особый вид окна, который может использоваться для различных целей, но всегда предоставляет пользователю возможность взаимодействия с программой. Диалоги, как правило, не имеют кнопок для минимизации и максимизации окна. Окна диалога также часто бывают модальными. Модальность окна определяется его поведением. Модальные окна блокируют доступ к другим окнам, пока пользователь не завершит работу с окном (не закроет его). Задать модальность окна можно с помощью метода QWidget::setWindowModality(), если передать ему логическое значение true.

    QMainWindow — класс, реализующий функциональность главного окна приложения. Для этого он дополнительно имеет специальные средства работы:

  • Главное меню (Main menu);
  • Панели инструментов (Toolbars);
  • Панель статуса (Status bar);
  • Присоединяемые панели (Docks);
  • Несколько элементов пользовательского интерфейса могут выполнять одно и то же действие (например: меню, кнопка на панели инструментов и т.д.). Класс QAction используют для того, чтобы привязать заданное действие к нескольким элементам управления. Благодаря группировке действия и связанных с ним данных (названия, подсказки, пиктограммы и т.д.), а также его многократного использования (в главном меню, на панели инструментов и т.д.), можно избежать дублирования кода.

    Присоединяемые панели QDockWidget (dock widgets), монтируются в крае окна, и могут быть перенесены и перегруппированы пользователем, или даже разделены и размещены как отдельные окна. Обычно содержат группу элементов пользовательского интерфейса, объединенных общей целью и назначением или группу инструментов для работы с текущим открытым файлом.

    Панель статуса QStatusBar (Status bar) обычно используют для изображения текстовых сообщений о статусе или текущие действия программы, но она может содержать пиктограммы, а также другие виджеты (например, индикаторы прогресса, метки).

    Таким образом, воспользовавшись возможностями главного окна и создав несколько диалогов, можно получить программу, которая будет соответствовать стандартам современных пользовательских интерфейсов. До сих пор для создания интерфейса программы нам приходилось самостоятельно создавать и компоновать виджеты. В следующем параграфе мы используем для этой цели программу Qt Designer, которая позволяет создать интерфейс средствами визуального проектирования.

    15.2 Быстрая разработка с помощью Qt Designer

    Как мы отмечали ранее, есть два подхода, которые можно использовать при построении графического пользовательского интерфейса, используя виджеты Qt:

  • создать, настроить виджеты и разместить их на форме в соответствующих компоновках с помощью программного кода;
  • воспользоваться визуальным редактором форм Qt Designer, который создаст файл формы (он будет описывать ее внешний вид, размещение, размеры, настройки, компонование и т.д.). В дальнейшем из файла формы на этапе компиляции будет создан файл с кодом программы, будет программно создавать этот интерфейс и предоставлять программисту доступ к элементам на форме.
  • Файлы формы имеют расширение .ui. Qt Designer позволяет редактировать файлы форм, содержащих настройки вида виджетов. Qt Designer можно использовать как отдельную программу или воспользоваться интеграцией с оболочкой Qt Creator — редактором форм.

    Визуальный редактор форм позволяет воспользоваться фактически всеми стандартными элементами управления имеющимися в Qt, настроить значение для их свойств, стилизовать их внешний вид и скомпоновать их на форме. Также он содержит большое количество инструментов: поле для редактирования формы, редактор сигнально-слотовых соединений, редактор свойств объектов, средства для работы с компоновками, стилями и т. п.

    Для того, чтобы продемонстрировать работу редактора форм, создадим новый пример — простой редактор текста:

  • Для того, чтобы создать форму главного окна, мы воспользуемся настройками мастера новых проектов. Вызовите мастер создания файлов и проектов, и выберите тип проекта Qt Widgets Application (Приложение Qt Widgets). В окне мастера введите имя для нового проекта (например: "SimpleTextEditor"), выберите также инструментарий для проекта. В окне Class Information (Информация о классе) выберите QMainWindow в качестве базового класса главного окна. Также установите флажок Generate Form (Создать форму) — это укажет мастеру на необходимость создания .ui-файла для главного окна (см. рис. 15.1). (рис 15.1) Окно мастера проектов: создание класса главного окна и формы.
  • После создания проекта откройте дерево проекта, и в разделе Forms(Формы) выберите файл формы для главного окна (mainwindow.ui). Qt Creator сразу перейдет в режим редактирования пользовательского интерфейса Design (Дизайн). Интерфейс редактора форм состоит из нескольких панелей (см. рис. 15.2).
  • Выберите на панели виджетов "(рис 15.2) Интерфейс редактора форм: 1. Панель виждетов (Widget box) 2. Окно дерева объектов (Object inspector) 3. Редактор свойств (Property editor) 4. Панель переключения режимов работы редактора форм 5. Редактор действий (Action editor) 6. Редактор сигнально-слотовых соединений (Signals Slots editor) 7. Центральная часть окна, в которой размещена форма.
  • Для того чтобы разместить Plain Text Edit в компоновке внутри главного окна, нажмите правой кнопкой мыши на свободной от виджетов части формы и выберите в контекстном меню тип компоновки (подменю Layout). Для примера мы используем вертикальную компоновку. После выбора компоновки текстовое поле займет все свободное пространство формы.
  • Добавим главное меню программы. Поскольку для главного окна был избран класс QMainWindow, то панель главного меню (QMenuBar), панель статуса (QStatusBar) и панель инструментов (QToolBar) автоматически добавлены к проекту (убедитесь в этом просмотрев дерево объектов). Главное меню расположено в верхней части формы, и пока не содержит ни одного элемента. Нажмите два раза мышкой на надписи "Type here" (Пишите здесь) в главном меню, и введите "file". По окончании ввода нажмите Enter — на форме появится меню "File". Откройте меню "File" и введите так же еще пункты: "new", "open..." и "Save...". Добавьте разделитель в меню нажав "Add separator" (Добавить разделитель). После этого добавьте еще один пункт — Exit.
  • Так же добавьте меню "Edit" и "About", добавьте подпункты для этих меню (смотрите на рисунке ниже). Для того, чтобы к пунктам главного меню можно получить доступ с помощью комбинации клавиш (Alt+<подчеркнутая буква в названии пункта>), используют символ "quot;. Например, для того чтобы открыть меню File с помощью комбинации Alt+F, название пункта меню задают как "file (см. рис. 15.3,рис. 15.4,рис. 15.5). Все пункты меню и разделители можно упорядочить перетащив мышкой. Дополнительные подменю можно создать для каждого из пунктов меню, которые уже существуют, нажав на значок справа от названия пункта. (рис 15.3) Меню "File" после редактирования. (рис 15.4) Меню "Edit" после редактирования. (рис 15.5) Меню "About" после редактирования.
  • Для каждого из пунктов автоматически было создано соответствующее действие ((рис 15.6) Вид списка действий в редакторе действий (Action Editor) после редактирования меню.
  • Уже на этапе конструирования пользовательского интерфейса в редакторе форм мы можем создать сигнально-слотовые соединения между объектами на форме. Для этого перейдите на вкладку Signals Slots Editor на нижней панели. Для того, чтобы добавить новое соединение нажмите на значок с символом "+". Появится новая строка, в которой двойным щелчком мыши в каждом из столбцов можно вызвать меню со списком доступных вариантов. Выберите в качестве объекта (Sender) actionExit, который будет посылать сигнал (Signal) triggered(), а в качестве адресата (Receiver) выберите MainWindow и слот (Slot) close(). Также добавьте остальные сигнальнослотовые соединения для действий, как на рис. 15.7. (рис 15.7) Вид редактора сигналов и слотов (Signals Slots Editor) после редактирования
  • Такие действия как Undo (Отменить), Redo (Повторить), Cut (Вырезать), Copy (Копировать), теперь присоединены к соответствующим сигналам Plain Text Edit, которые будут сигнализировать о возможности их выполнение действий пользователя в редакторе. Запустим созданную форму в режиме просмотра — для этого выберите Tools->Form Editor->Preview... (Инструменты->Редактор форм->Предпросмотр) или нажмите комбинацию клавиш (Alt+Shift+R). Редактор форм загрузит и покажет на экране форму без предварительной компиляции всей программы. Если мы сразу же откроем меню, то увидим что пункты Undo, Redo, Cut, Copy уже активны, хотя изменения, отмены и выделение текста еще не происходило. Это связано с тем, что по умолчанию, созданные действия являются активными. Для того чтобы это исправить, откройте дерево объектов (Object Tree), выберите любое из действий для пунктов Undo, Redo, Cut, Copy и снимите флажок enabled для них в редакторе свойств (Property Editor). Редактор свойств позволяет настроить каждый из виджетов на форме, изменив значение для их свойств.
  • Скомпилируйте программу и запустите. Главное окно будет иметь вид, который мы задали на этапе проектирования формы. Некоторые пункты меню уже работают благодаря сигнально-слотовым соединениям, которые мы сделали.
  • В следующем параграфе мы продолжим работу над примером и продемонстрируем, как получить доступ к элементам формы из кода программы.

    15.3 Программирование формы созданной в Qt Designer

    Рассмотрим, как файлы форм интегрируются в проект. Как мы уже отмечали ранее, для того чтобы добавить файл формы в проект существует специальная переменная FORMS. Если мы откроем .рro-файл, то увидим:

    FORMS += mainwindow.ui

    Файлы форм, добавленные в проект, считываются и превращаются в эквивалентный С++ код с помощью специальной программы uic (это происходит во время предварительной обработки проекта с помощью qmake). Код, сгенерированный uic, создает виджеты и компоновки, содержит настройки свойств, сигнально-слотовые соединения и стили, необходимые для получения визуального отображения содержимого .ui-файла. Если мы откроем папку с проектом (или папку для shadow build), то увидим среди исходных текстов и сгенерированных файлов файл ui_mainwindow.h, содержащий сгенерированный для формы код. Конечно, этот файл должен быть добавлен где-то в коде программы с помощью директивы #include для того, чтобы можно было воспользоваться сгенерированным кодом. Но нет необходимости делать это самостоятельно — гораздо легче воспользоваться мастером создания файлов и проектов.

    При создании проекта для нашего примера мы установили флажок "Generate form" для того, чтобы автоматически сгенерировать форму для главного окна и добавить необходимый код для доступа к элементам на форме в программе. Среди кода, мастер автоматически добавил к объявлению и реализации класса MainWindow:

  • предварительное объявление класса формы: /
    /Предварительное объявление класса формы созданной из .ui-файла
    namespace Ui {
    	class MainWindow;
    }
  • указатель на объект формы, позволяющий получить доступ к элементам на форме:
    class MainWindow : public QMainWindow
    {
    .....
    private :
    	//Указатель на объект формы созданной из .ui-файла
    	Ui::MainWindow * ui;
    };
  • Конструктор главного окна создает объект класса формы и инициализирует переменную ui, а также вызывает метод ui->setupUi(this), который создает все элементы которые есть на форме и устанавливает текущий виджет как родительский для них; /
    /Файл сгенерированный uic при обработке файла формы
    #include " ui_mainwindow.h "
    MainWindow::MainWindow ( QWidget  * parent ) :
    	QMainWindow ( parent ),
    	ui(new Ui::MainWindow ) //Создаем объект формы, созданной в QtDesigner
    {
    ui->setupui( this ); //Применяем дизайн, который мы создали в QtDesigner
    	//к текущему окну
    .....
  • Деструктор: удаляет объект на который ссылается указатель ui
    MainWindow::˜ MainWindow ( )
    {
    	//Удалить форму из памяти
    	delete ui;
    }
  • Таким образом, через указатель ui мы можем получить доступ к созданной форме и элементам на ней. Например, мы можем получить доступ к действиям добавленным к главному меню нашего редактора и задать горячие клавиши для них:

    //Задаём комбинации клавиш для действий
    ui->action_Undo->set Shortcut ( QKeySequence::Undo );
    ui->action_Redo->set Shortcut ( QKeySequence::Redo );
    ui->action_Cut->set Shortcut ( QKeySequence::Cut );
    ui->action_Copy->set Shortcut ( QKeySequence::Copy );
    ui->action_Past e->set Shortcut ( QKeySequence::Paste );
    ui->action_Select_all ->set Shortcut ( QKeySequence::selectAll );
    ui->action_New->set Shortcut ( QKeySequence::New);
    ui->action_Open->set Shortcut ( QKeySequence::Open );
    ui->action_Save->set Shortcut ( QKeySequence::Save );
    ui->action_Exit->set Shortcut ( QKeySequence::Quit );

    Обратите внимание на то, как мы используем класс Qt QKeySequence для того, чтобы задать горячие клавиши для действий. Помимо возможности задать комбинацию клавиш текстовой строкой (например QKeySequence (Ctrl + X)) или с помощью специально определенных констант для клавиш (например QKeySequence (Qt :: CTRL + Qt :: Key_X)), можно воспользоваться набором стандартных клавиатурных сокращений, которые будут соответствовать стандартам системы. Так для повторения отмененного действия в ОС Linux пользователь сможет воспользоваться комбинацией клавиш Ctrl+Shift+Z, а в ОС Windows привычной комбинацией Ctrl+Y.

    Нашему текстовому редактору еще не хватает функциональности: нам необходимо запрограммировать создание, открытие и сохранение текстового файла, а также пункты меню Help.

    Для начала добавим объявления слота для создания нового файла и добавим поле для сохранения пути текущего открытого файла:

    private slots :
    	void slotNew ( );
    	
    private :
    	QString mFileName;

    Мы используем частный метод updateTitle() для того, чтобы обновить заголовок окна и вывести название программы и путь к текущему открытому файлу:

    private :
    	void updateTitle ( );

    Реализация метода обновления заголовка:

    //Метод обновления заголовка окна
    void MainWindow::updateTitle ( )
    {
    //Подставляем в название заголовка имя текущего открытого файла.
    //Комбинацией символов " [ * ] " обозначаем место, где будет выводиться знак "*" в случае,
    //когда содержимое окна модифицировано.
    QString lTitle=QString ( " TextEditor - % 1 [ * ] " ).arg ( mFileName.FileName ( ) );
    //устанавливаем заголовок окна
    setWindowTitle ( lTitle );
    }

    Метод setWindowTitle() позволяет не только задать текст заголовка для окна, но и отметить несохраненные изменения в текущем открытом документе. Для этого, после заголовка мы добавили шаблон [*]. Теперь, если сообщить главному окну о редактировании содержимого посредством вызова слота QWidget::setWindowModified(bool) со значением true, то после текста заголовка появится символ "*", означающий, что содержание главного окна изменено и его следует сохранить. Слот setWindowModified(bool) мы соединили с сигналом modificationChanged(bool) текстового поля QPlanTextEdit с помощью редактора сигнально-слотовых соединений в редакторе форм (см. предыдущий пункт).

    Добавим реализацию слота MainWindow::slotNew():

    //Слот для создания нового документа
    void MainWindow::slotNew ( )
    {
    	mFileName = " Untitled File "; //Задать имя для нового файла по умолчанию
    	ui->plainTextEdit ->clear ( ); //Очистить текстовое поле
    	setWindowModified ( false ); //Установить — содержание не модифицировано
    	updateTitle ( ); //Обновить заголовок окна
    }

    Теперь присоединим объект QAction для создания нового документа к слоту:

    connect(ui->action_New, SIGNAL(triggered()),this, SLOT(slotNew()), Qt::UniqueConnection);

    Сигнал выпускается при выполнении действия (вызова пункта меню, нажатие кнопки на панели инструментов, для которой была добавлено действие и т.д.). В конце конструктора вызовем слот slotNew():

    MainWindow::MainWindow ( QWidget * parent ) :
    	QMainWindow ( parent ),
    ui(new Ui::MainWindow )
    //Создаем объект, который содержит дизайн созданный в редакторе форм
    {
    ..... .
    //В конце вызываем слот для нового документа. Таким образом, пользователь сможет сразу
    //же приступить к работе
    	slotNew ( );
    }

    Остальные пункты меню мы запрограммируем в следующем параграфе, в котором мы рассмотрим работу со стандартными системными диалогами в Qt.

    15.4 Стандартные диалоги

    Диалог выбора файла для открытия и сохранения, диалог выбора шрифта, окна сообщений об ошибках являются примерами диалоговых окон, с которыми часто приходится сталкиваться при работе с программами. Такие диалоги обычно имеют стандартные для всех программ в системе вид и функциональность. Qt позволяет воспользоваться готовыми диалогами для этих целей, которые легко вызвать в программе. Классы для работы с диалогами, которые часто используют в программе, приведены в табл. 15.1.

    Некоторые классы готовых диалогов Qt
    Класс Описание особенностей
    QInputDialog Используют для удобства в случае когда необходимо ввести числовое значение или строку текста. Имеет несколько сигналов, которые сигнализируют об изменении значения в поле ввода. Класс имеет статические методы для вызова диалога ввода числа (getDouble(), getInt()), ввода (getText()) выбора элемента из списка (getItem()).
    QColorDialog Стандартный диалог выбoра цвета. Класс имеет статический метод getColor() для удобного вызова диалога.
    QFontDialog Стандартный диалог выбoра шрифта. Класс имеет статический метод getFont() для удобного вызова диалога.
    QFileDialog Стандартный диалог выбора файла. Имеет большое количество настроек, возможность фильтрации файлов по расширению. Класс имеет статические методы (getExistingDirectory(), getOpenFileName(), getOpenFileNames(), getSaveFileName()) для вызова диалога
    QMessageBox Диалог сообщение. Используют для вывода информации, сообщений об ошибках и вопросов. Класс имеет статические методы для удобного вызова в программе информационных окон (about(), aboutQt()), сообщений об ошибках (critical(), warning()), вопросов (question()) и сообщений (information()).

    В нашем примере мы будем использовать класс QFileDialog для выбора файла при открытии и сохранении, а также класс QMessageBox. Добавим описание слотов для открытия и сохранения файла:

    private slots :
    void slotOpen ( );
    void slotSave ( );

    Подключим необходимые заголовочные файлы в mainwindow.cpp:

    #include <QFileDialog>
    #include <QMessageBox>
    #include <QDir>

    Добавим реализацию для слотов slotOpen() и slotSave():

    //Слот для открытия файла в редакторе
    void MainWindow::slotOpen ( )
    {
    	//Вызвать системный диалог открытия файла в домашней папке пользователя
    	QString lFileName=QFileDialog::getOpenFileName ( this, " open file...", QDir : :
    	homePath ( ), " Text files ( * .txt );; All files ( *.* ) " );
    	//указываем фильтры для просмотра файлов
    	if ( lFileName.isEmpty ( ) ) //Если пользователь не выбрал ни одного файла
    	{
    		return; //выйти из метода
    	}
    	//Спросить пользователя о сохранении документа
    	if ( ! askForFileSaveAndclose ( ) )
    	{
    	//Если пользователь нажал "Отмена" игнорировать вызов — продолжать работу
    		return;
    	}
    	QFile lFile ( lFileName ); //Устанавливаем имя открытого файла
    	//Если текстовый файл открыт только для чтения...
    	if ( lFile.open ( QIODevice::ReadOnly | QIODevice::Text ) )
    	{
    		mFileName = lFileName; //задать имя файла
    		//читаем все содержимое и устанавливаем текст для редактора
    		ui->plainTextEdit ->setPlainText( lFile.readAll ( ) );
    		lFile.close ( ); //закрываем открытый файл
    		//устанавливаем состояние окна — содержимое не модифицировано
    		setWindowModified ( false );
    		//и обновляем заголовок окна для демонстрации названия текущего открытого файла
    		updateTitle ( );
    	}
    	else
    	{
    		//Если при открытии файла возникла ошибка выводим диалоговое окно с сообщением,
    		//содержащим имя файла, одну кнопку "Ок" и заголовок "Error"
    		QMessageBox::warning ( this, " Error ", QString ( " Could not open File %1 for
    		reading " ).arg ( lFile.FileName ( ) ), QMessageBox::Ok);
    	}
    }
    //Слот для сохранения изменений в текущем файле
    void MainWindow::slotsave ( )
    {
    	//Если содержимое не модифицировано...
    	if ( ! isWindowModified ( ) ) //Если содержимое не модифицировано
    	{
    	return; //Выйти из метода — продолжить работу
    	}
    	//Вызвать системный диалог сохранения файла в домашней папке пользователя
    	QString lFileName=QFileDialog::getSaveFileName ( this, t r ( " Save File..." ),
    		QDir::homePath ( ), tr ( " Text files ( *.txt );; All files ( *.* ) " ) );
    	//Если пользователь не выбрал имя файла для сохранения...
    	if ( lFileName.isEmpty ( ) )
    	{
    		return; // ... выйти из метода
    	}
    	QFile lFile ( lFileName ); //Устанавливаем имя открытого файла
    	//Если текстовый файл открыт для записи
    	if ( lFile.open ( QIODevice::WriteOnly | QIODevice::Text ) )
    	{
    		mFileName = lFileName; //Задать имя файла
    		//Создаем временный QByteArray для записи данных
    		QByteArray lData;
    		//Читаем текст из редактора и добавляем QByteArray, записываем в файл
    		//и закрываем файл после записи
    		lData.append ( ui->plainTextEdit ->toPlainText( ) );
    		lFile.write ( lData );
    		lFile.close ( );
    		//Устанавливаем состояние окна — содержимое не модифицировано
    		setWindowModified ( false );
    	}
    	else
    	{
    		//Если при открытии файла возникла ошибка выводим диалоговое окно с сообщением,
    		//содержащим имя файла, одну кнопку "Ок" и заголовок "Error"
    		QMessageBox::warning ( this, " Error ", QString ( " Could not open File %1 for
    		writing " ).arg ( lFile.FileName ( ) ), QMessageBox::Ok);
    	}
    }

    Здесь в начале каждого из слотов мы вызываем диалог для выбора файла с помощью статических методов класса QFileDialog. Диалог для открытия файла мы вызываем с помощью метода QFileDialog::getOpenFileName(), а для сохранения — с помощью QFileDialog ::getSaveFileName(). Для каждого из случаев диалог будет иметь соответствующий вид. Для того, чтобы добавить фильтр для текстовых файлов мы передаем строку с описанием фильтра — "Text files (*.txt );;AllFiles (*.*)". В описании — названия для фильтров и шаблоны для фильтрации (в скобках). Можно задавать несколько шаблонов через пробел при необходимости. Фильтры в списке разделяем с помощью двойной точки с запятой.

    После выбора пользователем файла, статический метод вернет полный путь к нему. В случае, когда пользователь закрыл диалог или нажал "Отмена", метод вернет пустую строку. Для выбранного файла мы выполняем чтение содержимого и сохранения. Если при открытии возникла ошибка, мы выводим ее с помощью статического метода QMessageBox::warning().

    При открытии файла мы использовали собственный метод askForFileSaveAndClose (), который должен проверить текущий открытый файл на изменения и предложить пользователю сохранить их перед открытием другого файла. Добавим описание этого метода к описанию класса MainWindow:

    private :
    	bool askForFileSaveAndclose ( );

    и его реализацию в файл mainwindow.cpp:

    //Метод для проверки текущего файла на изменения и вывода диалога для пользователя,
    //с предложением сохранить изменения. Метод возвращает логическое значение,
    //содержащее false в случае, когда пользователь нажал в диалоге кнопку "Cancel"
    bool MainWindow::askForFileSaveAndclose ( )
    {
    	if ( isWindowMod ified ( ) ) //Если содержимое окна модифицировано
    	{
    	//вызываем диалог с вопросом, нужно ли сохранять изменения: подставляем в текст диалога
    	//название текущего открытого файла, задаем кнопки: "Да", "Нет" и "Отмена".
    	//Результат работы диалога (нажатой кнопки) записываем в переменную
    		int lResult = QMessageBox::question ( this, t r ( " Save changes " ),
    		QString ( tr ( " File %1 is modified.Do you want to save your changes ? " ) ).arg
    			( mFile.FileName ( ) ), QMessageBox::Yes, QMessageBox::No, QMessageBox : :
    			Cancel );
    		if ( QMessageBox::Yes == lResult ) //Если нажали кнопку "Да"
    		{
    			slotSave ( ); //сохранить изменения
    		}
    		else
    		{
    			if ( QMessageBox::Cancel == lResult ) //Если нажали кнопку "Отменить"
    			{
    				return false;
    			}
    		}
    	}
    	return true;
    }

    В этом фрагменте программы мы использовали статический метод QMessageBox :: question, и задали заголовок, текст и кнопки на диалоге с помощью специальных констант (QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel). Он, как и почти все статические методы QMessageBox, возвращает значение нажатой кнопки. Это значение мы сравниваем со значениями констант для кнопок, чтобы определить, какие дальнейшие действия выбрал пользователь. Используем метод askForFileSaveAndClose() также и в слоте для нового текстового файла:

    //Спросить пользователя о сохранении документа
    if ( ! askForFileSaveAndclose ( ) )
    {
    	//Если пользоватеель нажал "Отменить" игнорировать вызов — продолжать работу
    	return;
    }

    Осталось реализовать вывод информации о программе. Для этого сначала добавим к .pro-файлу информацию о версии. Например, добавим переменные с большим и меньшим номерами версии, а потом передадим их в переменную DEFINES таким образом, чтобы они были объявлены в программе. Это может быть удобно для дальнейшего изменения версии при разработке программы:

    MAJOR_VERSION = 1
    MINOR_VERSION = 0
    DEFINES += \
    	MAJOR_VERSION=$$MAJOR_VERSION \
    	MINOR_VERSION=$$MINOR_VERSION

    После изменений в файле проекта не забудьте вызвать qmake еще раз, чтобы программа обработала все изменения в файле проекта (выберите в главном меню Build->Run qmake) Теперь в файле main.cpp зададим версию, а также название для QApplication:

    int main ( int arg c, char * argv [ ] )
    {
    	QApplication a ( arg c, argv );
    	a.setApplicationName ( " TextEditor " );
    	a.setApplicationVersion ( QString ( " % 1.% 2 " )
    		. arg (MAJOR_VERSION)
    		. arg (MINOR_VERSION) );
    	MainWindow w;
    	w.show ( );
    	return a.exec ( );
    }

    Теперь используем объект QApplication для получения версии и названия программы в слоте отображения информации. Добавим объявления слота, а также следующую его реализацию:

    //Слот для отображения информации о программе
    void MainWindow::slotAboutProgram ( )
    {
    	//Выводим диалоговое информационное окно с сообщением, куда подставляем версию и название
    	//программы возвращаемых QApplication. Указываем — окно содержит заголовок "About".
    	QMessageBox::about ( this, t r ( " About " ),
    	QString ( " %1 v.%2 " ).arg ( qApp->
    	ApplicationName ( ) ).arg ( qApp->Application Version ( ) ) );
    }

    В конце подсоединяем сигналы от пунктов главного меню к созданным слотам:

    //Присоединяем действия к созданным слотам
    connect ( ui->action_New, SIGNAL( triggered ( ) ), this, SLOT( slotNew ( ) ), Qt : :
    	UniqueConnection );
    connect ( ui->action_Open, SIGNAL( triggered ( ) ), this, SLOT( slotOpen ( ) ), Qt : :
    	UniqueConnection );
    connect ( ui->act ion_Save, SIGNAL( triggered ( ) ), this, SLOT( slotsave ( ) ), Qt : :
    	UniqueConnection );
    connect ( ui->actionAbout_Qt, SIGNAL( triggered ( ) ), qApp, SLOT( aboutQt ( ) ), Qt : :
    	UniqueConnection );
    connect ( ui->actionAbout_program, SIGNAL( triggered ( ) ), this, SLOT(
    slotAboutProgram ( ) ), Qt::UniqueConnection );

    15.5 Ресурсы программы

    Часто бывает необходимо добавить в программу дополнительные файлы такие как изображения и значки, используемые при оформления интерфейса, звуковые файлы для уведомлений пользователя, сценарии, выполняемые программой и т.д. В таких случаях можно воспользоваться преимуществами, которые предоставляет ресурсная система Qt.

    Добавляя файл как ресурс в программу, мы указываем, что хотим включить данные, содержащиеся в этом файле в исполняемый файл. Таким образом скомпилированная программа будет содержать все ресурсные файлы внутри. Средства Qt позволяют обращаться к этим данным, и считывать файлы в ресурсах так же, как и обычные файлы в файловой системе.

    Для демонстрации работы с ресурсами в Qt добавим изображения для пунктов меню и кнопки на панель инструментов текстового редактора.

  • В папке проекта создадим подкаталог resources. Внутри добавим еще одну вложенную папку с названием images и добавим туда значки для меню.
  • Вызовем мастер создания новых файлов и проектов. Выберем раздел Qt и создадим Qt Resource File (Файл ресурсов Qt). В следующем окне мастера, введем для файла имя в поле Name (Имя): resources.qrc. В поле Path (Путь) нажмем на кнопку и в диалоге выберем созданную папку resources. Нажмем Next (Далее) и в следующем окне Finish (Завершить).
  • Выберем созданный файл ресурсов в дереве проекта (раздел Resources) и откроем его. В главном окне Qt Creator появится редактор ресурсов, который состоит из браузера ресурсов (сверху) и панели редактирования (снизу). На нижней панели выберите Add-> Add Prefix (Добавить->Добавить префикс). Сразу же в редакторе появится раздел для ресурсов и станет доступным для редактирования его название (поле Prefix). Установите название для раздела: actions.
  • Выберите раздел actions в редакторе ресурсов и нажмите внизу на панели Add-> Add Files (Добавить->Добавить файлы). В диалоге выбора файлов откройте папку resources/images внутри папки с проектом, выберите все файлы в папке и нажмите Open. Пиктограммы сразу будут добавлены в раздел и показаны в редакторе.
  • Для каждой из пиктограмм можно выбрать псевдоним — второе имя по которому к пиктограмме можно получить доступ. Это делают для того, чтобы при изменении файла не нужно было менять путь к изображению в коде программы. Для этого достаточно добавить другое изображение и установить такой же псевдоним как для старого, а старому изображению, в свою очередь, удалить псевдоним. Установите псевдоним для каждого из изображений согласно действию для которого они предназначены (смотрите на рис. 15.8).
  • Изображение для действия можно добавить в коде программы вызвав для действия метод (рис 15.8) Редактирование файла ресурсов Обратите внимание на то, как мы указываем путь к файлу с изображением в ресурсах программы. Для того чтобы получить доступ к ресурсам мы начинаем путь с символов : /. Далее мы указываем раздел, куда мы добавили пиктограммы — actions, и затем указываем псевдоним для самого изображения в ресурсной системе — new. Для того, чтобы установить изображение для действия, мы создаем временный объект QIcon, которому передаем путь к изображению. Для того, чтобы добавить изображение для действий, мы воспользуемся другим способом и добавим изображение в редакторе действий. Для этого перейдите к редактору действий, два раза нажмите на действие и в диалоге редактирования нажмите на кнопку "..." в поле Icon (Значок). Выберите нужное изображение из ресурсов. Так же измените и другие действия.
  • Добавим действий теперь также и в панель инструментов. Для этого перетащите действие из редактора действий на панель инструментов, которая расположена прямо под главным меню на форме. Для того, чтобы добавить разделитель нажмите правой кнопкой на панели и выберите (рис 15.9) Редактор с панелью инструментов
  • 15.6 Создание собственных диалогов

    Для нашего примера мы создадим диалог настроек, с помощью которого мы будем изменять вид главного окна.

  • Вызовем мастер новых файлов и проектов. Выберем раздел Files and Classes->Qt (Файлы и классы -> Qt) и Qt Designer Form Class ( Класс формы Qt Designer) для создания класса окна вместе с файлом формы. Выберем в окне Choose a Form Template (Выбор шаблона формы) шаблон для нового окна — Dialog with Buttons Bottom (Диалог с кнопками внизу) и нажмите Next. Этим мы укажем мастеру создать окно, унаследовав класс от QDialog и добавить внизу формы дополнительно две кнопки Ok и Cancel (см. рис. 15.10).
  • QDialog может возвращать значение после завершения, которое можно получить с помощью метода int result(). Перед выходом из диалога должен вызываться один из двух методов: QDialog::accept() или QDialog::reject(). Они изменяют значение QDialog::result() на QDialog::Accepted и QDialog::Rejected соответственно и закрывают диалог. Обратите внимание, что после создания формы для диалога, кнопки внутри QDialogButtonBox были автоматически подключены к слотам диалога accept() и reject() (убедитесь в этом открыв редактор сигнальнослотовых соединений в редакторе форм для диалога). Также QDialog можно вызвать с помощью метода exec(), который также возвращает результат его выполнения. В этом случае диалог вызывается как модальный.
  • В следующем окне (см. рис. 15.11) напишем имя класса для окна: SettingsDialog (поле Class name). Нажмем кнопку Next и в следующем окне нажмем Finish. Откроем форму в редакторе. На форме мастер автоматически добавил Diаlog Button Box с двумя кнопками "Ok" и "Cancel". (рис 15.10) Создание диалога настроек (шаг 1).
  • Наш диалог будет содержать одну группу настроек. Это будут настройки вида окна, а именно настройки видимости панели инструментов и строки статуса. Для этого перетащим на форму элемент Group Box. Для того чтобы отредактировать заголовок два раза нажмите мышкой на тексте надписи или выберите свойство title в редакторе свойств. Измените заголовок на View Settings.
  • Перетащите два флажка в Group Box и назовите их Show Toolbar и Show Status Bar (см. рис. 15.12). Выберите свойство objectName для каждого из них и установите в showToolbarCheckBox и showStatusBarCheckBox соответственно. Нажмите правой кнопкой мыши внутри элемента Group Box и выберите Layout->LayoutHorizontally (Компоновка->Скомпоновать по горизонтали). Флажки расположатся в горизонтальной компоновке внутри элемента Group Box.
  • Нажмите на свободном от элементов пространстве формы и выберите в контекстном меню Layout->LayoutVertically (Компоновка >Скомпоновать по вертикали). Элементы Group Box и Button Box расположатся в вертикальной компоновке на форме. (рис 15.11) Создание диалога настроек (шаг 2).
  • Измените размер формы. Для этого выберите SettingsDialog в дереве объектов и подведите указатель мышки к краю формы или выберите свойство geometry и измените. Сделайте размер формы меньше. В редакторе свойств выберите флажок modal и установите его. Измените свойство windowTitle на "Settings".
  • (рис 15.12) Пример: Диалог настроек для проекта SimpleTextEditor.

    Объявление класса диалога настроек (settingsdialog.h):

    class settingsDialog : public QDialog
    {
    	Q_OBJECT
    public :
    	explicit settingsDialog ( QWidget * parent = 0 );
    	~settingsDialog ( );
    	bool isShowToolBar ( ) const;
    	void setShowToolBar ( bool pShow );
    	bool isShowStatusBar ( ) const;
    	void setShowStatusBar ( bool pShow );
    private :
    	Ui::settingsDialog * ui;
    };

    Также реализуем методы для установки и получения значения флажков (settingsdialog.h):

    bool settingsDialog::isShowToolBar ( ) const
    {
    	return ui->showToolbarCheckBox->isChecked ( );
    }
    void settingsDialog::setShowToolBar ( bool pShow )
    {
    	ui->showToolbarCheckBox->setChecked ( pShow );
    }
    bool settingsDialog::isShowStatusBar ( ) const
    {
    	return ui->showStatusBarCheckBox->isChecked ( );
    }
    void settingsDialog::setShowStatusBar ( bool pShow )
    {
    	ui->showStatusBarCheckBox->setChecked ( pShow );
    }
    

    Добавим вызов диалога из программы. Для этого в файле mainwindow.h сделаем предварительное объявление:

    class SettingsDialog;

    и добавим объявление слота, который будет показывать диалог и переменную, указывающую на объект диалога настроек:

    private slots :
    	void showPreferencesDialog ( );
    private :
    	settingsDialog * msettingsDialog;

    В файле mainwindow.cpp подключите файл описания класса

    SettingsDialog: 
    #include "settingsdialog.h"

    Создаем диалог настроек

    MainWindow::MainWindow ( QWidget _ parent ) :
    	QMainWindow ( parent ),
    	ui(new Ui::MainWindow ),
    m settingsDialog (new settingsDialog ( this ) )
    //Создаем диалог настроек
    {
    ui->setupui( this ); //Применяем дизайн, который мы создали в редакторе форм в текущем
    окне

    и добавляем реализацию для слота, который вызовет диалог:

    void MainWindow::showPreferencesDialog ( )
    {
    	//Показываем диалог настроек
    	msettingsDialog->show ( );
    }

    В конце добавляем сигнально-слотовое соединение, которое присоединит сигнал от меню к слоту:

    connect ( ui->actionPr_ eferences, SIGNAL( triggered ( ) ), msettingsDialog, SLOT(
    	showPreferencesDialog ( ) ), Qt::UniqueConnection );

    Конечно, мы могли присоединить сигнал к слоту show() диалога напрямую, но в следующем параграфе мы рассмотрим, как хранить в системе и считывать настройки нашей программы, и перед тем как вызвать диалог мы добавим код, который будет инициализировать диалог настроек.

    А пока суммируем наши знания о дизайнере форм. Алгоритм работы с дизайнером форм выглядит примерно так:

  • Примерно разместить виджеты на форме;
  • Применить к виджетам компоновки, начиная с наиболее вложенных виджетов;
  • Добавить меню, панели, настроить действия;
  • Настроить объекты на форме с помощью редактора свойств;
  • Сделать сигнально-слотовые соединения, если необходимо;
  • Реализовать логику работы в исходном коде программы.
  • 15.7 Сохранение настроек

    Для сохранения настроек в программе мы воспользуемся классом QSettings. Он позволяет сохранять настройки программы стандартным для программной платформы способом. В ОС Linux и Mac OS X это конфигурационные файлы (.ini и. .plist), а под управлением ОС Windows настройки программы можно сохранять в системный реестр. Также класс QSettings часто используют для редактирования произвольных. .ini и .plist файлов и ключей системного реестра Windows.

    Чтобы добавить поддержку сохранения настроек в программе объявим несколько дополнительных методов.

    private slots :
    	void slotPreferencesAccepted ( );
    private :
    	void readsettings ( );
    	void writesettings ( );
    	void applysettings ( );

    Метод readSettings будет читать настройки программы и инициализировать настройками наш диалог. Метод writeSettings будет читать настройки, установленные в диалоге, и записывать их. Наконец метод applySettings будет получать текущие настройки от диалога и настраивать соответствующим образом программу.

    void MainWindow::read settings ( )
    {
    //Указываем, где хранились настройки. QSettings::NativeFormat — в формате определенном
    	системой
    //QSettings::UserScope — настройки для каждого пользователя отдельно.
    //Также устанавливаем имя организации и название программы
    	Qsettings lsettings ( Qsettings::NativeFormat, Qsettings::UserScope, " ", qApp
    	->ApplicationName ( ) );
    //Открываем группу настроек
    	lsettings.beg inGroup (SETTINGS_GROUP_VIEW);
    //Читаем настройки
    	bool lShowToolBar= lsettings.value (SETTING_SHOW_TOOLBAR, true ).toBool ( );
    	msettingsDialog->setShowToolBar ( lShowToolBar );
    	bool lShowStatusBar = lsettings.value (SETTING_SHOW_STATUS_BAR, true ).toBool
    	( );
    	msettingsDialog->s etShowStatusBar ( lShowStatusBar );
    }
    void MainWindow::write settings ( )
    {
    //Указываем как сохранять настройки. QSettings::NativeFormat — в формате определенном
    	системой
    //QSettings::UserScope — настройки для каждого пользователя отдельно.
    //Также устанавливаем имя организации и название программы.
    	Qsettings lSettings ( Qsettings::Nat iveFormat, Qset t i n gs::UserScope, " ", qApp
    	->ApplicationName ( ) );
    //Открываем группу настроек
    	lSettings.beg inGroup (SETTINGS_GROUP_VIEW);
    //Записываем настройки
    	lSettings.setvalue (SETTING_SHOW_TOOLBAR, msettingsDialog->isShowToolBar ( ) );
    	lSettings.setvalue (SETTING_SHOW_STATUS_BAR, mSettingsDialog ->
    	isShowStatusBar ( ) );
    }
    void MainWindow::applysettings ( )
    {
    //Читаем настройки установленые в диалоге и применяем их
    	ui->mainToolBar->setVisible ( msettingsDialog ->isShowToolBar ( ) );
    	ui->statusBar->setVisible ( msettingsDialog ->isShowStatusBar ( ) );
    }

    Модифицируем также метод showPreferencesDialog, чтобы он вызывал считывание настроек. Также добавим реализацию метода slotPreferencesAccepted.

    void MainWindow::showPreferencesDialog ( )
    {
    	readSettings ( ); //Считываем настройки и устанавливаем их для диалога
    	mSettingsDialog->show ( ); //Показываем диалог настроек
    }
    void MainWindow::slotPreferencesAccepted ( )
    {
    	writeSettings ( ); //Записать новые настройки
    	applySettings ( ); //Применить настройки
    }

    В конструкторе главного окна соединим сигнал accepted от диалога к слоту slotPreferencesAccepted: connect(mSettingsDialog, SIGNAL(accepted()), this, SLOT (slotPreferencesAccepted()), Qt::UniqueConnection);

    15.8 Использование сторонних (third party) разработок в собственном проекте

    Программная реализация уже существует для множества функциональных возможностей, для большинства распространенных стандартов и для решения большого количества типичных задач. Например, реализация протокола SMTP (Simple Mail Transfer Protocol) для электронной почты или библиотека для быстрого преобразования Фурье, или даже визуальный элемент для вывода графика функции — являются примерами задач, с которыми может сталкиваться разработчик. Поэтому программист может столкнуться с необходимостью исследовать предметную область, спроектировать и разработать собственный вариант решения для них, выполняя работу заново с самого начала, хотя для этих задач уже существует реализация в других программных продуктах (библиотеках, инструментариях, и т. п.).

    Использование сторонних разработок в собственном проекте может значительно сократить время создания программы, обогатить функциональные возможности, и, возможно, обеспечить совместимость со стандартами и другими программами, которые уже существуют. Конечно, в таком случае приходится иметь дело с кодом, созданным другим программистом, который может содержать свои собственные особенности, дефекты, требования к выполнению. Но в большинстве случаев (если сторонняя разработка достаточно хорошо протестирована и рассчитана на подобное использование, и поставляется как библиотека или фреймворк) это предоставляет серьезные преимущества, поскольку дает возможность избежать повторного решения задачи, которая была решена другим разработчиком ранее. Особенно это важно для свободного программного обеспечения с открытым кодом, которое на полную мощность пользуется возможностями повторного использования.

    Конечно, играет важную роль способ лицензирования программного кода, который будет предоставлять возможности для использования текста или готовой программы в собственных целях. Лицензия может также давать возможность использовать программное обеспечение или его исходный код в коммерческих проектах. Поэтому особенно важно убедиться в лицензионной чистоте кода, который Вы будете использовать в своем проекте и в ограничениях, которые лицензия накладывает на его использование.

    В нашем примере мы используем библиотеку Qwt, которая дает дополнительный набор виджетов для вывода графиков, гистограмм, значений, а также несколько дополнительных элементов управления. Исходный код и документацию можно получить на сайте http://qwt.sourceforge.net.

    Откроем папку с исходным кодом и файл проекта qwt.pro. Обратите внимание: файл qwt.pro содержит строку

    include(qwtconfig.pri)

    Файл qwtconfig.pri служит для настройки компиляции Qwt. Для компиляции примеров и тестового проекта для исследования Qwt откомментируйте:

    QWT_CONFIG += QwtExamples
    QWT_CONFIG += QwtPlayground

    Также закомментируйте строку для того, чтобы построить отдельную динамическую библиотеку для Qwt, которую мы будем использовать (эту строку необходимо комментировать только под Windows):

    QWT_CONFIG += QwtDesignerSelfContained

    Далее отключите Shadow Build и запустите компиляцию в режиме Release. После завершения компиляции мы получим такое размещение файлов :

  • qwt/designer/plugins/designer— расширение для QtDesigner, которое даст нам возможность использовать виджеты Qwt при визуальном проектировании интерфейса;
  • qwt/lib — содержит динамически загружаемую библиотеку Qwt;
  • qwt/src — содержит заголовочные файлы необходимые для разработки;
  • Создадим в папке нового GUI проекта подпапку и назовем ее 3rdparty. Добавим внутри нее еще одну папку с названием qwt. Наконец создадим внутри qwt еще две папки: lib и include. Внутрь папки lib скопируем содержимое qwt/lib — построенных библиотек qwt. А в папку include скопируем все файлы qwt/src, которые имеют расширение .h. Наконец для удобства создадим файл для включения в проект 3rdparty/qwt/qwt.pri и добавим содержание:

    INCLUDEPATH += $$PWD/ include
    
    LIBS += -L$$PWD/ lib
    LIBS += -lqwt

    Первая строка .pri-файла указывает путь к размещению .h-файлов. Переменная $$PWD хранит значение текущей папки, то есть той, где находится файл qwt.pri. Вторая строка указывает размещение библиотек (- L <путь_к_библиотекам>). Последняя строка указывает на необходимость связывать программу во время компиляции с библиотекой qwt (-l <название_библиотеки>, где название_библиотеки — название файла библиотеки без расширения и префикса lib).

    Отметим, что для того чтобы построить проект в Debug режиме необходимо добавить соответствующие (тоже построенные в Debug режиме) библиотеки. То же касается и Release режима.

    Теперь добавим этот файл к проекту PlotExample.pro. После строк

    QT += core gui
    greaterThan (QT_MAJOR_VERSION, 4 ) : QT += widgets

    добавим include(3rdparty/qwt/qwt.pri)

    Теперь мы можем воспользоваться библиотекой Qwt в проекте. Создадим виджет для графика и добавим его в окно.

    #include <QVBoxLayout>
    #include <qwt_plot.h>
    #include <qwt_plot_canvas.h>
    #include <qwt_plot_panner.h>
    #include <qwt_plot_magnifier.h>
    MainWindow::MainWindow ( QWidget * parent ) : QMainWindow ( parent ), ui(new Ui : :
    	MainWindow )
    {
    ui->setupui( this );
    setCentralwidget(new QWidget ); //Центральный виджет
    QLayout * lLayout = new QVBoxLayout;
    centralwidget( )->set Layout ( lLayout ); //компоновщик для размещения графика
    QwtPlot * lPlot = new QwtPlot; //Визуальный элемент — График
    lLayout->addWidget ( lPlot );
    QwtPlotCanvas * lCanvas = new QwtPlotCanvas ( ); //Объект для отображения
    lCanvas->setFrameStyle (QFrame::Box | QFrame::Plain ); //нашего графика
    lPlot ->setCanvas ( lCanvas );
    lPlot ->setAxisTitle ( QwtPlot::xBottom, " x " ); //Название оси — x
    lPlot ->setAxisScale ( QwtPlot::xBottom, -10.0, 10.0 ); //Границы оси x
    lPlot ->setAxisTitle ( QwtPlot::yleft, " y " ); //Название оси — y
    lPlot ->setAxisScale ( QwtPlot::yleft, -10.0, 10.0 ); //Границы оси у
    new QwtPlotPanner ( lCanvas ); //Добавляем инструмент для перетягивания смещения
    //графика указателем мышки
    new QwtPlotMagnifier ( lCanvas ); //Добавляем инструмент для увеличения уменьшения
    //графика роликом мышки
    }

    После запуска программы мы увидим пустое окно графика на экране (рис. 15.13).

    15.9 Задачи для самостоятельного решения

  • Добавьте вывод на панель статуса текущей позиции курсора в текстовом редакторе. Используйте класс QStatusBar и его метод showMessage(), который принимает как параметр сообщение и количество миллисекунд в течении которых сообщение видимо. Используйте сигнал QPlainTextEditor:: currentPositionChanged() для определения изменения позиции курсора. (рис 15.13) Пример: программа для вывода графика функции.
  • Добавьте к настройкам опцию для сохранения геометрии главного окна после закрытия программы. Если флажок опции включен, то после запуска главное окно будет на той же позиции и тех же размеров, как и в момент перед закрытием программы.
  • Создайте проект с графическим интерфейсом. Разместите на окне в компоновщиках 5 различных виджетов. Соедините их (по 2–3 между собой) сигнально-слотовыми соединениями, таким образом, чтобы они реагировали на изменения состояния друг друга. Выполните это задание исключительно с помощью редактора форм. Все виджеты должны быть расположены в компоновщиках и пропорционально изменять размеры при изменении размеров окна.
  • Попробуйте добавить вывод простого графика функции приведенному выше примеру использования компоненты Qwt.
  • Запрограммируйте вывод протабулированной функции на график из файла. Для этого добавьте пункт главного меню "File-> Load...", который будет показывать диалог открытия файла. После того, как пользователь выберет файл, программа должна загрузить данные из него и построить график функции.
  • Попробуйте использовать в качестве посторонней компоненты для вывода графика — QCustomPlot (сайт проекта :http://www.qcustomplot.com/, лицензия GPL). Использование этого проекта не требует компиляции библио- теки — просто добавьте файлы qcustomplot.h и qcustomplot.cpp в проект. Обратите внимание на документацию и примеры, которые поставляются вместе с проектом.
  • Вернуться к учебному плану