Программирование под Windows в среде Visual C++ 2005

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

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

Начало работы в Visual C++

Примеры программ для лекции.

Вывод текста в окно программы

Создадим программу, которая будет считать символы с клавиатуры и отображать их в клиентском окне. Для этого будем обрабатывать сообщение Windows WM_CHAR. Свяжем с ним функцию OnChar(UINT nChar, UINT nRepCnt, UINT nFlags ). Назовем программу Key. Будем создавать ее с помощью MFC Application Wizard, интерфейс SDI (Single Document Interface).

Этапы написания программы

  • буфер под хранение полученных символов.
  • Организовать чтение символов с клавиатуры.
  • Сохранить символы в документе.
  • Отобразить текст.
  • 1. Подготовка буфера для хранения символов

    Определим в классе CKeyDoc, прототип которого содержится в заголовочном файле KeyDoc.h, строку-объект StringData класса MFC CString. Для этого добавим следующий код:

    class CKeyDoc : public CDocument
    {
      ……
      DECLARE DYNCREATE(CKeyDoc)
    Public:
      CString StringData;
      ……
    };

    Проинициализируем переменную пустой строкой " ". Это делается в конструкторе объекта документа, расположенном в файле KeyDoc.cpp. Конструктор объекта документа CKeyDoc:

    CKeyDoc::CKeyDoc()
    {
      // TODO:
      StringData="";
    }

    2. Чтение символов с клавиатуры

    При нажатии клавиши Windows посылает сообщение WM_CHAR. Его надо связать с методом OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) объекта вида. Для этого используем утилиту Class View

  • Вызовем контекстное меню класса CKeyView (нажмем правой кнопкой по имени класса в окне утилиты Class View ) $$\to$$ выберем пункт Properties.
  • Во всплывшем окне свойств нажмем на кнопку Messages.
  • Выбираем в списке сообщений WM_CHAR. Раскрываем список действий в пустом поле напротив названия сообщения нажатием левой кнопкой мыши. Выбираем единственный вариант <Add> OnChar. Мастер автоматически создаст обработчик OnChar и откроет файл KeyView.cpp для редактирования тела метода.
  • 3. Сохранение символа в документе

    Введенный символ находится в параметре nChar и его необходимо сохранить в строковом объекте StringData. Обработчик OnChar:

    void CKeyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
       CKeyDoc *pDoc = GetDocument();   //Получение указателя на объект документа
       ASSERT_VALID(pDoc);   //Проверка указателя
       if(!pDoc)
          return;
       pDoc->StringData += char(nChar);   //Обновление строки символов
       Invalidate();   //Вызов метода OnDraw
    }

    Макрос ASSERT_VALID проверяет, что полученный указатель действительно ссылается на документ (иначе ошибка).

    4. Отображение текста

    Вывод будем осуществлять в методе OnDraw(CDC* pDC) класса вида. Для вызова метода OnDraw используется функция Invalidate. Метод OnDraw:

    void CKeyView::OnDraw(CDC *pDC)
    {
      …
      pDC->TextOut(0,0,pDoc->StringData);
    }

    Скомпилируем и запустим программу. Усложним задачу. Потребуем, чтобы текст выводился в центре клиентской области окна. Для решения поставленной задачи изменим метод OnDraw. Измененный метод OnDraw:

    void CKeyView::OnDraw(CDC* pDC)
    {
      CKeyDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      // TODO: add draw code for native data here
      CRect rect;    //Переменная для хранения размеров клиентской области
      GetWindowRect(rect);  //Получение размеров клиентской области
      int x=rect.Width()/2;  //Задание координаты X центра клиентской области
      int y=rect.Height()/2;  //Задание координаты Y центра клиентской области
      CSize size = pDC->GetTextExtent(pDoc->StringData);  //Определение размеров выводимой строки в пикселях
      x -= size.cx/2;    //Сдвиг координаты X на половину длины выводимой строки влево
      y -= size.cy/2;    //Сдвиг координаты Y на половину высоты выводимой строки вверх
      pDC->TextOut(x,y,pDoc->StringData);    //Вывод строки в центр клиентской области
    }

    Работа с курсором и мышью

    Напишем программу, которая будет создавать курсор, в выбранной произвольной точке клиентской области и выводить текст с указанной позиции. Создадим SDI программу под названием Mouse. Воспользуемся материалом предыдущего пункта: создадим обработчик сообщения WM_CHAR. Чтобы создать курсор, необходимо знать его размеры (обычно высота равна высоте символа, а ширина 1/8 ширины символа). Курсор будем создавать в методе OnDraw. Чтобы определить размеры курсора, необходимо получить данные из структуры TEXTMETRIC. Для этого необходимо создать объект типа TEXTMETRIC и заполнить его поля, используя метод GetTextMetrics(tm) . Но сначала добавим в прототип класса CMouseView следующие переменные

    void CMouseView::OnDraw(CDC* pDC)
    {
       CMouseDoc* pDoc = GetDocument();
       ASSERT_VALID(pDoc);
       if (!pDoc)
            return;
    
       TEXTMETRIC textmetric;   //Переменная для хранения информации о шрифте
       pDC->GetTextMetrics(textmetric);   //Получение информации о шрифте
       CSize size=pDC->GetTextExtent(pDoc->StringData);   //Получение размеров выводимой строки в пикселях
       CreateSolidCaret(textmetric.tmAveCharWidth/8,textmetric.tmHeight);   //Создаём курсор
       CaretPosition.x=size.cx;   //Присваивание x  корд. конца 
       CaretPosition.y=0;   //Присваивание y корд.
       SetCaretPos(CaretPosition);   //Задаем позицию курсора
       ShowCaret();   //Вывод на экран курсора
       pDC->TextOut(0,0,pDoc->StringData);
    }

    Рассмотрим задачу вывода строки с места, на который указывает указатель мыши. Свяжем сообщение WM_LBUTTONDOWN с методом OnLButtonDown(UINT nFlags, CPoint point) . Параметр Cpoint (объект класса CPoint) содержит текущие координаты указателя мыши. Для очистки строкового объекта используется метод Empty() класса CString. Обработчик нажатия левой кнопки мыши OnLButtonDown:

    void CMouseView::OnLButtonDown(UINT nFlags, CPoint point) 
    {
      // TODO: Add your message handler code here and/or call default
      x=point.x;    //Запоминаем координату X места где была нажата левая кнопка мыши
      y=point.y;    //Запоминаем координату Y места где была нажата левая кнопка мыши
      CMouseDoc* pDoc = GetDocument();  //Получаем указатель на объект документа
      ASSERT_VALID(pDoc);      //Проверяем правильность указателя
      pDoc->StringData.Empty();    //Очищаем выводимую строку
      Invalidate();        //Вызываем OnDraw
      CView::OnLButtonDown(nFlags, point);
    }

    В методе класса OnDraw сделаем изменения:

    CaretPosition.x=x+size.cx;  //Присваивание x  корд. конца 
      CaretPosition.y=y;
      ……
      pDC->TextOut(x,y,pDoc->StringData);

    Скомпилируем и запустим приложение.

    Панель инструментов, меню, акселераторы

    В этом параграфе будем рассматривать работу с меню с помощью редактора ресурсов на примере создания простой программы.

    Создание меню

  • Создадим SDI программу с названием Menu.
  • Вкладка Resource View, идем по дереву Menu $$\to$$ Menu.rc $$\to$$ Menu $$\to$$ IDR_MAINFRAME двойной клик.
  • Вставим пункт меню: File $$\to$$ Type Hear, вводим название Print Welcome.
  • Вызовем свойства пункта меню: Клик правой кнопкой по имени $$\to$$ Properties.
  • Запишем в поле ID: ID_FILE_PRINTWELCOME.
  • Закроем редактор меню и запустим программу.
  • В меню File появилась команда, но она не работает (не подключена). Для того чтобы программа реагировала на выбор только что созданного пункта меню, свяжем с ним обработчик.

  • В окне утилиты Class View вызовем свойства класса CMenuView.
  • В окне свойств нажмем кнопку Events.
  • Выберем в списке идентификаторов ID_FILE_PRINTWELCOME и раскроем ветку.
  • Раскроем список напротив поля COMMAND и выберем "Add" OnFilePrintwelcome.
  • Создадим переменную типа CString и инициализируем в объекте документа. Добавим код, выделенный жирным, в прототип класса CMenuDoc.

    public:
      virtual ~CMenuDoc();
      CString StringData;

    Проинициализируем переменную в конструкторе:

    StringData="";

    Напишем код обработчика OnFilePrintwelcome

    void CMenuView::OnFilePrintWelcome()
    {
      CMenuDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      if(!pDoc)
         return;
      pDoc->StringData="Добро пожаловать в меню.";
      Invalidate();
    }

    Добавим следующую строку в метод OnDraw:

    pDC->TextOut(0,0,pDoc->StringData);

    Усложним задачу. Создадим полномасштабную ветку меню с собственными подменю. Добавим в меню пункт Demo.

  • Войдем в редактор меню.
  • Выделим Edit $$\to$$ Вызовем контекстное меню $$\to$$ Выберем Insert New $$\to$$ Введем имя Demo.
  • Введем пункты: Grayed, Checked, Submenus
  • Если в начало ввести , то первая буква меню будет подчеркнута. Пусть это буква D, тогда Alt+D - это клавишный ускоренный вызов. Добавим подменю в пункте Submenus. Выделим пункт Submenus, в раскрывшейся слева вкладке Type Hear введем имя первого элемента подмею. Добавим в подменю еще оди пункт. Получим:

  • Sub1
  • Sub2
  • Создание акселератора

    Акселератор - сочетание клавиш для выбора команды меню. Чтобы добавить акселератор, сделаем следующее:

  • Откроем папку Accelerator в редакторе ресурсов. Дважды кликнем IDR_MAINFRAME.
  • Выделим последнюю пустую строку.
  • В появившемся списке в поле ID выберем строку ID_SUBMENUS_SUB1.
  • Выберем строку Ctrl из списка в поле Modifiers.
  • Выберем строку VK_F5 из списка в поле Key.
  • Выход.
  • Добавить в название клавиши Sub2\tCtrl+F5.
  • Добавление кнопок на панель инструментов

    Создадим кнопку на панели для команды Sub1

  • Откроем папку Toolbar в редактрое ресурсов. Дважды кликнем IDR_MAINFRAME
  • Выделим пустую кнопку и сделаем рисунок.
  • Вызовем свойства нарисованной кнопки. Выберем в поле ID имя идентификатора ID_SUBMENUS_SUB1.
  • Добавим код для пункта Sub1. Для этого вызовем свойства класса CMenuView из утилиты Class View. Перейдем к списку событий Events. Раскроем вкладку ID_SUBMENUS_SUB1 и добавим обработчик.

    void CMenuView::OnSubmenusSub1()
    {
      CMenuDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      if(!pDoc)
           return;
      pDoc->StringData="Меню 1";
      Invalidate();
    }

    Тоже можно сделать и для Sub2.

    Диалоговые окна. Работа с элементами управления - кнопками, текстовыми полями

    Модальное диалоговое окно

    Создадим программу, которая будет выводить диалоговое окно в ответ на выбор пользователем пункта Show Dialogs в меню File. В диалоговом окне создадим текстовое поле и кнопку. При нажатии на кнопку в текстовое поле должна заноситься символьная строка. Символьную строку изменим и при выходе из диалогового окна в клиентской области выведем измененную символьную строку.

    Создание диалогового окна

  • Создадим SDI программу Dialog.
  • Включим в меню File команду Show Dialogs и свяжем с обработчиком.
  • Войдем в редактор ресурсов. Щелкнем правой кнопкой по папке Dialog, выберем Insert
  • Dialog. Среда переведет нас в режим визуального редактирования диалогового окна.
  • Вставим два элемента: кнопку и текстовое поле.
  • Изменим надпись на кнопке через свойства Properties: "Print String".
  • Создадим класс диалогового окна на основе созданного только что ресурса:

  • Project $$\to$$ Add Class…
  • В списке Categories: выберем MFC. В списке Templates: выберем MFC Class. Нажмем Add.
  • В поле Base class: выберем CDialog. В поле Dialog ID выберем идентификатор только что созданного ресурса.
  • Введем имя класса: CDlg и нажмем Finish.
  • Связывание обработчиков с элементами диалоговых окон

    При нажатии на кнопку в текстовом поле должна появиться строка "Текст в диалоговом окне". Свяжем сообщение о нажатии на кнопку с обработчиком этого сообщения

  • Вызовем свойства класса CDlg через Class View.
  • Перейдем к списку событий Events.
  • Выберем поле ID_BUTTON1 и добавим обработчик OnBnClickedButton1.
  • Связывание переменных с элементами диалоговых окон

    Visual C++ позволяет связывать переменные класса с элементами диалоговых окон. Создадим переменную m_text для хранения строки текстового поля с помощью мастера:

  • Вызовем контекстное меню класса CDlg через Class View. Выберем пункт Add $$\to$$ Add Variable…
  • Установим флажок Control Variable. Выберем в поле Control ID: IDC_EDIT1, в поле Category: Value, Variable type: CString, Variable name: m_text.
  • Finish.
  • Обработчик OnButton1:

    void CDlg::OnBnClickedButton1()
    {
      m_text="Текст в диалоговом окне";
      UpdateData(false);
    }

    Обмен информацией между переменной и элементом IDC_EDIT1 осуществляется в специальном методе, включенном в класс диалогового окна:

    void CDlg::DoDataExchange(CDataExchange* pDX)
    {
      CDialog::DoDataExchange(pDX);
      DDX_Text(pDX, IDC_EDIT1, m_text);
    }

    Код этого метода генерируется мастером, изменять его вручную не рекомендуется. Вызов метода UpdateData(BOOL) с параметром false заносит в текстовое поле значение переменной m_text. Вызов с параметром true присваивает переменной m_text содержимое текстового поля.

    Переопределение метода для кнопки Ok

    Переопределение необходимо, если поле редактировалось и необходимо сохранить текущее содержимое текстового поля. Для того чтобы переопределить метод, вызовем свойства класса CDlg, перейдем к списку виртуальных функций, нажав кнопку Overrides. Выберем метод OnOk().

    void CDlg::OnOK()
    {
      UpdateData(true);
      CDialog::OnOK();
    }

    Отображение диалогового окна

    Чтобы класс вида мог работать с членами класса CDlg, необходимо включить в него Dlg.h - заголовочный файл класса CDlg:

    #include "Dlg.h"

    В методе OnFileShowdialogs() создадим новый объект класса CDlg и отобразим его, пользуясь методом DoModal(). Далее добавим в класс документа переменную StringData типа CString и присвоим ей значение переменной m_text. Выведем значение переменной StringData в клиентском окне в левом верхнем углу.

    Обработчик OnFileShowdialogs:

    void CDialogView::OnFileShowdialogs()
    {
       CDlg dlg;
       int result=dlg.DoModal();
       if(result==IDOK)
       {
            CDialogDoc* pDoc = GetDocument();
            ASSERT_VALID(pDoc);
            if(!pDoc)
                 return;
            pDoc->StringData=dlg.m_text;
            Invalidate();
       }
    }

    Добавим в метод OnDraw строку:

    pDC->TextOut(0,0,pDoc->StringData);

    Использование диалогового окна в качестве главного

    Создадим диалоговое окно в качестве главного и выведем в нем, по нажатию на кнопку, текстовое сообщение. Создадим программу BaseDialog.

  • На первом шаге работы мастера Application Wizard выбрать Dialog based (базовым классом для новой программы станет класс CDialog ).
  • Finish.
  • Перейдем в редактор ресурсов, откроем ресурс диалогового окна для редактирования.
  • Поместим кнопку и текстовое поле.
  • Свяжем кнопку с обработчиком (двойной клик по OnButton1).
  • Создавать класс диалогового окна в этом случае не надо. Его создал уже автоматически Application Wizard. Рассмотрим общий способ работы с элементами управления - будем рассматривать их как объекты:

  • Вызовем контекстное меню текстового поля (щелчок правой кнопкой мыши по элементу управления в редактрое ресурсов).
  • Выберем пункт Add Variable…
  • Выберем необходимый тип и введем имя переменной.
  • Нажмем Finish.
  • Обработчик OnButton1:

    void CBaseDialogDlg::OnButton1()
    {
      m_edit.SetWindowText(CString("Диалоговое окно!"));
    }

    Совместное использование флажков и переключателей

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

    Флажки

    Создадим диалоговое окно с тремя флажками и текстовым полем, в котором будет отображаться информация о выбранном флажке. Создадим программу Maps на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …", и добавить три флажка и текстовое поле. Флажки надо выстроить в виде столбца. Текстовое поле расположим внизу. Названия флажков - Flag 1, Flag 2, Flag 3.

    Выравнивание элементов в редакторе диалоговых окон:

    Нажать на Ctrl и, не отпуская ее, щелкнуть на каждом из флажков. Последний из выбранных флажков будет служить "эталоном" (он выделен синими маркерами).

  • Выравнивание по горизонтали (по левому краю): Контекстное меню->Align Lefts.
  • Выравнивание по вертикали (расстояния между флажками будут одинаковыми):
  • Контекстное меню -> Align Tops.
  • Связывание флажков с кодом программы:

    С помощью Class View каждый флажок свяжем со своим обработчиком ( OnCheck1(), OnCheck2(), OnCheck3() ). Создадим переменную m_text и свяжем ее с текстовым полем.

    void CMapsDlg::OnCheck1()
    {
      m_text="Выбран 1 флажок";
      UpdateData(false);
    }
    void CMapsDlg::OnCheck1()
    {
      m_text=" Выбран 2 флажок ";
      UpdateData(false);
    }
    void CMapsDlg::OnCheck1()
    {
      m_text=" Выбран 3 флажок ";
      UpdateData(false);
    }

    Переключатели

    Создать диалоговое окно с тремя переключателями и текстовым полем, в котором будет отображаться информация о выбранном переключателе. Создадим программу Radios на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …" , и добавить три переключателя и текстовое поле. Переключатели надо выстроить в виде столбца и внизу текстовое поле. Названия переключателей: Radio 1, Radio 2, Radio 3.

    void CRadiosDlg::OnRadio1() 
    {
      m_text="Выбран  1 переключатель";
      UpdateData(false);
    }
    void CRadiosDlg::OnRadio2() 
    {
      m_text=" Выбран  2 переключатель";
      UpdateData(false);
    }
    void CRadiosDlg::OnRadio3() 
    {
      m_text=" Выбран  3 переключатель";
      UpdateData(false);
    }

    Совместное использование флажков и переключателей

    Создать диалоговое окно с тремя флажками, тремя переключателями и текстовым полем, в котором будет отображаться информация о выбранном варианте. Использовать групповые поля. Групповые поля предназначены для группировки элементов - как визуальной, так и функциональной. Все переключатели внутри группового поля работают совместно. Создадим программу Choice на основе диалогового окна (Dialog based). Редактор диалоговых окон: Удалить надпись "TODO …" , и добавить две группы флажков и переключателей и одно текстовое поле. Названия переключателей - Math, Gum, Inf, Bio, флажки - Math, Phys, Hist, Bio. Создадим для каждого переключателя свой обработчик - OnRadio1(),…, OnRadio4(). Для текстового поля введем переменную m_text. Свяжем с флажками переменные, которые будут изменять состояния флажков. Эти переменные будут представлять весь элемент. Зададим нужное состояние флагов в обработчиках для каждого переключателя.

    void CChoiceDlg::OnRadio1() 
    {
      m_check1=true;
      m_check2=true;
      m_check3=false;
      m_check4=true;
      m_text="85 ball";
      UpdateData(false);
    }
    void CChoiceDlg::OnRadio2() 
    {
      m_check1=false;
      m_check2=true;
      m_check3=true;
      m_check4=true;
      m_text="70 ball";
      UpdateData(false);
    }
    void CChoiceDlg::OnRadio3() 
    {
      m_check1=true;
      m_check2=true;
      m_check3=true;
      m_check4=false;
      m_text="83 ball";
      UpdateData(false);
    }
    void CChoiceDlg::OnRadio4() 
    {
      m_check1=true;
      m_check2=true;
      m_check3=false;
      m_check4=true;
      m_text="70 ball";
      UpdateData(false);
    }

    Списки. Комбинированные поля и бегунки

    Список - управляющий элемент, представляющий пользователю перечень из нескольких строк. (Выделить -1 клик, выбрать - 2 клика.) Комбинированное поле - сочетание текстового поля, раскрывающегося списка и кнопки, с помощью которой пользователь открывает список. Бегунок - управляющий элемент. Чаще всего применяется для ввода числовых величин - например, интенсивности цвета.

    Списки

    Создадим список и выведем выбранную строку в текстовое поле. Создадим программу Lists на основе диалогового окна, добавим список, текстовое поле и 2 надписи ("Дважды щелкните по строке" и "Вы выбрали"). Управляющие элементы можно отображать и в обычном окне (недиалоговым). В этом случае нельзя воспользоваться редактором WISIWYG.

    Создание объекта для работы со списком

  • Вызовем контестное меню списка в редакторе диалогового окна и выберем пункт Add
  • Variable.
  • В поле Category: выберем значение Control.
  • Variable name: m_list.
  • Finish.
  • Инициализация данных в списке

    Для инициализации данных используем метод OnInitDialog() и метод AddString(…) объекта m_list. Внесем список группы.

    BOOL CListsDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
      m_list.AddString("Васильев Н.И.");
      …
      m_list.AddString("Яковлева Е.А.");
      …
    }

    Замечание: Если сортировка не нужна, то сделать клик правой кнопкой мыши в редакторе диалоговых окон и вызвать свойства списка Properties->Sort = false.

    Обработка двойных кликов в списках

    При двойном клике заносится текст выбранной строки в текстовое поле. Создадим переменную m_text типа CString, связанную с текстовым полем, и обработчик OnDblclkList1(). Для определения выбранной строки будем использовать метод GetCurSel(). Это метод класса CListBox. Он возвращает индекс строки, которую пользователь дважды кликнул левой кнопкой мыши. Содержимиое строки получим при помощи метода GetText(…) класса CListBox.

    void CListsDlg::OnDblclkList1()
    {
      m_list.GetText(m_list.GetCurSel(),m_text);
      UpdateData(false);
    }

    Комбинированные поля

    Создадим программу Combos на основе диалогового окна. Создадим комбинированное поле и текстовое поле. Добавим переменные m_combo типа CComboBox, связанную с элементом управления комбинированное поле, и m_text типа CString, связанную с текстовым полем. Комбинированные поля инициализируются в методе OnInitDialog():

    BOOL CCombosDlg::OnInitDialogs()
    {
      CDialog::OnInitDialog();
      m_combo.AddString("Васильев Н.И.");
      …
      m_combo.AddString("Яковлева Е.А.");
      m_combo.SetCurSel(0);  //Выбор первой строки списка
      …
    }

    Программа должна сообщать, какую строку списка выбрал пользователь. Комбинированное поле генерирует событие CBN_SELCHANGE. Добавим обработчик этого события в класс диалогового окна.

    Properties (Класс диалогового окна)->Events->IDC_COMBO1->CBN_SELCHANGE->OnSelchangeCombo1()

    Обработчик OnSelchangeCombo1:

    void CComboDlg::OnSelchangeCombo1()
    {
      m_combo.GetLBText(m_combo.GetCurSel(),m_text);
      UpdateData(false);
    }

    Прокрутка и использование бегунков

    Создадим программу Slider на базе диалогового окна. Добавим текстовое поле, две надписи ("координата бегунка" и "движение") и бегунок (ему присвотся идентификатор IDC_SLIDER1 ) и, связанную с ним, переменную m_slider типа CSliderCtrl. При инициализации бегунка необходимо задать его интервал. Эта величина определяет возможные позиции бегунка от крайнего левого до крайнего правого положения. В примере бегунок принимает значения от 1 до 100. Эти значения задаются методами SetRangeMin(…) и SetRangeMax(…) класса CSliderCtrl. Второй параметр этих методов показывает, нужно ли перерисовывать ползунок после изменения интервала. Передавая значение false, мы отказываемся от перерисовки.

    BOOL  CSliderDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
      m_slider.SetRangeMin(1, false);
      m_slider.SetRangeMax(100, false);
      m_text="1";    //Связываем переменную с содержимым текстового поля и присваем значение 1
      UpdateData(false);
      …
    }

    Обработка сообщений бегунка

    При перемещении бегунка элемент посылает сообщение WM_HSCROLL. Добавим его обработчик. Нам необходимо перехватывать сообщения с кодом SB_THUMBPOSITION, посылаемые при перемещение бегунка. Выведем в текстовом поле его новую позицию. Создадим переменную m_text, связанную с содержимым текстового поля, и присвоим ей значение параметра nPos обработчика OnHScroll(). Объект m_text относится к классу CString, а параметр nPos имеет целый тип. Для представления целого числа в виде текстовой строки воспользуется методом Format() класса CString.

    void CSliderDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
    {  
      if(nSBCode == SB_THUMBPOSITION) 
      {
        m_text.Format("%ld",nPos);
        UpdateData(false); 
      }
      else CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
    }

    Сериализация. Работа с файлами

    Сериализация объектов класса CString

    Сериализация - процесс записи (чтения) объектовов и данных на диск (с диска). Рассмотрим сериализацию как встроенных классов Visual C++ (например, CString ), так и нестандартных. Если в программе отсутствует документ, на который можно возложить выполнение файловых операций (программы на базе диалоговых окон), то работают с классом MFC CFile. Рассмотрим программу Writer, которая будет записывать на диск введенную строку, а затем, по требованию пользователя, загружать ее из файла.

  • Создатим SDI программу Writer.
  • Создадим объект CString StringData и инициализируем ее.
  • Создадим обаботчик WM_CHAR OnChar().
  • void CWriterView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
      //TODO:
      CWriterDoc *pDoc = GetDocument();  
      ASSERT_VALID(pDoc);      
      pDoc->StringData += char(nChar);	
      Invalidate();         
    }

    Отобразим данные в OnDraw:

    pDC->TextOut(0,0,pDoc->StringData);

    Класс документа (файл WriterDoc.cpp) содержит встроенный метод Serialize(CArchive ar) . В этом методе происходит сериализация объекта StringData. Методу Serialize(CArchive ar) передается ссылка на объект ar класса CArchive. Работа с объектом ar практически не отличается от работы с потоками cout и cin.

    if(ar.IsStoring()) ar<<StringData;
    else ar>>StringData;

    Чтобы сообщить приложению об изменении данных (заносим новый символ), вызовем в OnChar(...) метод объекта документа SetModifiedFlag(). Если будет сделана попытка выхода из программы без сохранения данных, то приложение выведет диалоговое окно с предложением сохранить данные.

    Добавим строку в метод OnChar(...): Д

    pDoc->SetModifiedFlag();

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

    Метод OnNewDocument:

    BOOL CWriterDoc::OnNewDocument()
    {
      if (!CDocument::OnNewDocument())
        return FALSE;
      // TODO: add reinitialization code here
      // (SDI documents will reuse this document)
      StringData="";
      UpdateAllViews(NULL);
      return TRUE;
    }

    Сериализация нестандартных объектов

    Создадим пользовательский класс и организуем сериализацию его объектов. Пусть класс содержит строковую переменную (типа CString ). Кроме этого в классе должен быть реализован конструктор, три метода для работы со строками: AddText() - добавление текста в конец строки, DrawText() - вывод текста в констексте устройства и ClearText() - очистка (стирание содержимого) строки .

    Алгоритм организации сериализации нестандартных объектов

  • Создать SDI программу Serializer.
  • Добавить в проект заголовочный файл.
  • Включить в этот файл описание класса (наследуем его от CObject) - члены и методы класса.
  • Включить в заголовочный файл документа ссылку на новый файл.
  • Создать объект класса.
  • Использовать новый объект (его методы) в приложении.
  • Включить в определение класса макрос Visual C++ DECLARE_SERIAL, объявляющий методы, используемые в процессе сериализации.
  • Переопределить метод Serialize() класса CObject.
  • Для написания новой версии Serialize(), добавить в проект новый файл .cpp и определить в нем метод Serialize().
  • Реализуем пользовательский класс CData, наследуемый от CObject. Для этого создадим заголовочный файл Data.h и включим его в проект.

    class CData: public CObject
    { 
    private:
      CString data;
    DECLARE_SERIAL(CData);
    public:
      CData(){data=CString("");}  
      void AddText(CString text){data+=text;}
      void DrawText(CDC* pDC) {pDC->TextOut(0, 0, data);}
      void ClearText(){data="";}
    void Serialize(CArchive archive);
    };

    Подключим Data.h в файл SerializerDoc.h, для этого добавим в начало файла строку:

    #include "Data.h"

    В классе документа введем переменную типа CData

    …
    public:
      CData DataObject;
    …

    Добавим в документ вида метод OnChar(…) и используем методы объекта DataObject.

    void CSerializerView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
    {
      // TODO: Add your message handler code here and/or call default
      CSerializerDoc* pDoc=GetDocument();
      ASSERT_VALID(pDoc);
      if(!pDoc)
            return;
      pDoc->DataObject.AddText(CString(char(nChar)));
      Invalidate();
      CView::OnChar(nChar, nRepCnt, nFlags);
    }

    Метод OnDraw:

    void CSerializerView::OnDraw(CDC* pDC)
    {
      CSerializerDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      pDoc->DataObject.DrawText(pDC);
    }

    Добавим в проект новый файл Data.cpp и определим Serialize(CArchive archive)

    #include "stdafx.h"
    #include "SerializerDoc.h"
    
    void CData::Serialize(CArchive archive)
    {
      CObject::Serialize(archive); // Вызов метода Serialize() базового класса (CObject)
      if(archive.IsStoring())archive<<data;
      else archive>>data;
    }    
    IMPLEMENT_SERIAL(CData, CObject, 0)

    Макрос IMPLEMENT_SERIAL содержит дополнительные методы, ипользуемые Visual C++ для сериализации. Чтобы выполнить сериализацию для объекта DataObject класса CData, следует вызвать его метод Serialize(…) внутри метода Serialize(…) документа.

    void CSerializerDoc::Serialize(CArchive ar)
    {
      DataObject.Serialize(ar);
      // if (ar.IsStoring())
      //{
        // TODO: add storing code here
      //}
      //else
      //{
        // TODO: add loading code here
    //}
    }

    Работа с файлами. Класс CFile

    Некоторые методы класса CFile:

    Метод Назначение
    Abort Закрывает файл, игнорируя любые предупреждения и ошибки
    Close Закрывает файл и удаляет объект
    GetLength Получает длину файла
    GetPosition Получает текущую позицию файлового указателя
    Open Производит открытие файла с возможностью проверки ошибок
    Read Читает данные из файла с текущей позиции
    Remove Удаляет заданный файл
    Rename Переименовывает заданный файл
    Seek Перемещает файловый указатель в заданную позицию
    SeekToBegin Перемещает файловый указатель в начало файла
    SeekToEnd Перемещает файловый указатель в конец файла
    SetLength Изменяет длину файла
    Write Записывает данные в файл с текущей позиции

    Режимы открытия файлов в конструкторе класса CFile:

    Константа Назначение
    CFile::modeCreate создает новый файл
    CFile::modeNoTruncate Комбинируеся с modeCreate - если создаваемый файл уже существует, он не обрезается до нулевой длины
    CFile::modeRead Открывает файл только для чтения
    CFile::modeReadWrite Открывает файл для чтения - записи
    CFile::modeWrite Открывает файл только для записи
    CFile::typeBinary Устанавливает двоичный режим
    CFile::typeText Устанавливает текстовый режим со специальной обработкой пар символов конца/перевода строки

    Задача: Написать программу, которая на базе диалогового окна по нажатию кнопки записывает в заданный файл на диске некоторый текст и считывает этот текст в тектовое поле в обратном порядке. Создадим на базе диалогового окна программу Filer. Создадим 4 текстовых записи, длина каждой записи 20 символов. Реализуем эти записи в программе как массив из 4-х символьных строк OutString.

    …
    protected:
      HICON m_hIcon;
      char OutString[4][20];
    …

    Добавим в диалоговое окно необходимые управляющие элементы - два текстовых поля и кнопку. Для кнопки создадим функцию обработчик, создадим также две переменные m_text1 и m_text2 и свяжем их с текстовыми полями. Имя кнопки: Write and Read.

    BOOL CFilerDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
      strcpy(OutString[0], "Иллюстрация ");
      strcpy(OutString[1], "работы ");
      strcpy(OutString[2], "с ");
      strcpy(OutString[3], "файлами ");
      m_text1 = CString(OutString[0]) + CString(OutString[1]) + CString(OutString[2]) + CString(OutString[3]); 
      UpdateData(false);
    }

    Для записи данных в файл будем использовать класс CFile. При нажатии на кнопку будет создаваться и открываться для записи файл data.txt. Запись будем производить с помощью метода Write(…), для чтения используем методы Read(…) и Seek(…) класса CFile.

    void CFilerDlg::OnButton1()
    {
      UpdateData(true);
      CFile OutFile("data.txt",CFile::modeCreate | CFile::modeWrite); //Создание обекта
      OutFile.Write(m_text1.GetBuffer(), m_text1.GetLength());    //Запись данных в файл
      OutFile.Close();  //Закрытие файла
      CFile InFile("data.txt",CFile::modeRead);  
      ULONGLONG pos = InFile.SeekToEnd();
      while(pos != CFile::begin)
      {
        char buf;
        InFile.Seek(--pos,CFile::begin);
        int n = InFile.Read(buf,1);
        m_text2+=CString(buf);
      }
      UpdateData(false);
      InFile.Close();
    }

    Графика. Работа с растровыми изображениями (фракталы)

    Алгебраические фракталы

    Создадим программу Fractal для построения алгебраических фракталов и рассмотрим на ее примере работу с растровыми изображениями на основе класса CImage. Алгебраические фракталы строятся на основе алгебраических формул, при этом по алгоритму производится последовательность преобразований исходных данных, и результаты очередного шага зависят от предыдущего. Будем строить фрактальное множество, заданное формулой: zn+1=zZnn+c, где : zn, c - комплексные числа, а - номер текущей итерации, z0=0. Для того чтобы получить изображение (фрактал), надо произвести определенное количество итераций для точки комплексной плоскости.

    Алгоритм

  • Все точки последовательно перебираются и закрашиваются разными цветами в зависимости от номера итерации при выполнении следующих условий:
  • модуль zn больше или равен 2 и достигает заданного предела. Можно установить определенные цвета для закрашивания (в программе 16 цветов). Процесс продолжается до тех пор, пока не будут перебраны все точки.
  • Для того чтобы определить цвет каждого пиксела во множестве, необходимо выполнить ряд действий:
  • Задать значение мнимой части параметра равным максимальному значению по мнимой оси, а значение действительной части параметра - равной минимальному значению по действительной оси.
  • Выбрать текущий столбец пикселов.
  • Для каждой строки текущего столбца пикселов увеличить счетчик итераций и проверить, не превышает ли модуль комплексного числа на данной итерации значение 2 и не достигнуто ли предельное количество итераций.
  • Если хотя бы одно из этих условий не выполняется, нарисовать точку с текущими координатами, задаваемыми номерами столбца и строки, окрашенную цветом, зависящим от номера текущей итерации.
  • Перейти к следующей точке столбца, уменьшив значение мнимой части .
  • Перейти к следующему столбцу, увеличив значение действительной части.
  • Создание приложения

    Напишем программу, реализующую данный алгоритм. Создадим SDI приложение без поддержки архитектуры "документ вид" (для этого необходимо снять соответствующий флажок во втором окне мастера MFC Application Wizard). Добавим в главное меню пункт Save для сохранения полученного фрактала. Объявление необходимых переменных в прототипе класса CChildView (заголовочный файл ChildView.h):

    public:
      CImage imgOriginal;  //Объект класса CImage
      CRect m_ClientRect;  //Переменная для хранения размеров клиентской области
      double m_LeftBound;
      double m_RightBound;
      double m_TopBound;
      double m_BottomBound;
      COLORREF m_ColorTable[16];  //Таблица цветов
      const unsigned int m_MaxIter;  //Константа - максимальное число итераций

    Проведем необходимую инициализацию в конструкторе (исходный файл ChildView.cpp ):

    CChildView::CChildView(): m_MaxIter(256),
    m_RightBound(0), m_LeftBound(-1.5),
    m_TopBound(0.5), m_BottomBound(-0.5)
    {
      //Создание таблицы цветов
      m_ColorTable[0] = RGB(128,0,0);
      m_ColorTable[1] = RGB(255,0,0);
      m_ColorTable[2] = RGB(255,128,128);
      m_ColorTable[3] = RGB(255,128,192);
      m_ColorTable[4] = RGB(255,0,255);
      m_ColorTable[5] = RGB(128,0,255);
      m_ColorTable[6] = RGB(0,0,255);
      m_ColorTable[7] = RGB(0,0,128);
      m_ColorTable[8] = RGB(0,128,128);
      m_ColorTable[9] = RGB(0,255,0);
      m_ColorTable[10] = RGB(128,255,128);
      m_ColorTable[11] = RGB(255,255,128);
      m_ColorTable[12] = RGB(255,255,0);
      m_ColorTable[13] = RGB(255,128,0);
      m_ColorTable[14] = RGB(255,128,64);
      m_ColorTable[15] = RGB(128,64,0);
    }

    Добавим в класс CChildView с помощью мастера обработчик изменения размеров окна OnSize.

    void CChildView::OnSize(UINT nType, int cx, int cy)
    {
      GetClientRect(m_ClientRect);
    }

    Добавим в класс CChildView функцию для построения фрактала. В прототип класса (файл CChildView.h ) добавим строку:

    public:
      void BuildFractal();

    Определим ее в файле ChildView.cpp Функция BuildFractal:

    void CChildView::BuildFractal()
    {
      imgOriginal.Destroy();
      imgOriginal.Create(m_ClientRect.right,m_ClientRect.bottom,24);
      double f_Dx = (m_RightBound - m_LeftBound)/m_ClientRect.right;
      double f_Dy = (m_TopBound - m_BottomBound)/m_ClientRect.bottom;
      Complex c0(m_LeftBound,m_TopBound);
      for(int i = 0; i < m_ClientRect.right; i++)
      {
      for(int j = 0; j < m_ClientRect.bottom; j++)
      {
        Complex z0(0,0);
        int n_Iter = 0;
        while(n_Iter < m_MaxIter  z0.Abs() < 4)
        {
          z0 = pow(z0,z0) + z0*z0*z0 + c0;
          n_Iter++;
        }
        imgOriginal.SetPixel(i,j,m_ColorTable[n_Iter%16]);
        c0 -= Complex(0,f_Dy);
      }
      c0 = Complex(c0.GetRe(),m_TopBound);
      c0 += f_Dx;
      }
    }

    В данной функции используются методы класса CImage: Destroy() - для удаления растрового изображения из объекта, Create(int nWidth, int nHeight, int nBPP) - для создания растрового изображения с размерами nWidth и nHeight и глубиной цвета nBPP, SetPixel(int x, int y, COLORREF color) - для закрашивания одного пикселя изображения. Также используется класс Complex, его полный исходный код приведен в приложении. Чтобы его использовать, нужно добавить в программу с помощью утилиты Solution Explorer файлы Complex.h и Complex.cpp в проект и добавить в файл ChildView.cpp следующую строку:

    #include "Complex.h"

    Построение и вывод изображения на экран будем производить по двойному клику левой кнопкой мыши. Обработчик OnLButtonDblClick (файл CChildView.cpp):

    void CChildView::OnLButtonDblClk(UINT nFlags, CPoint point)
    {
      BuildFractal();
      Invalidate();
      CWnd::OnLButtonDblClk(nFlags, point);
    }

    Обработчик OnPaint (файл CChildView.cpp):

    void CChildView::OnPaint() 
    {
      CPaintDC dc(this); // device context for painting
      if(!imgOriginal.IsNull())
        imgOriginal.StretchBlt(dc,0,0,imgOriginal.GetWidth(),imgOriginal.GetHeight(),SRCCOPY);
    }

    Функция StretchBlt копирует изображение объекта класса CImage в контекст устройства, переданный ей в качестве параметра. Чтобы сохранить полученное изображение, обработаем пункт меню Save. Обработчик OnFileSave:

    void CChildView::OnFileSave()
    {
      CString strFilter;
      HRESULT hResult;
    //Строка с поддерживаемыми расширениями
      strFilter = "Bitmap image|*.bmp|JPEG image|*.jpg|GIF image|*.gif|PNG image|*.png||";
    //Создание стандартного диалога сохранения
      CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter);
      hResult = (int)dlg.DoModal();  //Вызов диалога сохранения
      if (FAILED(hResult)) return;
      //Создание строки с путем и полным именем файла, в который будет производиться сохранение
    CString strFileName;
      CString strExtension;
      strFileName = dlg.m_ofn.lpstrFile;
      if (dlg.m_ofn.nFileExtension == 0) 
      {
      switch (dlg.m_ofn.nFilterIndex)
      {
        case 1 : strExtension = "bmp"; break;
        case 2 : strExtension = "jpg"; break;
        case 3 : strExtension = "gif"; break;
        case 4 : strExtension = "png"; break;
        default : break;
      }
      strFileName = strFileName + '.' + strExtension;
    }
    //Сохранение
      hResult = imgOriginal.Save(strFileName);
      if (FAILED(hResult)) 
      {
        CString fmt;
        fmt.Format("Save image failed:\n%x", hResult);
        ::AfxMessageBox(fmt);
        return;
      }
    }

    Для сохранения изображения используется метод класса CImage: Save, которому необходимо передать строку с путем и именем файла. Для использования класса CImage и математических формул, необходимо добавить в файл stdafx.h строки:

    #include <atlimage.h>
    #include <cmath>

    Программа готова. Скомпилируем и запустим. В результате должно получиться следующее изображение:рис 4.1

    (рис 4.1) Изображение после компиляции программы
    Вернуться к учебному плану