C++В основе приложений MFC лежит концепция document object/view window (Документ/Вид). Обычно документ представляет собой отдельный файл, который приложение способно открыть. В нем хранятся данные, управляемые приложением. Отображение данных и интерфейс работы с ними обеспечивает представление. Представление способно отображать данные документа частично и в разной форме, выполняя заданный срез документа.
Отношение между документом и представлением строится по принципу "один ко многим". То есть один документ (одно содержание) может иметь несколько представлений (несколько форм), но каждое из этих представлений отображает одни и те же данные.
Программно такая концепция реализуется механизмом - однодокументный интерфейс. Управление документом осуществляется объектом на основе производного класса от базового класса CDocument, а управление интерфейсами - на основе базового класса CView.
Основой для CFrameWnd. Фреймовое окно является главным окном приложения и играет роль родительского по отношению к содержащимся в нем окнам. Неотъемлемой частью фреймового окна является его заголовок и меню. Клиентскую область фреймового окна занимают всевозможные дочерние окна, в том числе окно панели инструментов, окно строки состояния, окно представления и др.

В приложениях , созданных мастером MFC Application Wizard, исходный код реализации фреймового окна (реализация класса CMainFrame ) размещается в файлах проекта MainFrm.h и MainFrm.cpp. Класс CMainFrame наследует практически все свои функциональные возможности от базового класса CFrameWnd.
В свою очередь, фреймовым окном управляет каркас приложения, порожденный классом CWinApp. Каркас приложения не имеет визуального представления, его задача - обеспечить взаимодействие окна-рамки и дочерних фреймовых окон. Это взаимодействие осуществляется с помощью сообщений. Модель отношений между базовыми классами приложения приведена на рисунке

Класс CDocument обеспечивает стандартное поведение объектов документа MFC -приложения. Независимо от того, создается однодокументное приложение или многодокументное, мастер AppWizard создает только один класс документа, производный от базового класса CDocument.
В библиотеке MFC существует несколько классов массивов для хранения различных типов данных. У всех этих классов одинаковые функциональные возможности и работают они по одному и тому же принципу. Основное различие - это тип хранимых в них данных.
Классы массивов MFC | |
|---|---|
| Класс | Описание |
CArray |
Шаблонный класс для создания пользовательских классов массивов |
CByteArray |
Массив с данными типа байт |
CDWordArray |
Массив с данными типа DWORD (двойное слово) |
CObArray |
Массив может содержать классы, производные от класса CObject |
CPtArray |
Массив указателей на любые объекты и типы данных |
CStringArray |
Массив объектов типа |
CUIntArray |
Массив данных типа UINT (unsigned int) |
CWordArray |
Массив данных типа WORD (слово) |
Методы классов массивов MFC | |
|---|---|
| Класс | Описание |
array[int index] |
Оператор ображения к значению элемента массива по его индексу. |
array.GetAt(int index) |
Возвращает элемент массива по его индексу. |
array.SetAt(int index, element) |
Записывает элемент массива по его индексу. Тип данных записываемого элемента должен соответствовать типу данных всего массива. Вызов функции не приводит к росту количества элементов массива. |
array.InsertAt(int index, element) |
Вставляет новый элемент перед элементом index. Размерность массива увеличивается на единицу. |
array.Add(element) |
Добавляет новый элемент в конец массива. Добавляемый элемент должен иметь тип данных всего массива. |
array.RemoveAt(int index, int count=1) |
Удаляет count элементов, начиная с позиции index. Если второй аргумент не указан, то удаляется один элемент. |
array.RemoveAll() |
Удаляет все элементы массива и присваивает свойству количества элементов массива значение ноль. |
В массивах классов CObArray и CPtArray элементы как объекты не удалаются. Из массивов удаляются только указатели на объекты. При использовании функций RemoveAt() и RemoveAll() необходимо сначала удалить сами объекты, а затем удалить из массива их указатели. |
|
array.GetSize() |
Возвращает количество элементов массива. |
array.GetCount() |
Возвращает количество элементов массива. |
array.GetUpperBound() |
Возвращает индекс последнего элемента массива. |
array.SetSize(int iNewSize, iGrowBy) |
Управляет размером массива. |
array.Copy(source) |
Копирует source в array |
array.Append(source) |
Присоединяет source в конец array |
В случае с массивами классов CObArray и CPtArray функции Copy() и Append() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным. |
|
Создадим графическое приложение на основе однодокументного интерфейса, в котором можно будет рисовать, и сохранять нарисованное в файле с последующим восстановлением.
Войдите в свой каталог выполнения лабораторных работ
Создайте новый проект MFC Application и назовите его DrawSDI
Настройте мастер приложений
Application Type Application Type:Single document Document/View architecture support Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library Compound Document Support Compound document support:NoneDocument Template Strings File extension :dhcDatabase Support Database support :NoneUser Interface Features Main frame styles:Minimize box Maximize box System menu Initial status bar Toolbars:Standard docking Advanced Features Generated Classes Base class :CViewПосле настройки вкладки мастера должны иметь такой вид






Создадим свой класс, который будет обеспечивать поддержку хранения координат отрезка прямой линии и отображения ее в окне представления документа.
В панели Class View найдите верхний узел с названием DrawSDI и для него выполните опцию Add Class контекстного меню
В диалоговом окне
Появившееся окно мастера добавления класса заполните так
Щелкните по кнопке Finish
Мастер создаст заготовки для объявления и определения класса и разместит их в отдельных файлах MyLine.h и MyLine.cpp
Отредактируйте эти заготовки так, чтобы класс мог хранить координаты начальной и конечной точек линии. Для этого воспользуйтесь мастером добавления переменных, предварительно выделяя в панели Class View класс MyLine. Переменные определите с модификатором доступа private
Для того, чтобы класс мог нарисовать линию между двумя точками, добавьте в него с помощью мастера Не забудьте нажать кнопку Add перед нажатием кнопки Finish
В итоге должно получиться следующее
Файл MyLine.h
#pragma once
#include "afx.h"
#include "atltypes.h"
class MyLine :
public CObject
{
public:
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
};
Файл MyLine.cpp
#include "StdAfx.h"
#include ".\myline.h"
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
}
MyLine::~MyLine(void)
{
}
// Функция-член класса MyLine для рисования линий
void MyLine::Draw(CDC* pDC)
{
// Нарисовать линию
pDC->MoveTo(m_pointBegin);
pDC->LineTo(m_pointEnd);
}
Экземпляры объекта MyLine можно сохранять в объекте документа в простом динамическом массиве, реализуемом в MFC -библиотеке классом CObArray. Объект этого класса массива изменяет свой размер динамически в зависимости от количества находящихся в нем элементов. Этот класс-массив может содержать в себе любой объект, являющийся наследником класса CObject, размер массива ограничивается только количеством свободной памяти в компьютере.
Создадим в классе CDrawSDIDoc приложения переменную-экземпляр класса CObArray для хранения линий-экземпляров созданного класса MyLine. Назовем ее m_Lines. Для этого:
В панели Class View выделите класс CDrawSDIDoc, через опцию Add Variable вызовите мастер и заполните его так

Таким же образом добавьте в класс CDrawSDIDoc функцию-член AddLine() для добавления объектов линий в массив, идентифицируемый только что созданной переменной. Мастер заполните так

Мастер внесет следующие добавления в класс CDrawSDIDoc
Файл DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
..............................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
};
Файл DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
return NULL;
}
Отредактируйте заготовку функции AddLine() так
Файл DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
MyLine* pLine = NULL; // Создали указатель на стеке
try // Попытка
{
// Создать экземпляр объекта-линии на куче
pLine = new MyLine(pointBegin, pointEnd); // Конструктор
// Добавить указатель на новую линию в массив объектов
m_Lines.Add(pLine);
// Отметить документ как измененный
SetModifiedFlag();
}
catch(CMemoryException* pErr) // Ловушка на недостаток памяти
{
// Сообщили пользователю
AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
// Уничтожим объект-линию, на котором сгенерировалось исключение
if(pLine){
delete pLine;
pLine = NULL;
}
// Удалить объект с исключением
pErr->Delete();
}
return pLine; // Вернуть указатель на объект-линию
}
Подключите объявление класса MyLine к классу CDrawSDIDoc вначале файла DrawSDIDoc.h так
Файл DrawSDIDoc.h
// DrawSDIDoc.h : interface of the CDrawSDIDoc class
//
#pragma once
#include "afxcoll.h"
#include "MyLine.h"
class CDrawSDIDoc : public CDocument
{
...........................................
};
Добавим в класс CDrawSDIDoc еще две публичные функции-члена, играющих роль функций доступа к закрытой переменной m_Lines:
GetLineCount() - возвращает количество объектов-линий, хранящихся в массиве m_Lines класса CObArrayGetLine() - возвращает указатель на линию, хранящийся в массиве m_Lines, по индексу массиваОбе функции доступа будут нужны в классе представления CDrawSDIView для отображения линий, хранящихся как закрытые в классе документа CDrawSDIDoc.
Заполните мастера добавления функций так


Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Добавления, выполненные мастерами, должны быть такими
Добавления в файле DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
..........................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
// Количество нарисованных линий
int GetLineCount(void);
// Возвращает указатель на объект-линию по индексу
MyLine* GetLine(int index);
};
Добавления в файле DrawSDIDoc.cpp
// Количество нарисованных линий
int CDrawSDIDoc::GetLineCount(void)
{
return 0;
}
// Возвращает указатель на объект-линию по индексу
MyLine* CDrawSDIDoc::GetLine(int index)
{
return NULL;
}
Заполните код функций так
Код функций доступа в файле DrawSDIDoc.cpp
// Количество нарисованных линий
int CDrawSDIDoc::GetLineCount(void)
{
// Возвратить количество элементов в массиве
return (int)m_Lines.GetCount();
}
// Возвращает указатель на объект-линию по индексу
MyLine* CDrawSDIDoc::GetLine(int index)
{
// Возвратить указатель на объект-линию, хранящийся
// в указанной позиции в массиве CObArray m_Lines
return (MyLine*)m_Lines[index];
}
Постройте приложение, чтобы своевременно выловить возможные ошибкиТаким образом, мы выполнили подготовительные мероприятия, позволяющие классу документа сохранять и выдавать информацию о координатах линий. Теперь необходимо реконструировать заготовку класса представления для манипулирования с данными класса документа и представления их в окне приложения. Для этого
Введите частную переменную m_pointPrevPos в класс CDrawSDIView для хранения предыдущей точки рисования

В панели Class View выберите класс CDrawSDIView
Используя вкладку Properties в режиме Messages, добавьте к классу CDrawSDIView функции для обработки сообщений операционной системы
WM_LBUTTONDOWN - для начала рисования и захвата мышиWM_LBUTTONUP - для завершения рисования и освобождения мышиWM_MOUSEMOVE - для выполнения рисования и запоминания координат линий в классе документаДобавление переменной и обработчиков в класс CDrawSDIView
class CDrawSDIView : public CView
{
...................................................
private:
// Предыдущая точка рисования
CPoint m_pointPrevPos;
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
};
Регистрация обработчиков в карте сообщений DrawSDIView.cpp BEGIN_MESSAGE_MAP(CDrawSDIView, CView) // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() END_MESSAGE_MAP()
Отредактируйте функции-обработчики, как показано ниже
Код функций-обработчиков в файле DrawSDIView.cpp
void CDrawSDIView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Захватим мышь, чтобы другое приложение не могло
// перезахватить мышь в момент, когда она
// покинет пределы окна приложения
SetCapture(); // мышь только для нас!!!
// Сохраним точку начала рисования
m_pointPrevPos = point;
CView::OnLButtonDown(nFlags, point);
}
void CDrawSDIView::OnLButtonUp(UINT nFlags, CPoint point)
{
// Если мышь была ранее захвачена нашим приложением,
// то отдадим ее, чтобы другие приложения
// тоже могли использовать мышь
if(GetCapture() == this)
ReleaseCapture();
CView::OnLButtonUp(nFlags, point);
}
void CDrawSDIView::OnMouseMove(UINT nFlags, CPoint point)
{
// Если нажата левая кнопка мыши, то рисовать
if((nFlags MK_LBUTTON) == MK_LBUTTON){
// Если мышь захвачена нами, то рисовать
if(GetCapture() == this){
// Получить контекст устройства
// клиентской области окна приложения
CClientDC dc(this);
// Добавить линию к документу и вернуть на нее
// указатель как на объект, чтобы воспользоваться
// ее функцией-членом для рисования
MyLine* pLine =
GetDocument()->AddLine(m_pointPrevPos, point);
// Нарисовать текущую линию
pLine->Draw(dc);
// Сохранить текущую точку в качестве предыдущей
m_pointPrevPos = point;
} // end of if
} // end of if
CView::OnMouseMove(nFlags, point);
}
Постройте приложение, порисуйте и поперемещайте клиента заграницы экрана
Видно, что рисунок не восстанавливается, хотя все линии у нас сохранены в классе документа. Функция OnDraw() класса представления срабатывает всякий раз даже при частичном разрушении окна представления или когда данные документа были отмечены как измененные с помощью функции SetModifiedFlag().
Добавьте в функцию OnDraw() код прорисовки всех сохраненных линий (не забудьте раскомментировать аргумент указателя в заголовке функции), как показано в листинге
Код функции OnDraw() в файле DrawSDIView.cpp
void CDrawSDIView::OnDraw(CDC* pDC)
{
CDrawSDIDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
// Получить количество линий в документе
int count = pDoc->GetLineCount();
// Если есть линии, то нарисовать их все
if(count){
for(int i = 0; i < count; i++){
MyLine* pLine = pDoc->GetLine(i);
pLine->Draw(pDC);
}
}
}
Постройте приложение и убедитесь, что теперь все восстанавливается после разрушения окна приложенияКогда в классе документа CDrawSDIDoc срабатывает функция OnNewDocument(), то данные в массиве m_Lines необходимо уничтожить и освободить занимаемую ими память. Это ни в коем случае нельзя делать в функции OnNewDocument(), (хотя принципиально это и можно сделать), потому, что по логике своего предназначения эта функция должна инициализировать данные нового документа.
В данной ситуации для удаления объектов линий воспользуемся обработчиком сообщения операционной системы о событии DeleteContents(). Переопределим этот обработчик, выполнив следующие действия:
В панели Class View выберите класс CDrawSDIDoc
Во вкладке Properties включите режим Overrides (Переопределение)
Найдите виртуальную функцию DeleteContents класса CDocument и создайте переопределение этой функции

Заполните переопределенную функцию DeleteContents() так
Код переопределения виртуальной функции DeleteContents() в файле DrawSDIDoc.cpp
void CDrawSDIDoc::DeleteContents()
{
// Получить количество линий в массиве объектов
int count = (int)m_Lines.GetCount();
// Попытка выполнить удаление объектов
try
{
if(count){ // есть что удалять
// Освободить память на куче по каждому объекту
for(int i = 0; i < count; i++)
delete (MyLine*)m_Lines.GetAt(i);
// Установить массив в исходное состояние
m_Lines.RemoveAll();
}
}
// Ловушка исключения о нарушении памяти
catch(CMemoryException* pErr)
{
// Отобразить сообщение о причине исключения
pErr->ReportError();
// Удалить объект, вызвавший исключение
pErr->Delete();
}
CDocument::DeleteContents();
}
Добавим возможность сохранения документа. Воспользуемся функцией сериализации Serialize() класса CDrawSDIDoc, которую перепишем так, чтобы она только передавала сообщение о преобразовании в последовательную форму объекту-массиву m_Lines. В самом же объекте-массиве m_Lines предусмотрим свою функцию сериализации, в которой и осуществим сохранение документа с использованием функциональных возможностей C++.
В классе CDrawSDIDoc найдите функцию Serialize() преобразования в последовательную форму
Удалите все содержимое функции и перепишите ее так
Функция сериализации класса CDrawSDIDoc
void CDrawSDIDoc::Serialize(CArchive ar)
{
// Передать объекту-массиву сообщение
// о преобразовании в последовательную форму
m_Lines.Serialize(ar);
}
Добавьте новую функцию-член к классу MyLine, выделив класс MyLine в панели Class View и заполнив мастер

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Отредактируйте функцию так
Функция сериализации класса MyLine
// Преобразование в последовательную форму объекта-массива
void MyLine::Serialize(CArchive ar)
{
CObject::Serialize(ar);
if(ar.IsStoring())
ar << m_pointBegin << m_pointEnd; // Сохранить
else
ar >> m_pointBegin >> m_pointEnd; // Восстановить
}
Постройте приложение и попробуйте сохранить документ на диск. Система выдаст сообщение, что невозможно сохранить документ

Исправим это...
Зарегистрируем класс MyLine, как способный самостоятельно выполнять сериализацию. Для этого:
В начало объявления класса MyLine в файле MyLine.h и перед конструктором класса в файле MyLine.cpp добавьте по строке регистрации и конструктор по умолчанию
Конструктор по умолчанию теперь нужно определить самим, поскольку определен общий конструктор, который делает невозможным вызов автоматического конструктора по умолчанию.
Добавление в объявление класса MyLine в файле MyLine.h
class MyLine : public CObject
{
public:
DECLARE_SERIAL(MyLine);
MyLine(){}; // Констуктор по умолчанию
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
private:
// Массив линий
CObArray m_Lines;
public:
// Преобразование в последовательную форму объекта-массива
void Serialize(CArchive ar);
};
Добавление вначало файла MyLine.cpp
#include "StdAfx.h"
#include ".\myline.h"
IMPLEMENT_SERIAL(MyLine, CObject, 1)
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
}
MyLine::~MyLine(void)
{
}
....................................................
Постройте приложение и убедитесь, что документы сохраняются на диске и загружаются с диска. Вот один из восстановленных шедевров

Добавим в приложение возможность изменять цвет линий и их толщину. Для этого изменим меню, сгенерированное мастером, а также внесем необходимые изменения в уже имеющийся код.
Добавьте переменные в класс MyLine и функцию доступа
Добавления в файл MyLine.h
class MyLine : public CObject
{
public:
DECLARE_SERIAL(MyLine);
MyLine(){}
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
private:
// Массив линий
CObArray m_Lines;
public:
// Преобразование в последовательную форму объекта-массива
void Serialize(CArchive ar);
private:
// Для хранения цвета
COLORREF color;
// Для хранения толщины линий
int widthLine;
public:
// Функция доступа
void Set(COLORREF c, int w)
{
color = c;
widthLine = w;
}
};
Инициализируйте переменные в общем конструктореДобавления в файл MyLine.cpp
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
color = RGB(0, 0, 0);
widthLine = 1;
}
Дополните функцию рисования Draw класса MyLine
Добавления в файл MyLine.cpp
// Функция-член класса MyLine для рисования линий
void MyLine::Draw(CDC* pDC)
{
// Создать перо
CPen currPen(PS_SOLID, widthLine, color);// Конструктор пера
// Установить новое перо в качестве рисующего объекта
CPen* pOldPen = pDC->SelectObject(currPen);
// Нарисовать линию
pDC->MoveTo(m_pointBegin);
pDC->LineTo(m_pointEnd);
// Восстановить прежнее перо
pDC->SelectObject(pOldPen);
}
Измените функцию MyLine::Serialize()
Добавления в файл MyLine.cpp
// Преобразование в последовательную форму объекта-массива
void MyLine::Serialize(CArchive ar)
{
CObject::Serialize(ar);
if(ar.IsStoring()) // Сохранить
ar << m_pointBegin << m_pointEnd
<< (DWORD)color << widthLine;
else // Восстановить
ar >> m_pointBegin >> m_pointEnd
>> (DWORD)color >> widthLine;
}
Постройте приложение и проверьте - пока ничего измениться не должноДобавьте в класс документа CDrawSDIDoc массив таблицы цветов, индекс массива таблицы цветов и толщину линии. Код приведен ниже
Добавления в файл DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
................................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
// Количество нарисованных линий
int GetLineCount(void);
// Возвращает указатель на объект-линию по индексу
MyLine* GetLine(int index);
virtual void DeleteContents();
private:
int m_iColor; // индекс массива таблицы цветов
int m_iWidth; // толщина линии
public:
// Объявление таблицы цветов
static const COLORREF m_Colors[8];
};
Добавьте инициализацию переменных m_iColor и m_iWidth в функции OnNewDocument()
Добавление в функцию OnNewDocument() файла DrawSDIDoc.cpp
BOOL CDrawSDIDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
// Инициализировать начальный цвет черным
// Толщину линии 1
m_iColor = 0;
m_iWidth = 1;
return TRUE;
}
Добавьте в файл DrawSDIDoc.cpp таблицу цветов сразу после таблицы сообщений
Добавления таблицы цветов в файл DrawSDIDoc.cpp
BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
END_MESSAGE_MAP()
const COLORREF CDrawSDIDoc::m_Colors[] = {
RGB(0, 0, 0), // Black - черный
RGB(0, 0, 255), // Blue - синий
RGB(0, 255, 0), // Green - зеленый
RGB(0, 255, 255), // Cyan - голубой
RGB(255, 0, 0), // Red - красный
RGB(255, 0, 255), // Magenta - сиреневый
RGB(255, 255, 0), // Yellow - желтый
RGB(255, 255, 255) // White - белый
};
Измените функцию AddLine() класса CDrawSDIDoc так, чтобы перед сохранением объекта MyLine в него добавлялась информация о цвете и толщине линии
Добавления в функцию AddLine() файла DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
MyLine* pLine = NULL; // Создали указатель на стеке
try // Попытка
{
// Создать экземпляр объекта-линии на куче
pLine = new MyLine(pointBegin, pointEnd); // Конструктор
// Добавить информацию о текущем цвете и толщине линии
pLine->Set(m_Colors[m_iColor], m_iWidth);
// Добавить указатель на новую линию в массив объектов
m_Lines.Add(pLine);
// Отметить документ как измененный
SetModifiedFlag();
}
catch(CMemoryException* pErr) // Ловушка на недостаток памяти
{
// Сообщили пользователю
AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
// Уничтожим объект-линию, на котором сгенерировалось исключение
if(pLine){
delete pLine;
pLine = NULL;
}
// Удалить объект с исключением
pErr->Delete();
}
return pLine; // Вернуть указатель на объект-линию
}
Чтобы полностью завершить решаемую задачу, введем возможность установки цвета и толщины линии в меню. Для этого необходимо
Через вкладку Resource View вызовите ресурс меню на редактирование
Отредактируйте ресурс меню в соответствии с рисунками, которые получены для режима Edit IDs оболочки

Мастер создал Solution Explorer необходимо файл приложения Resource.h. Файл должен содержать, примерно, следующий код
Файл приложения Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by DrawSDI.rc
//
#define IDD_ABOUTBOX 100
#define IDP_OLE_INIT_FAILED 100
#define IDR_MAINFRAME 128
#define IDR_DrawSDITYPE 129
#define ID_COLOR_BLACK 32771
#define ID_COLOR_BLUE 32772
#define ID_COLOR_GREEN 32773
#define ID_COLOR_CYAN 32774
#define ID_COLOR_RED 32775
#define ID_COLOR_MAGENTA 32776
#define ID_COLOR_YELLOW 32777
#define ID_COLOR_WHITE 32778
#define ID_WIDTH_WIDTH 32779
#define ID_WIDTH_1 32780
#define ID_WIDTH_2 32781
#define ID_WIDTH_3 32782
#define ID_WIDTH_4 32783
#define ID_WIDTH_5 32784
#define ID_WIDTH_6 32785
#define ID_WIDTH_7 32786
#define ID_WIDTH_8 32787
...............................................
Сообщения о нажатиях пунктов меню должен принимать и обрабатывать класс документа. Поэтому именно для него мы добавим соответствующие обработчики. Обработчики сообщений о выборе пунктов меню можно добавить через оболочку двумя способами:
Первым способом является вызов в редакторе меню через команду Add контекстного меню соответствующего пункта

В появившемся диалоговом окне мастера добавления обработчика нужно выбрать класс, который будет обрабатывать сообщение, а также нужно выбрать тип (из двух) сообщения.
Вторым способом является выделение в панели Class View нужного класса, который будет принимать и обрабатывать сообщения от пунктов меню. Затем в панели Properties необходимо установить режим Events и для соответствующих идентификаторов пунктов меню создать обработчики, раскрыв узлы дерева событий

Первый тип сообщения ( COMMAND ) Windows генерирует каждый раз, когда пользователь выбирает соответствующий пункт меню. При этом сообщение передается со значением ID идентификатора пункта в качестве параметра сообщения. Сообщение типа UPDATE_COMMAND_UI не является чистым сообщением Windows, это запрос MFC на обновление внешнего вида отдельных пунктов меню перед их показом пользователю. Мы создадим обработчики для обеих сообщений каждого пункта, добавленного нами в меню.
Откройте вкладку Class View и выберите класс CDrawSDIDoc
В панели Properties установите режим Events и найдите идентификатор ID_COLOR_BLACK
Раскройте узел событий для пункта меню как объекта и для каждого из сообщений COMMAND и UPDATE_COMMAND_UI создайте обработчики, которые заполните так
Обработчики меню для выбора цвета Black
void CDrawSDIDoc::OnColorBlack()
{
m_iColor = 0;
}
void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
// Отобразить пункт меню (если TRUE) с флажком
pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
}
Аналогичным образом создайте обработчики для выбора всех других цветов
Обработчики меню для выбора остальных цветов
void CDrawSDIDoc::OnColorBlue()
{
m_iColor = 1;
}
void CDrawSDIDoc::OnUpdateColorBlue(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iColor == 1 ? TRUE : FALSE);
}
.........................................................
void CDrawSDIDoc::OnColorWhite()
{
m_iColor = 7;
}
void CDrawSDIDoc::OnUpdateColorWhite(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iColor == 7 ? TRUE : FALSE);
}
Подобным образом создайте обработчики для выбора через меню толщины линий рисования
Обработчики меню для выбора толщины линий
void CDrawSDIDoc::OnWidth1()
{
m_iWidth = 1;
}
void CDrawSDIDoc::OnUpdateWidth1(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iWidth == 1 ? TRUE : FALSE);
}
.....................................................
void CDrawSDIDoc::OnWidth8()
{
m_iWidth = 8;
}
void CDrawSDIDoc::OnUpdateWidth8(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iWidth == 8 ? TRUE : FALSE);
}
Мы видим, что количество обработчиков пунктов меню очень большое. Имеется возможность скомбинировать обработчики меню каждой категории, существенно уменьшив количество обработчиков и сделав код более изящным. Для этого применяют групповые сообщения Windows с параметрами.
Если посмотреть карту сообщений в файле DrawSDIDoc.cpp, то можно увидеть, что обработчики и идентификаторы пунктов меню связаны так
Карта сообщений в файле CDrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND(ID_COLOR_BLACK, OnColorBlack) ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack) ON_COMMAND(ID_COLOR_BLUE, OnColorBlue) ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue) ON_COMMAND(ID_COLOR_GREEN, OnColorGreen) ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen) ON_COMMAND(ID_COLOR_CYAN, OnColorCyan) ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan) ON_COMMAND(ID_COLOR_RED, OnColorRed) ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed) ON_COMMAND(ID_COLOR_MAGENTA, OnColorMagenta) ON_UPDATE_COMMAND_UI(ID_COLOR_MAGENTA, OnUpdateColorMagenta) ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow) ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow) ON_COMMAND(ID_COLOR_WHITE, OnColorWhite) ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite) ON_COMMAND(ID_WIDTH_1, OnWidth1) ON_UPDATE_COMMAND_UI(ID_WIDTH_1, OnUpdateWidth1) ON_COMMAND(ID_WIDTH_2, OnWidth2) ON_UPDATE_COMMAND_UI(ID_WIDTH_2, OnUpdateWidth2) ON_COMMAND(ID_WIDTH_3, OnWidth3) ON_UPDATE_COMMAND_UI(ID_WIDTH_3, OnUpdateWidth3) ON_COMMAND(ID_WIDTH_4, OnWidth4) ON_UPDATE_COMMAND_UI(ID_WIDTH_4, OnUpdateWidth4) ON_COMMAND(ID_WIDTH_5, OnWidth5) ON_UPDATE_COMMAND_UI(ID_WIDTH_5, OnUpdateWidth5) ON_COMMAND(ID_WIDTH_6, OnWidth6) ON_UPDATE_COMMAND_UI(ID_WIDTH_6, OnUpdateWidth6) ON_COMMAND(ID_WIDTH_7, OnWidth7) ON_UPDATE_COMMAND_UI(ID_WIDTH_7, OnUpdateWidth7) ON_COMMAND(ID_WIDTH_8, OnWidth8) ON_UPDATE_COMMAND_UI(ID_WIDTH_8, OnUpdateWidth8) END_MESSAGE_MAP()
Обратите внимание, что каждое Add , имеет в заголовке только два параметра. Но существуют
Возможная таблица сообщений с диапазоном в файле CDrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnUpdateWidthUI) END_MESSAGE_MAP()
Написанные нами ранее в приложении обработчики, на примере выбора одного цвета, имеют вид
Обработчики сообщений файла CDrawSDIDoc.cpp
void CDrawSDIDoc::OnColorBlack()
{
m_iColor = 0;
}
void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
// Отобразить пункт меню (если TRUE) с флажком
pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
}
....................................................
Мастер при создании первого обработчика типа OnColorBlack() не предусмотрел в нем передачу аргументов, однако этот обработчик может принимать аргумент в виде значения идентификатора нажатого пункта меню. Во второй обработчик типа OnUpdateColorBlack(CCmdUI *pCmdUI) в качестве аргумента передается указатель на объект, в свойстве pCmdUI->m_nID которого содержится значение идентификатора соответствующего пункта меню. Учитывая это, мы можем создать комбинированные обработчики для каждой категории меню.
Вначале расширим категорию меню Width добавлением новых элементов, позволяющих задавать существенную толщину линий.
Дополнительные пункты меню категории Width | |
|---|---|
| Название пункта | Толщина пера |
Medium (Средняя) |
16 |
(Толстое) |
24 |
Very (Очень толстое) |
32 |
Через панель Resource View вызовите редактор меню
Добавьте в конец категории Width три новых пункта так, чтобы вначале названия пунктов были введены цифрами 16, 24, 32. Это нужно для того, чтобы редактор автоматически сформировал имена идентификаторов похожими на предыдущие пункты
Измените названия уже сгенерированных пунктов на слова, приведенные в таблице вышеДолжно получиться что-то подобное в режиме Edit IDs редактора меню
Теперь займемся сокращением количества обработчиков пунктов меню. Вспомним, что интересующие нас обработчики прописаны в трех местах приложения:
CDrawSDIDoc в файле DrawSDIDoc.hDrawSDIDoc.cppCDrawSDIDoc в начале файла DrawSDIDoc.cppДля того, чтобы нам не организовывать в комбинированном обработчике условия проверки значений передаваемых идентификаторов, необходимо учесть, чтобы значения идентификаторов в каждой из категорий шли в порядке возрастания с шагом единица. Для этого:
Откройте через панель Solution Explorer файл Resource.h
Он должен иметь примерно такой вид
Идетификаторы пунктов меню в файле Resource.h ............................................... #define ID_COLOR_BLACK 32771 #define ID_COLOR_BLUE 32772 #define ID_COLOR_GREEN 32773 #define ID_COLOR_CYAN 32774 #define ID_COLOR_RED 32775 #define ID_COLOR_MAGENTA 32776 #define ID_COLOR_YELLOW 32777 #define ID_COLOR_WHITE 32778 #define ID_WIDTH_WIDTH 32779 #define ID_WIDTH_1 32780 #define ID_WIDTH_2 32781 #define ID_WIDTH_3 32782 #define ID_WIDTH_4 32783 #define ID_WIDTH_5 32784 #define ID_WIDTH_6 32785 #define ID_WIDTH_7 32786 #define ID_WIDTH_8 32787 #define ID_WIDTH_16 32788 #define ID_WIDTH_24 32789 #define ID_WIDTH_32 32790 ...............................................
В файле DrawSDIDoc.h уберите прототипы всех созданных ранее обработчиков для категорий меню Color и Width и введите прототипы комбинированных обработчиков отдельно для каждой категории в конец класса
Код должен выглядеть так
Прототипы комбинированных обработчиков в файле DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
................................................
public:
// Объявление таблицы цветов
static const COLORREF m_Colors[8];
// Комбинированные обработчики Color и Width
afx_msg void OnColorCommand(UINT nID);
afx_msg void OnUpdateColorUI(CCmdUI *pCmdUI);
afx_msg void OnWidthCommand(UINT nID);
afx_msg void OnUpdateWidthUI(CCmdUI *pCmdUI);
};
В файле DrawSDIDoc.cpp карта сообщений после редактирования должна выглядеть так
Карта сообщений в файле DrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) END_MESSAGE_MAP()
Уберите в файле DrawSDIDoc.cpp код всех прежних обработчиков для категорий Color и Width и введите новый код обобщенных обработчиков так
Новый код обработчиков сообщений в файле DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Color
void CDrawSDIDoc::OnColorCommand(UINT nID)
{
m_iColor = nID - ID_COLOR_BLACK;
}
// Срабатывает для каждого пункта перед раскрытием меню Color
void CDrawSDIDoc::OnUpdateColorUI(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
bool check = pCmdUI->m_nID == m_iColor + ID_COLOR_BLACK;
// Какой цвет установлен, такой пункт
// меню и отобразить с флажком
pCmdUI->SetCheck(check ? TRUE : FALSE);
}
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
}
// Срабатывает для каждого пункта перед раскрытием меню Width
void CDrawSDIDoc::OnUpdateWidthUI(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
bool check;
int pos = 0;
if(m_iWidth <= 8)
pos = m_iWidth - 1; //0-7
else if(m_iWidth == 16)
pos = m_iWidth - 8; //8
else if(m_iWidth == 24)
pos = m_iWidth - 15;//9
else if(m_iWidth == 32)
pos = m_iWidth - 22;//10
// Какая толщина установлена, такой пункт
// меню и отобразить с флажком
check = pCmdUI->m_nID == pos + ID_WIDTH_1;
pCmdUI->SetCheck(check ? TRUE : FALSE);
}
Постройте приложение и проверьте его работоспособность
Оформите только About, но ни в коем случае не оформляйте строку заголовка окна и не русифицируйте ресурс String Table. Может смениться кодировка и придется тексты интерфейсов переколачивать заново вручнуюВ этой части мы наделим наше приложение еще большими возможностями, добавив панель инструментов и строку состояния.
Строкой состояния в и документах называется информационное окно, расположенное, как правило, в нижней части рамки приложения и отражающее поясняющий текст (длинные подсказки) о назначении пунктов меню или кнопок панели инструментов. Панелями инструментов называются окна, содержащие наборы кнопок быстрого доступа к функциональным возможностям приложения.
Панели инструментов, как и меню, делают контекстными к данному режиму приложения, т.е. они умеют к нему подстраиваться. Панели инструментов тесно связаны с меню, поскольку предназначены для тех же целей. Для удобства панели инструментов могут делать свободно плавающими ( floating ) или приклеивающимися ( ) к рамке окна приложения. Панели инструментов снабжают короткими всплывающими подсказками о назначении кнопок. Панели инструментов и строка состояния относятся к группе элементов графического интерфейса пользователя ( GUI ).
Откройте вкладку Resource View и через контекстное меню на узле Toolbar добавьте новый ресурс Toolbar, нажав кнопку New

Появится окно графического редактора с заготовкой панели инструментов

Одновременно в узле Toolbar вкладки Resource View приложения появится новый ресурс с автоматически сгенерированным идентификатором

Присвойте идентификатору новое имя IDR_TBCOLOR
Присвойте пиктограммы каждой кнопке, т. е. заполните восемь кнопок новой панели восемью доступными в графическом редакторе оболочки цветами
Чтобы не работать с карандашом, который включен по умолчанию, можно переключить инструмент рисования на кисть, предварительно вызвав через контекстное меню панели инструментов оболочки панель

Убедитесь, что ресурс новой панели русифицирован, чтобы можно было заполнять в него тексты на русском
Выделите по очереди каждую кнопку новой панели и через вкладку оболочки Properties задайте свойства кнопок в соответствии с таблицей
Свойство Prompt состоит из длинной (отображаемой в строке состояния) и короткой (всплывающей, когда пользователь задерживает курсор над кнопкой на время около 2 секунд) подсказок, разделенных управляющим символом "новая строка".
ID |
Prompt |
|---|---|
ID_COLOR_BLACK |
Цвет рисования черный \n Черный |
ID_COLOR_BLUE |
Цвет рисования синий \n Синий |
ID_COLOR_GREEN |
Цвет рисования зеленый \n Зеленый |
ID_COLOR_CYAN |
Цвет рисования бирюзовый \n Бирюзовый |
ID_COLOR_RED |
Цвет рисования красный \n Красный |
ID_COLOR_MAGENTA |
Цвет рисования сиреневый \n Сиреневый |
ID_COLOR_YELLOW |
Цвет рисования желтый \n Желтый |
ID_COLOR_WHITE |
Цвет рисования белый \n Белый |
Убедитесь, и это очень важно, что мы только что присвоили кнопкам панели инструментов IDR_TBCOLOR точно такие же идентификаторы, что и соответствующим пунктам меню категории ColorОни и должны совпадать, поскольку предназначены для одного и того же. А это значит, что обработчики команд специально для панели инструментов нам создавать не нужно - для одинаковых идентификаторов будут использованы обработчики команд меню.
По умолчанию размер кнопок принят 16x15 пикселов. Использование изображений, размеры которых отличаются от принятых по умолчанию, вполне возможно, но об этом необходимо уведомить объект панели инструментов при помощи функции CToolBar::SetSizes().
Закройте окно графического редактора, чтобы случайно не испортить подготовленную панель инструментовЛюбая панель инструментов хранится в виде растрового рисунка формата bmp в каталоге res проекта. Ее всегда можно открыть и отредактировать другим внешним редактором.
Выделите во вкладке В нашем случае файл имеет имя toolbar1.bmp. Открытый во внешнем редакторе он имеет вид
Пока мы только заготовили ресурс панели инструментов. Теперь подсоединим его к приложению. Поскольку панели инструментов присоединяются к рамке, необходимо начать добавление и изменение кода именно в модуле рамки. Рамкой нашего приложения управляет класс CMainFrame, являющийся производным от базового класса CFrameWnd и расположенный в файлах MainFrm.h и MainFrm.cpp.
У нас уже есть одна панель инструментов, автоматически сгенерированная мастером при создании заготовки приложения в самом начале выполнения лабораторной работы. В файле MainFrm.cpp можно увидеть, что она создается и прикрепляется к рамке окна приложения в функции OnCreate() - члене класса CMainFrame
Функция OnCreate() файла MainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
return 0;
}
В этой функции мы, подобным образом, создадим и прикрепим нашу новую панель. Но вначале для этого добавим в класс CMainFrame новую переменную типа CToolBar, которая и будет содержать новую панель. Эта переменная должна быть защищенной и назовем ее m_wndColorBar.
Щелкните правой кнопкой мыши на классе CMainFrame во вкладке Class View, вызовите мастер Add Variable, который заполните так

Мастер добавит в объявление класса следующий код
Объявление переменной-панели в классе CMainFrame файла MainFrm.h
class CMainFrame : public CFrameWnd
{
........................................................
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
// Панель инструментов для цвета
CToolBar m_wndColorBar;
};
Добавьте к функции OnCreate() класса CMainFrame следующий код
Добавления в функцию CMainFrame::OnCreate() файла MainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0){
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to be dockable
// Что делать: Удалите эти три строки, если инструментальную панель не нужно прикреплять
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
Постройте приложение и убедитесь, что панель ведет себя как нужно и переключает цвета точно также, как это делается через меню ColorЭто происходит потому, что идентификаторы ресурсов кнопок и пунктов меню одни и те же.
Класс панели инструментов CToolBar является лишь одним из нескольких классов, которые создают окна, пригодные для ввода команды и вывода информации. Базовым классом для всей этой группы элементов управления CToolBar, CStatusBar, CControlBar, CReBar, COleResizerBar, известных под общим названием панели управления, является класс CControlBar, производный от класса CWnd. Это очень важно, поскольку означает, что все панели инструментов как наследники класса CWnd, имеют все необходимые API -функции для работы с окнами.
Класс CToolBar является наследником класса CControlBar, который в свою очередь является наследником класса CWnd. Соответственно класс CToolBar имеет все необходимые функциональные возможности для успешного управления программным способом панелями инструментов. Рассмотрим некоторые методы, в том числе и унаследованные, класса CToolBar.
Некоторые методы класса CToolBar | |
|---|---|
| Метод | Пояснения |
CreateEx() |
Создает панель инструментов |
LoadToolBar() |
Загружает ресурс панели инструментов |
CommandToIndex() |
Возражает индекс кнопки панели инструментов |
GetButtonStyle() |
Возвращает целочисленное значение стиля кнопки по ее индексу |
SetButtonStyle() |
Устанавливает стиль кнопки по ее индексу |
GetItemID() |
Возвращает идентификатор ресурса кнопки по ее индексу |
GetItenRect() |
Возвращает координаты размещения и размер кнопки |
GetButtonText() |
Возвращает содержимое текстовой подсказки кнопки по ее индексу в формате |
SetButtonText() |
Устанавливает для кнопки с индексом ее текстовую подсказку |
GetBarStyle() |
Возвращает стиль (переменную типа DWORD ), установленный ранее для панели инструментов |
SetBarStyle() |
Устанавливает стиль для панели инструментов |
GetCount() |
Возвращает количество элементов, расположенных на панели инструментов |
IsFloating() |
Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов плавающей |
IsVisible() |
Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов видимой |
GetDockingFrame() |
Возвращает указатель на фреймовое окно, которое является родительским для панели инструментов |
EnableDocking() |
Посылает уведомление панели инструментов и фреймовому окну о том, что пользователям разрешено самостоятельно перемещать панель инструментов и располагать ее в других частях фреймового окна. |
ShowWindow() |
Как член класса CWnd управляет видимостью панели инструментов как окном. При этом фон исчезнувшей панели инструментов не удается скрыть |
ShowControlBar() |
Управляет видимостью панели инструментов более умело с сокрытием и фона. Применяется совместно с функцией RecalcLayout() |
RecalcLayout() |
Заново создает макет в пределах рамки окна, т. е. перерасполагает заново все панели инструментов, строки состояния и все остальное в пределах рамки окна. Кроме того, данная функция передвигает созданную по умолчанию панель инструментов вверх и вниз при переключении состояния панели скрыть/отобразить |
GetButtonInfo() |
Возвращает информацию о кнопке |
SetButtonInfo() |
Устанавливает идентификатор, стиль и индекс пиктограммы для кнопки панели инструментов |
GetToolBarCtrl() |
Возвращает ссылку на объект класса CToolBarCtrl |
LoadBitmap() |
Загружает пиктограмму для панели инструментов |
SetBitmap() |
Устанавливает писктограмму для новой панели инструментов |
SetButtons() |
Устанавливает идентификаторы для кнопок панели инструментов |
SetHeight() |
Устанавливает высоту панели инструментов |
SetSizes() |
Устанавливает размеры кнопок панели инструментов |
Добавленный нами ранее в функцию CMainFrame::OnCreate() фрагмент кода
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
содержит две функции-члена класса CToolBar
CreateEx() - создает панель инструментов с нужными свойствами в окне-рамке класса CMainFrame для экземпляра CToolBar m_wndColorBar CreateEx() CRect, задающего желаемую ширину и высоту панели инструментов. По умолчанию для всех измерений прямоугольника задается нуль, а это означает, что фактические размеры панели инструментов жестко не устанавливаютсяID ) дочернего окна панели инструментов. По умолчанию он равен константе AFX_IDW_TOOLBAR, но при необходимости можно указать любой необходимый идентификатор| Стили размещения кнопок относительно окна (второй параметр) | |
|---|---|
| Стиль | Описание |
TBSTYLE_ALTDRAG |
Позволяет перемещать панель инструментов (перетаскивать ее при нажатии кнопки Alt ) |
TBSTYLE_CHECK |
Задает режим поведения кнопок панели как флажков. Получаются кнопки с фиксацией, которые залипает при щелчке на них. Для возврата в исходное состояние необходимо щелкнуть повторно |
TBSTYLE_GROUP |
Кнопки панели рассматриваются как группа |
TBSTYLE_CUSTOMERASE |
Генерирует сообщение NM_CUSTOMDROW при стирании фона панели инструментов и кнопок, позволяя выбирать, когда и каким образом управлять процессом стирания фона |
TBSTYLE_FLAT |
Создает плоскую панель инструментов. Текст кнопки располагается под растровым изображением |
TBSTYLE_LIST |
Текст кнопки располагается справа от растрового изображения |
TBSTYLE_REGISTERDROP |
Используется при перетаскивании и опускании объектов на кнопки панели инструментов |
TBSTYLE_TOOLTIPS |
Создает всплывающую подсказку (элемент управления ToolTip ), которая может использоваться для отображения текста описания кнопок |
TBSTYLE_TRANSPARENT |
Создает прозрачную панель инструментов |
TBSTYLE_WRAPABLE |
Создает панель инструментов, которая может иметь несколько рядов кнопок |
| Стили самой панели инструментов как окна (третий параметр) | |
|---|---|
| Стиль | Описание |
WS_CHILD |
Панель инструментов создается как дочернее окно |
WS_VISIBLE |
Создается видимая сразу панель инструментов |
CBRS_ALIGN_TOP |
Позволяет прикреплять панель инструментов к верхней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_BOTTOM |
Позволяет прикреплять панель инструментов к нижней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_LEFT |
Позволяет прикреплять панель инструментов к левой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_RIGHT |
Позволяет прикреплять панель инструментов к правой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_ANY |
Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна |
CBRS_BORDER_TOP |
Если панель инструментов не прикреплена, помещает границу на верхний край панели инструментов |
CBRS_BORDER_BOTTOM |
Если панель инструментов не прикреплена, помещает границу на нижний край панели инструментов |
CBRS_BORDER_LEFT |
Если панель инструментов не прикреплена, помещает границу на левый край панели инструментов |
CBRS_BORDER_RIGHT |
Если панель инструментов не прикреплена, помещает границу на правый край панели инструментов |
CBRS_FLOAT_MULTI |
Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов |
CBRS_TOOLTIPS |
Отображает всплывающие подсказки ( ToolTips ) для кнопок панелей инструментов |
CBRS_FLYBY |
Одновременно обновляет текст сообщения строки состояния и текст всплывающей подсказки ToolTip |
CBRS_GRIPPER |
Рисует область захвата на панели инструментов при ее перемещении |
CBRS_SIZE_DYNAMIC |
Позволяет изменять размеры окна панели инструментов |
Рассмотрим еще один фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0)
{
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
Вначале применяется функция-член CommandToIndex(), которая возвращает индекс элемента управления, под которым он числится в панели инструментов как экземпляре класса CToolBar. Учитывая, что кнопка с таким идентификатором имеет наименьшее значение ID при графической разработки панели инструментов, в экземпляре класса она имеет наименьшее значение индекса (значение 0).
Индексы всех кнопок панели как элементов объекта m_wndColorBar, идут в возрастающем порядке, поскольку в таком же порядке идут значения их идентификаторов в ресурсе панели. Значит можно перебрать все элементы экземпляра класса по порядку в цикле и установить для каждого необходимый стиль. Стиль устанавливается второй функцией-членом SetButtonStyle(). В данном случае устанавливается стиль поведения радиокнопок в группе. Список возможных стилей кнопок приведен в таблице.
| Список возможных индивидуальных стилей кнопок панели инструментов | |
|---|---|
| Стиль | Описание |
TBBS_AUTOSIZE |
Вычисляет ширину кнопки в зависимости от текста кнопки |
TBBS_BUTTON |
Создает стандартную кнопку (без фиксации) |
TBBS_CHECKBOX |
Создает кнопку, которая действует подобно флажку. Такая кнопка переключается из состояния "выбрана" в состояние "отменить выбор" и наоборот. Иными словами, это кнопка с фиксацией, которая залипает при щелчке на ней. Для возврата в исходное состояние необходимо щелкнуть на ней еще раз |
TBBS_CHECKGROUP |
Создает кнопку, которая действует подобно радиокнопкам (селективным кнопкам) - остается выбранной до выбора другой кнопки в группе. Фактически это комбинация стилей TBSTYLE_CHECK и TBSTYLE_GROUP |
TBBS_DROPDOWN |
Создается кнопку, представляющую собой раскрывающийся список |
TBBS_GROUP |
Создает кнопку, которая остается выбранной, пока не будет выбрана другая кнопка из группы |
TBBS_NOPREFIX |
Не связывает префикс |
TBBS_SEPARATOR |
Создает разделитель (небольшой интервал) между кнопками с обеих сторон |
Рассмотрим следующий фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!! EnableDocking(CBRS_ALIGN_ANY); DockControlBar(m_wndToolBar); DockControlBar(m_wndColorBar);// Наша!!!
По умолчанию положение панели инструментов можно изменить только программно. Но можно разрешить пользователям самостоятельно перемещать панели инструментов и располагать их в других частях фреймового окна. О том, что такая возможность разрешена, необходимо уведомить как саму панель инструментов, так и фреймовое окно. Для этого и вызываются функции CToolBar::EnableDocking() и CFrameWnd::EnableDocking() (разрешить прикрепление).
Обязательно фрейму и панели инструментов нужно передавать одинаковые стили, иначе прикрепить панель инструментов к фреймовому окну не удастся.
Последняя функция DockControlBar(m_wndColorBar) кода - это функция фреймового окна, дающая команду изначально прикрепить панель инструментов, адрес на которую ей передается, к фрейму перед первым появлением окна на экране.
Параметры, передаваемые функциями EnableDocking() | |
|---|---|
| Стиль | Описание |
CBRS_ALIGN_TOP |
Прикрепляет панель инструментов к верхней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_BOTTOM |
Прикрепляет панель инструментов к нижней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_LEFT |
Прикрепляет панель инструментов к левой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_RIGHT |
Прикрепляет панель инструментов к правой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_ANY |
Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна |
CBRS_FLOAT_MULTI |
Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов |
| 0 | Не позволяет прикреплять панель инструментов к рамке |
Расширим меню View нашего приложения и добавим в него пункт включения/выключения панели инструментов IDR_TBCOLOR. Затем нужно создать код отображения и сокрытия панели инструментов. Здесь можно применить два варианта, первый из которых использует методы базового класса CWnd панели инструментов, второй - методы класса CToolBar.
Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов содержит флаг WS_VISIBLE стиля ее окна. Для этого с помощью функции CWnd::GetStyle() необходимо получить сначала значение стиля окна панели инструментов, а затем сравнить его по маске со значением флага WS_VISIBLE.
// Выяснить текущее состояние видимости bool bVisible = (m_wndColorBar.GetStyle() WS_VISIBLE);
На основании информации о том, скрыта панель инструментов или отображена, можно применить соответствующее действие
// Показать или скрыть панель инструментов int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL; m_wndColorBar.ShowWindow(nShow);
После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()
// Переконфигурировать элементы всех панелей инструментов, // строки состояния и всего остального в пределах фрейма RecalcLayout();
Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов можно получить с помощью функции CControlBar::IsVisible(). Затем для отображения или скрытия панели инструментов можно применить функцию фреймового окна CFrameWnd::ShowControlBar(). Прототип этой функции
void ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay);
pBar - адрес переменной управляемой панели инструментовbShow - булева переменная, которая определяет, показывать или скрывать панель инструментов ( TRUE - показать, FALSE - скрыть)TRUE ) или немедленно ( FALSE )После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()
// Переконфигурировать элементы всех панелей инструментов, // строки состояния и всего остального в пределах фрейма RecalcLayout();
Для добавления возможности
Откройте ресурс меню на редактирование, русифицируйте его и добавьте в категорию
View ресурса меню ID (идентификатор)ID_VIEW_COLORBARCaption (надпись)Color BarPrompt (подсказка)\n Показать/ Скрыть
Во вкладке Class View выделите класс CMainFrame
Во вкладке Properties установите режим Events
Найдите узел событий ID_VIEW_COLORBAR, раскройте его и добавьте обработчики для событий COMMAND и UPDATE_COMMAND_UI
Заполните обработчики такОбработчики меню View пункта Color Bar
void CMainFrame::OnViewColorbar()
{
/***Студент Жук! Чтобы раскомментировать блок, добавьте спереди один слэш***
// Вариант 1
// Выяснить текущее состояние видимости
bool bVisible = (m_wndColorBar.GetStyle() WS_VISIBLE);
// Показать или скрыть панель инструментов
int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
m_wndColorBar.ShowWindow(nShow);
//*****************************************************/
//***Студент Жук! Чтобы закомментировать блок, уберите спереди один слэш***
// Вариант 2
// Показать или скрыть панель инструментов
ShowControlBar(m_wndColorBar
,!m_wndColorBar.IsVisible()
,FALSE);
//*****************************************************/
// Переконфигурировать элементы всех панелей инструментов,
// строки состояния и всего остального в пределах фрейма
RecalcLayout();// Можно и закомментировать, я разницу не нашел!
}
void CMainFrame::OnUpdateViewColorbar(CCmdUI *pCmdUI)
{
// В зависимости от видимости панели отобразить
// пункт меню с отметкой флажком или без флажка
pCmdUI->SetCheck(m_wndColorBar.IsVisible());
}
Проследите за тем, чтобы обработчики были членами класса CMainFrame и попали в MainFrm.cpp.В первом обработчике реализованы два варианта управления отображением панели, рассмотренные выше. Один из вариантов закомментирован. Проверьте оба варианта, попеременно раскомментируя один и закомментируя другой. Обратите внимание на способ быстрого комментирования нужного блока, авось пригодится где-нибудь в "Силиконовой долине", где время - деньги.
Добавим к панели инструментов комбинированный список, отвечающий за установку толщины пера рисования.
Прежде всего нужно вручную отредактировать исходный файл ресурсов, имеющий имя проекта с расширением .rc, чтобы зарезервировать место для размещения комбинированного списка. Сделать это автоматически с помощью оболочки не удается.
Перейдите на вкладку
Из контекстного меню этого ресурса откройте окно
Выберите текстовый редактор Source Code (, как показано на рисунке, и нажмите кнопку Open. Если появится окно сообщения с вопросом, хотим ли мы закрыть текущий редактор ресурсов (вкладку Resource View ),

щелкните по кнопке Да ( Yes )
А теперь будьте очень осторожными и случайно не испортите файл ресурсов, иначе это приведет к выводу из строя всего проекта приложения!!! (лучше вначале создать его копию)
Найдите раздел панели инструментов IDR_TBCOLOR и добавьте две строки SEPARATOR (разделитель) как показано нижеПрямое редактирование файла ресурсов DrawSDI.rc
....................................................
///////////////////////////////////////////////////////////
//
// Toolbar
//
IDR_TBCOLOR TOOLBAR 16, 15
BEGIN
BUTTON ID_COLOR_BLACK
BUTTON ID_COLOR_BLUE
BUTTON ID_COLOR_GREEN
BUTTON ID_COLOR_CYAN
BUTTON ID_COLOR_RED
BUTTON ID_COLOR_MAGENTA
BUTTON ID_COLOR_YELLOW
BUTTON ID_COLOR_WHITE
SEPARATOR
SEPARATOR
END
...................................................
Закройте окно редактора с сохранением измененийТеперь, если открыть вкладку Resource View, то можно увидеть предупреждение, что файл ресурсов редактировался напрямую через прочие редакторы

Сверните узел дерева с изображением папки и разверните вновь - предупреждение исчезнетПрямое редактирование файла ресурсов было выполнено по трем причинам:
Постройте приложение и обратите внимание, что в конце панели инструментов выбора цвета появились два вертикальных разделителя - это и есть добавленные нами сепараторыТеперь нужно добавить текстовые строки как ресурс, которые будут загружены в комбинированный список при его создании. Следует помнить, что каждая строка комбинированного списка является отдельным ресурсом и должна иметь уникальный идентификатор с уникальным значением. Текстовые строки добавим через таблицу строк String Table ресурса приложения.
Через вкладку Resource View откройте узел String TableЕсли вы не русифицировали этот ресурс и одновременно для кнопок панели инструментов вводили русские сообщения, то в файле ресурсов оболочка создала два раздела, относящиеся к таблице строк. Один из разделов с последними изменениями имеет вид
Дополнительный раздел таблицы строк в DrawSDI.rc
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
ID_COLOR_BLACK "Цвет рисования черный\nЧерный"
ID_COLOR_BLUE "Цвет рисования синий\nСиний"
ID_COLOR_GREEN "Цвет рисования зеленый\nЗеленый"
ID_COLOR_CYAN "Цвет рисования бирюзовый\nБирюзовый"
ID_COLOR_RED "Цвет рисования красный\nКрасный"
ID_COLOR_MAGENTA "Цвет рисования сиреневый\nСиреневый"
ID_COLOR_YELLOW "Цвет рисования желтый\nЖелтый"
ID_COLOR_WHITE "Цвет рисования белый\nБелый"
END
STRINGTABLE
BEGIN
ID_VIEW_COLORBAR "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
END
Двойным щелчком мыши на русифицированном ресурсе вызовите редактор таблицы строк. Добавляете новые строки в соответствии с таблицей, приведенной ниже. При этом следите за тем, чтобы значения поля
View ресурса меню ID Caption IDS_WIDTH_1IDS_WIDTH_2IDS_WIDTH_3IDS_WIDTH_4IDS_WIDTH_5IDS_WIDTH_6IDS_WIDTH_7IDS_WIDTH_8IDS_WIDTH_16MediumIDS_WIDTH_24Thick IDS_WIDTH_32Very Thick Должно получиться нечто подобное

Теперь в файле ресурсов соответствующий раздел выглядит так
Раздел таблицы строк в DrawSDI.rc после добавления
//////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
ID_COLOR_BLACK "Цвет рисования черный\nЧерный"
ID_COLOR_BLUE "Цвет рисования синий\nСиний"
ID_COLOR_GREEN "Цвет рисования зеленый\nЗеленый"
ID_COLOR_CYAN "Цвет рисования бирюзовый\nБирюзовый"
ID_COLOR_RED "Цвет рисования красный\nКрасный"
ID_COLOR_MAGENTA "Цвет рисования сиреневый\nСиреневый"
ID_COLOR_YELLOW "Цвет рисования желтый\nЖелтый"
ID_COLOR_WHITE "Цвет рисования белый\nБелый"
END
STRINGTABLE
BEGIN
ID_VIEW_COLORBAR "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
IDS_WIDTH_1 "1"
IDS_WIDTH_2 "2"
IDS_WIDTH_3 "3"
IDS_WIDTH_4 "4"
IDS_WIDTH_5 "5"
IDS_WIDTH_6 "6"
IDS_WIDTH_7 "7"
IDS_WIDTH_8 "8"
IDS_WIDTH_16 "Medium"
IDS_WIDTH_24 "Thick"
IDS_WIDTH_32 "Very Thick"
END
Прежде всего создадим идентификатор ID, который далее при создании списка программно система свяжет с самим списком и далее под этим ID будет учитывать этот список во всех других функциях управления списком.
Выберите вкладку
Нажмите кнопку Для программной поддержки комбинированного списка создадим переменную m_ctrlWidth типа CComboBox как член класса CMainFrame. Для этого:
Во вкладке
Убедитесь в том, что мастер добавил переменную именно в класс CMainFrame файла MainFrm.hДалее нам нужно будет выполнить такие действия:
ID заглушки-заполнителя комбинированного списка на панели инструментов равными соответственно ширине и идентификатору ID самого комбинированного спискаЧтобы упорядочить этот процесс и не захламлять функцию OnCreate() класса фреймового окна CMainFrame не свойственным ей кодом, создадим отдельную функцию-член фрейма, а в функции OnCreate() разместим вызов этой вспомогательной упаковки кода. Вспомогательную функцию назовем CreateColorBar().
Выделите во вкладке Class View класс CMainFrame
Вызовите мастер
Перенесите (это значит полностью уберите со старого места!!!) соответствующий код из функции OnCreate() файла MainFrm.cpp в эту новую функцию (файл тот же!), а также добавьте новый код для создания комбинированного спискаНиже приведен листинг функции, где красным выделен перенесенный код, а синим - добавленный новый код
Вспомогательная функция в файле MainFrm.cpp
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return FALSE; // fail to create
}
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0){
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
// Добавить комбинированный список
int nWidth = 100;
int nHeight = 125;
// Выбрать конфигурацию заглушки-заполнителя комбинированного списка
m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
// Получить высоту панели инструментов выбора цвета
CRect rect;
m_wndColorBar.GetItemRect(9, rect);
rect.bottom = rect.top + nHeight;
// Создать комбинированный список
m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
,rect, m_wndColorBar, IDC_CBWIDTH);
// Заполнить комбинированный список текстами из ресурса String Table
CString str;
UINT id = IDS_WIDTH_1; // Важно, что значения идентификаторов
// расположены в порядке возрастания с шагом 1,
// поэтому можно организовать цикл
for(; id - IDS_WIDTH_1 < 11; id++) // Перебираем 11 элементов
if(str.LoadString(id))
m_ctrlWidth.AddString((LPCTSTR)str);
// Устанавливаем первый элемент видимым
m_ctrlWidth.SetCurSel(0);
return TRUE;
}
В приведенном коде используется функция
// Выбрать конфигурацию заглушки-заполнителя комбинированного списка m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
Первый параметр (9) - означает индекс размещаемого в панели элемента ( начинается с нуля ). Этим модифицируемым элементом-заглушкой будет ранее введенный нами последний сепаратор в файле ресурсов. Фрагмент файла ресурсов таков
Идентификаторы элементов строки состояния в файле ресурсов
/////////////////////////////////////////////////////////////////////////////
//
// Toolbar
//
IDR_TBCOLOR TOOLBAR 16, 15
BEGIN
BUTTON ID_COLOR_BLACK
BUTTON ID_COLOR_BLUE
BUTTON ID_COLOR_GREEN
BUTTON ID_COLOR_CYAN
BUTTON ID_COLOR_RED
BUTTON ID_COLOR_MAGENTA
BUTTON ID_COLOR_YELLOW
BUTTON ID_COLOR_WHITE
SEPARATOR
SEPARATOR // заглушка-заполнитель как элемент с индексом 9
END
Второй параметр - идентификатор ресурса комбинированного списка, для которого резервируется место на панели и за которым в дальнейшем функция Create() закрепит комбинированный список как приемник сообщений.
Третий параметр - стандартная константа TBBS_SEPARATOR библиотеки MFC, представляющая разделитель панели инструментов.
Четвертый аргумент - ширина заглушки-заполнителя для резервируемого места под комбинированный список.
Вновь дополните функцию CMainFrame::OnCreate() кодом вызова только что созданной упаковки CMainFrame::CreateColorBar(), после чего функция должна выглядеть такВызов вспомогательной функции из OnCreate()
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Добавить инструментальную панель выбора цвета
// вместе с комбинированным списком выбора толщины пера
if(!CreateColorBar()){
TRACE0("Failed to create color toolbar\n");
return -1;
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
Постройте приложение и убедитесь, что список раскрывается, но ничего пока не делаетМы создали комбинированный список как немодальное окно с помощью функции Create, которая выглядит так
Функция Create в файле MainFrm.cpp
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
...............................................................
// Создать комбинированный список
m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
,rect, m_wndColorBar, IDC_CBWIDTH);
.......................................................................
return TRUE;
}
Первым аргументом этой функции является сочетание стилей комбинированного списка, определяющих его внешние свойства и поведение. Часть флажков относятся к оконным стилям WS_ (window style) , поскольку комбинированный список наследует класс CWnd, а часть флажков специфицирует стиль только списка ( CBS_ - ComboBox Style ). Перечислим значения флажков стилей списка
| Стили комбинированного списка | |
|---|---|
| Стиль | Описание |
WS_CHILD |
Указывает, что данное окно является дочерним (обязательный стиль) |
WS_VISIBLE |
Делает комбинированный список изначально видимым |
WS_DISABLED |
Отключает комбинированный список |
WS_VSCROLL |
Добавляет вертикальную прокрутку к раскрывающемуся списку |
WS_HSCROLL |
Добавляет горизонтальную прокрутку к раскрывающемуся списку |
WS_GROUP |
Группирует элементы управления |
WS_TABSTOP |
Включает комбинированный список в последовательность перехода по клавише Tab |
CBS_AUTOHSCROLL |
Обеспечивает прокрутку текста в поле редактирования при его заполнении |
CBS_DROPDOWN |
Подобен стилю CBS_SIMPLE, но не отображает список до тех пор, пока пользователь не выберет пиктограмму, следующую за полем редактирования |
CBS_DROPDOWNLIST |
Подобен стилю CBS_DROPDOWN. Однако заменяет поле редактирования статическим текстом, который отображает текущее выбранное значение элемента списка |
CBS_HASSTRINGS |
Указывает, что за перерисовку комбинированного списка отвечает владелец. Элементы в окне списка состоят из строк |
CBS_OEMCONVERT |
Текст, выводимый для отображения на экране в поле редактирования, конвертируется из набора символов ASCII в набор OEM, а затем, при передаче данных из комбинированного списка в программу - опять из набора OEM в набор символов ASCII. Набор отображаемых символов ASCII используется в MS-DOS, а OEM - в Windows. Символы с кодами 32-127 обычно совпадают в обеих системах. Другие же символы, используемые национальными кодировками, требуют конвертации. Для правильного отображения текста на экране в консольном режиме для этого используется функция CharToOem(char* strASCII, char* strOEM) |
CBS_OWNERDRAWFIXED |
Указывает, что владелец списка отвечает за перерисовку содержимого. Содержимое списка имеет фиксированную высоту |
CBS_OWNERDRAWVARIABLE |
Указывает, что владелец списка отвечает за перерисовку содержимого. Высота содержимого списка может изменяться |
CBS_SIMPLE |
Все время отображает список на экране |
CBS_SORT |
Автоматически сортирует строки в списке |
CBS_DISABLENOSCROLL |
Если список не содержит достаточно элементов для прокрутки, все равно отображается полоса прокрутки, хоть и в отключенном ( Disabled ) виде |
CBS_NOINTEGRALHEIGHT |
Указывает, что комбинированный список имеет точно указанный размер |
При нажатии на кнопки панели инструментов нам не нужно передавать из класса CMainFrame, которому принадлежит панель инструментов, значение выбранного цвета пера в закрытую переменную m_iColor класса CDrawSDIDoc. Это делается через обработчики событий меню, которые перехватываются классом CDrawSDIDoc, поскольку кнопки панели и пункты меню Color имеют одинаковые идентификаторы.
В случае с комбинированным списком нам нужно сделать так, чтобы при выборе опции комбинированного списка на панели инструментов выбранное значение толщины пера передавалось бы как значение в переменную m_iWidth класса CDrawSDIDoc. Поскольку в этом классе мы объявили эту переменную закрытой от клиента класса, то необходимо дополнить класс функцией доступа, которую назовем SetWidth().
Выделите класс CDrawSDIDoc во вкладке Class View и через контекстное меню вызовите мастер Add Function, который заполните так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Тело функции заполните такФункция доступа SetWidth() класса CDrawSDIDoc
// Установить толщину пера
void CDrawSDIDoc::SetWidth(int nWidth)
{
m_iWidth = nWidth;
}
Теперь, имея указатель на текущий документ, мы сможем с помощью этой функции передать в документ значение толщины пера, установленное в комбинированном списке панели инструментов.
Но как получить значение от комбинированного списка. Можно перхватывать события, которые генерирует комбинированный список при выборе пользователем нового элемента списка. Это событие имеет имя ON_CBN_SELCHANGE. Поскольку это сообщение генерируется в классе CMainFrame, перехватим его здесь. Для этого:
Введите вручную регистрацию события ON_CBN_SELCHANGE в карту сообщений класса, чтобы она выглядела такКарта сообщений класса CMainFrame в файле MainFrm.cpp BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar) ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar) ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth) END_MESSAGE_MAP()
Через мастер добавьте в класс
Заполните, обработчик кодом, как показано нижеОбработчик OnSelChangeWidth() в файле MainFrm.cpp
// Функция-обработчик выбора элемента комбинированного списка
void CMainFrame::OnSelChangeWidth(void)
{
int iIndex = m_ctrlWidth.GetCurSel();
if(iIndex == CB_ERR)// Если получили индекс
return;
int iWidth = iIndex + 1;
switch(iIndex + 1){ // Скорректировать толщину
case 9: iWidth = 16; break;
case 10: iWidth = 24; break;
case 11: iWidth = 32;
}
// Получить активный документ
CDrawSDIDoc* pDoc = (CDrawSDIDoc*)GetActiveDocument();
if(pDoc) // Если получили указатель
pDoc->SetWidth(iWidth); // Передать толщину в документ
}
Поскольку класс документа CDrawSDIDoc в файле MainFrm.cpp не известен, включите в начало этого файла инструкцию #include. Следует иметь ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный кодИнструкция включения файла DrawSDIDoc.h в файл MainFrm.cpp ......................................... ......................................... #include "DrawSDIDoc.h" // CMainFrame IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar) ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar) ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth) END_MESSAGE_MAP() .........................................
Постройте приложение и убедитесь, что список управляет толщиной линии, но при изменении толщины через меню значение списка синхронно не изменяетсяИсправим это...
Выбор толщины линии через меню обрабатывается функцией, расположенной в документе. Вот эта функция
Функция-обработчик в файле DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
}
Если ввести в этот обработчик вызов функции, которая во фреймовом окне обновит комбинированный список, то это и будет решением задачи. Но для этого из документа прежде нужно будет получить указатель на фреймовое окно-рамку, и уж через него воздействовать на комбинированный список панели инструментов, заставив его обновиться в соответствии с установленным в меню значением ширины пера.
Здесь необходимы следующие шаги:
View, поскольку сам документ не является оконным объектом и не происходит от базового класса CWndCMainFrame в документе не виденС учетом вышеизложенного выполните следующее:
Введите в документ функцию обновления комбинированного списка, назовите ее CDrawSDIDoc::UpdateComboBox(). Для этого для класса CDrawSDIDoc заполните мастер Add Function так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Мастер сгенерирует заготовку, которую заполните следующим кодомФункция UpdateComboBox() в файле DrawSDIDoc.cpp
// Функция обновления комбинированного списка
void CDrawSDIDoc::UpdateComboBox(int iIndex)
{
// Получить позицию активного вида в Z-последовательности видов
POSITION pos = GetFirstViewPosition();
if(!pos) // Позиция не получена, она нулевая
return;
// Получить указатель на верхнее представление (активный вид)
CView* pView = GetNextView(pos);
if(!pView) // Указатель не получен, он нулевой
return;
// Получить указатель на фреймовое окно с помощью вида
CMainFrame* pFrame = (CMainFrame*)pView->GetTopLevelFrame();
if(!pFrame) // Указатель не получен, он нулевой
return;
// Обновить комбинированный список на панели инструментов
pFrame->m_ctrlWidth.SetCurSel(iIndex);
}
Добавьте в начала файла DrawSDIDoc.cpp инструкцию препроцессору для подключения класса фреймового окна, объявление которого находится в файле приложения MainFrm.h. Имейте ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный кодИнструкция препроцессору в файле DrawSDIDoc.cpp ............................................................... #include "MainFrm.h" // CDrawSDIDoc IMPLEMENT_DYNCREATE(CDrawSDIDoc, CDocument) BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) END_MESSAGE_MAP() ...............................................................
Поместите вызов функции UpdateComboBox() в обработчике выбора пункта меню WidthВызов функции в обработчике меню файла DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
UpdateComboBox(nID - ID_WIDTH_1);
}
Постройте приложение и убедитесь, что все работает правильно!!!..., кроме одного - при создании нового документа комбинированный список не устанавливается в начало
Чтобы это исправить, разместите вызов UpdateComboBox(0) еще и в функции OnNewDocument() класса CDrawSDIDoc (сделайте это самостоятельно!!!)Есть еще одна неувязочка, которая может не понравиться ПОЛЬЗОВАТЕЛЮ. После прикрепления к левому или правому боку фреймового окна панель скрывает комбинированный список. На данном этапе исправим это, установив свойство панели прикрепляться только к верхнему краю, что не очень ограничит права пользователя. Для этого:
Измените в функции CMainFrame::OnCreate() соответствующий фрагмент кодаИзменение свойства прикрепления панели в файле CMainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
................................................................
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_TOP);// Наша!!!
EnableDocking(CBRS_ALIGN_TOP);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
И последнее. Если потянуть панель в плавающем состоянии за нижний край, то список опять исчезает. Разозлимся и отключим у пользователя свет, газ и воду, а у панели - возможность изменять размер.
Просто закомментируйте код, как показано в листингеОтключение свойства панели изменять размер
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY /* | CBRS_SIZE_DYNAMIC */) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return FALSE; // fail to create
}
..................................................
// Устанавливаем первый элемент видимым
m_ctrlWidth.SetCurSel(0);
return TRUE;
}
Строка состояния является еще одним элементом GUI, служащим для поддержки пользователя. Она, как правило, располагается в нижней части окна, и служит для отображения различных данных, специфичных для данного приложения. В приложениях и она создается автоматически мастером, и разработчику достаточно ее слегка модифицировать, в приложениях же на основе диалога ее нужно создавать самому разработчику, т.е. нам, с нуля (а этот процесс несколько сложнее). В данной лабораторной работе мы выполним только некоторую модификацию строки состояния.
Строка состояния делится на части, которые называются панелями. Элементы управления, входящие в строку состояния, называются индикаторами и каждый из них отображает определенную информацию о состоянии приложения и системы. Строка состояния настолько стандартна, что для нее даже не предусмотрен редактор ресурсов, как для других визуальных ресурсов, как например, диалоговые панели или панели инструментов. Достаточно только указать мастеру создания приложений о необходимости ее создания, и весь требуемый код будет создан автоматически.
Строка состояния MFC является окном немодального типа, наследуемым от класса CWnd, и поддерживается двумя классами:
CStatusBar - в основном используется в SDI и MDI приложенияхCStatusBar CommandToIndex()Create()GetItemID()GetItemRect()GetPaneInfo()GetPaneStyle()GetPaneText()GetPaneBarCtrl()CStatusBarCtrl SetIndicators()SetPaneInfo()SetPaneStyle()SetPaneText()GetTextExtent()Когда в нашем случае мастер создал приложение (или ), то мы видим, что сгенерированная им строка состояния содержит 4 панели (части). Первая панель предназначена для подсказок меню (или кнопок панели инструментов) и она самая длинная. Далее следуют еще три панели <. Соответствующий код поддержки находится в трех местах, рассмотрим его
Переменная-экземпляр класса CStatusBar - ее объявление находится в классе фреймового окна CMainFrame файла MainFrm.h
Объявление переменной-экземпляра класса CStatusBar
class CMainFrame : public CFrameWnd
{
......................................................
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
......................................................
};
Массив индикаторов - массив индикаторов, представленный идентификаторами ресурсов, которые будут отображать информацию в строке состояния. Этот массив определен как глобальный статический в начале файла MainFrm.cpp сразу после карты сообщений
Массив индикаторов в файле MainFrm.cpp
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
Порядок расположения идентификаторов в массиве соответствует позиции индикаторов в панели состояния. Первым в этом массиве всегда должен стоять ID_SEPARATOR, который имеет стандартное имя и сообщает среде выполнения, что с данной конкретной панелью строки состояния не связан ни один строковый ресурс. Фактически это означает, что первая панель строки состояния может принимать информацию от любых ресурсов, а в свободном состоянии отображать слово "Ready" (Готов).
Три последних идентификатора массива индикаторов можно найти в строковом ресурсе файла ресурсов DrawSDI.rc при открытии внешним редактором
Файл ресурсов DrawSDI.rc
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDR_MAINFRAME "DrawSDI\n\nDrawSDI\nDrawSDI Files (*.dhc)\n.dhc\nDrawSDI.Document\nDrawSDI.Document"
END
STRINGTABLE
BEGIN
AFX_IDS_APP_TITLE "DrawSDI"
AFX_IDS_IDLEMESSAGE "Ready"
END
STRINGTABLE
BEGIN
ID_INDICATOR_EXT "EXT"
ID_INDICATOR_CAPS "CAP"
ID_INDICATOR_NUM "NUM"
ID_INDICATOR_SCRL "SCRL"
ID_INDICATOR_OVR "OVR"
ID_INDICATOR_REC "REC"
END
Обратите внимание на текст, выделенный синим цветом. Кстати, если Вы этот текст в стороннем редакторе переместите в часть файла ресурсов String Table, поддерживающего русскую кодировку 1251, то вялое слово "Ready" можете заменить на свое, которое будет выдаваться как дежурное сообщение в первой панели строки состояния, когда эта панель простаивает.
Функция OnCreate() фреймового окна-рамки - содержит код MainFrm.cpp
Файл ресурсов DrawSDI.rc
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...................................................
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
...................................................
}
Строка состояния создается функцией Create() с единственным обязательным параметром, являющемся указателем на родительское окно ( pParentWnd ). Родительским окном для строки состояния является фреймовое окно, поэтому достаточно указать this - указатель на экземпляр класса CMainFrame фреймового окна-рамки. Вторая функция SetIndicators() присваивает массив indicators[], в котором содержаться значения идентификаторов ресурсов, состояние которых нужно отобразить.
Фактически функция Create() имеет три параметра, два из которых (последние) установлены по умолчанию.
CWnd* pParentWnd - указатель на родительское окно строки состоянияDWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | CBRS_BOTTOM - комбинация флагов, определяющих тип, видимость, форму границ, месторасположениеUINT nID = AFX_IDW_STATUS_BAR - стандартный идентификатор самой строки состоянияДля добавления в строку состояния нового индикатора прежде нужно создать его идентификатор в String Table файла ресурсов приложения. Идентификатор будет представлять в приложении закрепленный за ним индикатор. Чтобы в индикатор поместился любой закрепленный за ним текст, необходимо при создании соответствующего идентификатора ввести самый длинный текст из предполагаемых для отображения.
Вызовите на редактирование ресурс String Table и добавьте новую строку
В поле ID введите значение ID_INDICATOR_COLOR, поле Value оставьте по умолчанию, а в поле Caption введите строку Бирюзовый, как самую длинную из набора слов "Черный, Синий, Зеленый, Бирюзовый, Красный, Сиреневый, Желтый, Белый", подлежащих отображению
В массив индикаторов файла MainFrm.cpp добавьте новый идентификатор такМассив индикаторов в файле MainFrm.cpp
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_COLOR,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
Постройте приложение и убедитесь, что новый индикатор создан и в нем находится статический текст "Бирюзовый"Когда окно приложения требует обновления, всем дочерним окнам об этом подается сообщение по каналу сообщений с требованием обновить себя. Сообщение называется UPDATE_COMMAND_UI. В картах сообщений отправитель сообщения отыскивается по его идентификатору, вызывается обрабатывающая функция, в которую передается адрес окна, отправившего сообщение. Воспользуемся этим для обновления содержимого индикатора цвета в строке состояния.
Добавьте в класс CDrawSDIDoc новую функцию-обработчик, заполнив мастер так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Введите вручную в карту сообщений класса CDrawSDIDoc регистрацию обработчика за нашим новым индикатором с идентификатором ID_INDICATOR_COLORКарта сообщений в файле DrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) ON_UPDATE_COMMAND_UI(ID_INDICATOR_COLOR, OnUpdateIndicatorColor) END_MESSAGE_MAP()
Наполните обработчик кодомКод обработчика в файле DrawSDIDoc.cpp
// Обработчик обновления индикатора
void CDrawSDIDoc::OnUpdateIndicatorColor(CCmdUI* pCmdUI)
{
char* str[] = {"Черный",
"Синий",
"Зеленый",
"Бирюзовый",
"Красный",
"Сиреневый",
"Желтый",
"Белый"
};
pCmdUI->Enable(); // По умолчанию TRUE - доступный
pCmdUI->SetText((LPCTSTR)str[m_iColor]);
}
Постройте приложение и убедитесь, что все работает
Нарисуйте свой автопортрет, например, так
А он лучше оригинала...
C++В основе приложений MFC лежит концепция document object/view window (Документ/Вид). Обычно документ представляет собой отдельный файл, который приложение способно открыть. В нем хранятся данные, управляемые приложением. Отображение данных и интерфейс работы с ними обеспечивает представление. Представление способно отображать данные документа частично и в разной форме, выполняя заданный срез документа.
Отношение между документом и представлением строится по принципу "один ко многим". То есть один документ (одно содержание) может иметь несколько представлений (несколько форм), но каждое из этих представлений отображает одни и те же данные.
Программно такая концепция реализуется механизмом - однодокументный интерфейс. Управление документом осуществляется объектом на основе производного класса от базового класса CDocument, а управление интерфейсами - на основе базового класса CView.
Основой для CFrameWnd. Фреймовое окно является главным окном приложения и играет роль родительского по отношению к содержащимся в нем окнам. Неотъемлемой частью фреймового окна является его заголовок и меню. Клиентскую область фреймового окна занимают всевозможные дочерние окна, в том числе окно панели инструментов, окно строки состояния, окно представления и др.

В приложениях , созданных мастером MFC Application Wizard, исходный код реализации фреймового окна (реализация класса CMainFrame ) размещается в файлах проекта MainFrm.h и MainFrm.cpp. Класс CMainFrame наследует практически все свои функциональные возможности от базового класса CFrameWnd.
В свою очередь, фреймовым окном управляет каркас приложения, порожденный классом CWinApp. Каркас приложения не имеет визуального представления, его задача - обеспечить взаимодействие окна-рамки и дочерних фреймовых окон. Это взаимодействие осуществляется с помощью сообщений. Модель отношений между базовыми классами приложения приведена на рисунке

Класс CDocument обеспечивает стандартное поведение объектов документа MFC -приложения. Независимо от того, создается однодокументное приложение или многодокументное, мастер AppWizard создает только один класс документа, производный от базового класса CDocument.
В библиотеке MFC существует несколько классов массивов для хранения различных типов данных. У всех этих классов одинаковые функциональные возможности и работают они по одному и тому же принципу. Основное различие - это тип хранимых в них данных.
Классы массивов MFC | |
|---|---|
| Класс | Описание |
CArray |
Шаблонный класс для создания пользовательских классов массивов |
CByteArray |
Массив с данными типа байт |
CDWordArray |
Массив с данными типа DWORD (двойное слово) |
CObArray |
Массив может содержать классы, производные от класса CObject |
CPtArray |
Массив указателей на любые объекты и типы данных |
CStringArray |
Массив объектов типа |
CUIntArray |
Массив данных типа UINT (unsigned int) |
CWordArray |
Массив данных типа WORD (слово) |
Методы классов массивов MFC | |
|---|---|
| Класс | Описание |
array[int index] |
Оператор ображения к значению элемента массива по его индексу. |
array.GetAt(int index) |
Возвращает элемент массива по его индексу. |
array.SetAt(int index, element) |
Записывает элемент массива по его индексу. Тип данных записываемого элемента должен соответствовать типу данных всего массива. Вызов функции не приводит к росту количества элементов массива. |
array.InsertAt(int index, element) |
Вставляет новый элемент перед элементом index. Размерность массива увеличивается на единицу. |
array.Add(element) |
Добавляет новый элемент в конец массива. Добавляемый элемент должен иметь тип данных всего массива. |
array.RemoveAt(int index, int count=1) |
Удаляет count элементов, начиная с позиции index. Если второй аргумент не указан, то удаляется один элемент. |
array.RemoveAll() |
Удаляет все элементы массива и присваивает свойству количества элементов массива значение ноль. |
В массивах классов CObArray и CPtArray элементы как объекты не удалаются. Из массивов удаляются только указатели на объекты. При использовании функций RemoveAt() и RemoveAll() необходимо сначала удалить сами объекты, а затем удалить из массива их указатели. |
|
array.GetSize() |
Возвращает количество элементов массива. |
array.GetCount() |
Возвращает количество элементов массива. |
array.GetUpperBound() |
Возвращает индекс последнего элемента массива. |
array.SetSize(int iNewSize, iGrowBy) |
Управляет размером массива. |
array.Copy(source) |
Копирует source в array |
array.Append(source) |
Присоединяет source в конец array |
В случае с массивами классов CObArray и CPtArray функции Copy() и Append() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным. |
|
Создадим графическое приложение на основе однодокументного интерфейса, в котором можно будет рисовать, и сохранять нарисованное в файле с последующим восстановлением.
Войдите в свой каталог выполнения лабораторных работ
Создайте новый проект MFC Application и назовите его DrawSDI
Настройте мастер приложений
Application Type Application Type:Single document Document/View architecture support Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library Compound Document Support Compound document support:NoneDocument Template Strings File extension :dhcDatabase Support Database support :NoneUser Interface Features Main frame styles:Minimize box Maximize box System menu Initial status bar Toolbars:Standard docking Advanced Features Generated Classes Base class :CViewПосле настройки вкладки мастера должны иметь такой вид







Создадим свой класс, который будет обеспечивать поддержку хранения координат отрезка прямой линии и отображения ее в окне представления документа.
В панели Class View найдите верхний узел с названием DrawSDI и для него выполните опцию Add Class контекстного меню
В диалоговом окне
Появившееся окно мастера добавления класса заполните так
Щелкните по кнопке Finish
Мастер создаст заготовки для объявления и определения класса и разместит их в отдельных файлах MyLine.h и MyLine.cpp
Отредактируйте эти заготовки так, чтобы класс мог хранить координаты начальной и конечной точек линии. Для этого воспользуйтесь мастером добавления переменных, предварительно выделяя в панели Class View класс MyLine. Переменные определите с модификатором доступа private
Для того, чтобы класс мог нарисовать линию между двумя точками, добавьте в него с помощью мастера Не забудьте нажать кнопку Add перед нажатием кнопки Finish
В итоге должно получиться следующее
Файл MyLine.h
#pragma once
#include "afx.h"
#include "atltypes.h"
class MyLine :
public CObject
{
public:
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
};
Файл MyLine.cpp
#include "StdAfx.h"
#include ".\myline.h"
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
}
MyLine::~MyLine(void)
{
}
// Функция-член класса MyLine для рисования линий
void MyLine::Draw(CDC* pDC)
{
// Нарисовать линию
pDC->MoveTo(m_pointBegin);
pDC->LineTo(m_pointEnd);
}
Экземпляры объекта MyLine можно сохранять в объекте документа в простом динамическом массиве, реализуемом в MFC -библиотеке классом CObArray. Объект этого класса массива изменяет свой размер динамически в зависимости от количества находящихся в нем элементов. Этот класс-массив может содержать в себе любой объект, являющийся наследником класса CObject, размер массива ограничивается только количеством свободной памяти в компьютере.
Создадим в классе CDrawSDIDoc приложения переменную-экземпляр класса CObArray для хранения линий-экземпляров созданного класса MyLine. Назовем ее m_Lines. Для этого:
В панели Class View выделите класс CDrawSDIDoc, через опцию Add Variable вызовите мастер и заполните его так

Таким же образом добавьте в класс CDrawSDIDoc функцию-член AddLine() для добавления объектов линий в массив, идентифицируемый только что созданной переменной. Мастер заполните так

Мастер внесет следующие добавления в класс CDrawSDIDoc
Файл DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
..............................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
};
Файл DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
return NULL;
}
Отредактируйте заготовку функции AddLine() так
Файл DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
MyLine* pLine = NULL; // Создали указатель на стеке
try // Попытка
{
// Создать экземпляр объекта-линии на куче
pLine = new MyLine(pointBegin, pointEnd); // Конструктор
// Добавить указатель на новую линию в массив объектов
m_Lines.Add(pLine);
// Отметить документ как измененный
SetModifiedFlag();
}
catch(CMemoryException* pErr) // Ловушка на недостаток памяти
{
// Сообщили пользователю
AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
// Уничтожим объект-линию, на котором сгенерировалось исключение
if(pLine){
delete pLine;
pLine = NULL;
}
// Удалить объект с исключением
pErr->Delete();
}
return pLine; // Вернуть указатель на объект-линию
}
Подключите объявление класса MyLine к классу CDrawSDIDoc вначале файла DrawSDIDoc.h так
Файл DrawSDIDoc.h
// DrawSDIDoc.h : interface of the CDrawSDIDoc class
//
#pragma once
#include "afxcoll.h"
#include "MyLine.h"
class CDrawSDIDoc : public CDocument
{
...........................................
};
Добавим в класс CDrawSDIDoc еще две публичные функции-члена, играющих роль функций доступа к закрытой переменной m_Lines:
GetLineCount() - возвращает количество объектов-линий, хранящихся в массиве m_Lines класса CObArrayGetLine() - возвращает указатель на линию, хранящийся в массиве m_Lines, по индексу массиваОбе функции доступа будут нужны в классе представления CDrawSDIView для отображения линий, хранящихся как закрытые в классе документа CDrawSDIDoc.
Заполните мастера добавления функций так


Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Добавления, выполненные мастерами, должны быть такими
Добавления в файле DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
..........................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
// Количество нарисованных линий
int GetLineCount(void);
// Возвращает указатель на объект-линию по индексу
MyLine* GetLine(int index);
};
Добавления в файле DrawSDIDoc.cpp
// Количество нарисованных линий
int CDrawSDIDoc::GetLineCount(void)
{
return 0;
}
// Возвращает указатель на объект-линию по индексу
MyLine* CDrawSDIDoc::GetLine(int index)
{
return NULL;
}
Заполните код функций так
Код функций доступа в файле DrawSDIDoc.cpp
// Количество нарисованных линий
int CDrawSDIDoc::GetLineCount(void)
{
// Возвратить количество элементов в массиве
return (int)m_Lines.GetCount();
}
// Возвращает указатель на объект-линию по индексу
MyLine* CDrawSDIDoc::GetLine(int index)
{
// Возвратить указатель на объект-линию, хранящийся
// в указанной позиции в массиве CObArray m_Lines
return (MyLine*)m_Lines[index];
}
Постройте приложение, чтобы своевременно выловить возможные ошибкиТаким образом, мы выполнили подготовительные мероприятия, позволяющие классу документа сохранять и выдавать информацию о координатах линий. Теперь необходимо реконструировать заготовку класса представления для манипулирования с данными класса документа и представления их в окне приложения. Для этого
Введите частную переменную m_pointPrevPos в класс CDrawSDIView для хранения предыдущей точки рисования

В панели Class View выберите класс CDrawSDIView
Используя вкладку Properties в режиме Messages, добавьте к классу CDrawSDIView функции для обработки сообщений операционной системы
WM_LBUTTONDOWN - для начала рисования и захвата мышиWM_LBUTTONUP - для завершения рисования и освобождения мышиWM_MOUSEMOVE - для выполнения рисования и запоминания координат линий в классе документаДобавление переменной и обработчиков в класс CDrawSDIView
class CDrawSDIView : public CView
{
...................................................
private:
// Предыдущая точка рисования
CPoint m_pointPrevPos;
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
};
Регистрация обработчиков в карте сообщений DrawSDIView.cpp BEGIN_MESSAGE_MAP(CDrawSDIView, CView) // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() END_MESSAGE_MAP()
Отредактируйте функции-обработчики, как показано ниже
Код функций-обработчиков в файле DrawSDIView.cpp
void CDrawSDIView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Захватим мышь, чтобы другое приложение не могло
// перезахватить мышь в момент, когда она
// покинет пределы окна приложения
SetCapture(); // мышь только для нас!!!
// Сохраним точку начала рисования
m_pointPrevPos = point;
CView::OnLButtonDown(nFlags, point);
}
void CDrawSDIView::OnLButtonUp(UINT nFlags, CPoint point)
{
// Если мышь была ранее захвачена нашим приложением,
// то отдадим ее, чтобы другие приложения
// тоже могли использовать мышь
if(GetCapture() == this)
ReleaseCapture();
CView::OnLButtonUp(nFlags, point);
}
void CDrawSDIView::OnMouseMove(UINT nFlags, CPoint point)
{
// Если нажата левая кнопка мыши, то рисовать
if((nFlags MK_LBUTTON) == MK_LBUTTON){
// Если мышь захвачена нами, то рисовать
if(GetCapture() == this){
// Получить контекст устройства
// клиентской области окна приложения
CClientDC dc(this);
// Добавить линию к документу и вернуть на нее
// указатель как на объект, чтобы воспользоваться
// ее функцией-членом для рисования
MyLine* pLine =
GetDocument()->AddLine(m_pointPrevPos, point);
// Нарисовать текущую линию
pLine->Draw(dc);
// Сохранить текущую точку в качестве предыдущей
m_pointPrevPos = point;
} // end of if
} // end of if
CView::OnMouseMove(nFlags, point);
}
Постройте приложение, порисуйте и поперемещайте клиента заграницы экрана
Видно, что рисунок не восстанавливается, хотя все линии у нас сохранены в классе документа. Функция OnDraw() класса представления срабатывает всякий раз даже при частичном разрушении окна представления или когда данные документа были отмечены как измененные с помощью функции SetModifiedFlag().
Добавьте в функцию OnDraw() код прорисовки всех сохраненных линий (не забудьте раскомментировать аргумент указателя в заголовке функции), как показано в листинге
Код функции OnDraw() в файле DrawSDIView.cpp
void CDrawSDIView::OnDraw(CDC* pDC)
{
CDrawSDIDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
// Получить количество линий в документе
int count = pDoc->GetLineCount();
// Если есть линии, то нарисовать их все
if(count){
for(int i = 0; i < count; i++){
MyLine* pLine = pDoc->GetLine(i);
pLine->Draw(pDC);
}
}
}
Постройте приложение и убедитесь, что теперь все восстанавливается после разрушения окна приложенияКогда в классе документа CDrawSDIDoc срабатывает функция OnNewDocument(), то данные в массиве m_Lines необходимо уничтожить и освободить занимаемую ими память. Это ни в коем случае нельзя делать в функции OnNewDocument(), (хотя принципиально это и можно сделать), потому, что по логике своего предназначения эта функция должна инициализировать данные нового документа.
В данной ситуации для удаления объектов линий воспользуемся обработчиком сообщения операционной системы о событии DeleteContents(). Переопределим этот обработчик, выполнив следующие действия:
В панели Class View выберите класс CDrawSDIDoc
Во вкладке Properties включите режим Overrides (Переопределение)
Найдите виртуальную функцию DeleteContents класса CDocument и создайте переопределение этой функции

Заполните переопределенную функцию DeleteContents() так
Код переопределения виртуальной функции DeleteContents() в файле DrawSDIDoc.cpp
void CDrawSDIDoc::DeleteContents()
{
// Получить количество линий в массиве объектов
int count = (int)m_Lines.GetCount();
// Попытка выполнить удаление объектов
try
{
if(count){ // есть что удалять
// Освободить память на куче по каждому объекту
for(int i = 0; i < count; i++)
delete (MyLine*)m_Lines.GetAt(i);
// Установить массив в исходное состояние
m_Lines.RemoveAll();
}
}
// Ловушка исключения о нарушении памяти
catch(CMemoryException* pErr)
{
// Отобразить сообщение о причине исключения
pErr->ReportError();
// Удалить объект, вызвавший исключение
pErr->Delete();
}
CDocument::DeleteContents();
}
Добавим возможность сохранения документа. Воспользуемся функцией сериализации Serialize() класса CDrawSDIDoc, которую перепишем так, чтобы она только передавала сообщение о преобразовании в последовательную форму объекту-массиву m_Lines. В самом же объекте-массиве m_Lines предусмотрим свою функцию сериализации, в которой и осуществим сохранение документа с использованием функциональных возможностей C++.
В классе CDrawSDIDoc найдите функцию Serialize() преобразования в последовательную форму
Удалите все содержимое функции и перепишите ее так
Функция сериализации класса CDrawSDIDoc
void CDrawSDIDoc::Serialize(CArchive ar)
{
// Передать объекту-массиву сообщение
// о преобразовании в последовательную форму
m_Lines.Serialize(ar);
}
Добавьте новую функцию-член к классу MyLine, выделив класс MyLine в панели Class View и заполнив мастер

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Отредактируйте функцию так
Функция сериализации класса MyLine
// Преобразование в последовательную форму объекта-массива
void MyLine::Serialize(CArchive ar)
{
CObject::Serialize(ar);
if(ar.IsStoring())
ar << m_pointBegin << m_pointEnd; // Сохранить
else
ar >> m_pointBegin >> m_pointEnd; // Восстановить
}
Постройте приложение и попробуйте сохранить документ на диск. Система выдаст сообщение, что невозможно сохранить документ

Исправим это...
Зарегистрируем класс MyLine, как способный самостоятельно выполнять сериализацию. Для этого:
В начало объявления класса MyLine в файле MyLine.h и перед конструктором класса в файле MyLine.cpp добавьте по строке регистрации и конструктор по умолчанию
Конструктор по умолчанию теперь нужно определить самим, поскольку определен общий конструктор, который делает невозможным вызов автоматического конструктора по умолчанию.
Добавление в объявление класса MyLine в файле MyLine.h
class MyLine : public CObject
{
public:
DECLARE_SERIAL(MyLine);
MyLine(){}; // Констуктор по умолчанию
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
private:
// Массив линий
CObArray m_Lines;
public:
// Преобразование в последовательную форму объекта-массива
void Serialize(CArchive ar);
};
Добавление вначало файла MyLine.cpp
#include "StdAfx.h"
#include ".\myline.h"
IMPLEMENT_SERIAL(MyLine, CObject, 1)
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
}
MyLine::~MyLine(void)
{
}
....................................................
Постройте приложение и убедитесь, что документы сохраняются на диске и загружаются с диска. Вот один из восстановленных шедевров

Добавим в приложение возможность изменять цвет линий и их толщину. Для этого изменим меню, сгенерированное мастером, а также внесем необходимые изменения в уже имеющийся код.
Добавьте переменные в класс MyLine и функцию доступа
Добавления в файл MyLine.h
class MyLine : public CObject
{
public:
DECLARE_SERIAL(MyLine);
MyLine(){}
MyLine(CPoint, CPoint);
~MyLine(void);
private:
// Начальная точка линии
CPoint m_pointBegin;
// Конечная точка линии
CPoint m_pointEnd;
public:
// Функция-член класса MyLine для рисования линий
void Draw(CDC* pDC);
private:
// Массив линий
CObArray m_Lines;
public:
// Преобразование в последовательную форму объекта-массива
void Serialize(CArchive ar);
private:
// Для хранения цвета
COLORREF color;
// Для хранения толщины линий
int widthLine;
public:
// Функция доступа
void Set(COLORREF c, int w)
{
color = c;
widthLine = w;
}
};
Инициализируйте переменные в общем конструктореДобавления в файл MyLine.cpp
MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
{
m_pointBegin = pointFrom;
m_pointEnd = pointTo;
color = RGB(0, 0, 0);
widthLine = 1;
}
Дополните функцию рисования Draw класса MyLine
Добавления в файл MyLine.cpp
// Функция-член класса MyLine для рисования линий
void MyLine::Draw(CDC* pDC)
{
// Создать перо
CPen currPen(PS_SOLID, widthLine, color);// Конструктор пера
// Установить новое перо в качестве рисующего объекта
CPen* pOldPen = pDC->SelectObject(currPen);
// Нарисовать линию
pDC->MoveTo(m_pointBegin);
pDC->LineTo(m_pointEnd);
// Восстановить прежнее перо
pDC->SelectObject(pOldPen);
}
Измените функцию MyLine::Serialize()
Добавления в файл MyLine.cpp
// Преобразование в последовательную форму объекта-массива
void MyLine::Serialize(CArchive ar)
{
CObject::Serialize(ar);
if(ar.IsStoring()) // Сохранить
ar << m_pointBegin << m_pointEnd
<< (DWORD)color << widthLine;
else // Восстановить
ar >> m_pointBegin >> m_pointEnd
>> (DWORD)color >> widthLine;
}
Постройте приложение и проверьте - пока ничего измениться не должноДобавьте в класс документа CDrawSDIDoc массив таблицы цветов, индекс массива таблицы цветов и толщину линии. Код приведен ниже
Добавления в файл DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
................................................
private:
// Массив линий
CObArray m_Lines;
public:
// Добавить линию
MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
// Количество нарисованных линий
int GetLineCount(void);
// Возвращает указатель на объект-линию по индексу
MyLine* GetLine(int index);
virtual void DeleteContents();
private:
int m_iColor; // индекс массива таблицы цветов
int m_iWidth; // толщина линии
public:
// Объявление таблицы цветов
static const COLORREF m_Colors[8];
};
Добавьте инициализацию переменных m_iColor и m_iWidth в функции OnNewDocument()
Добавление в функцию OnNewDocument() файла DrawSDIDoc.cpp
BOOL CDrawSDIDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: add reinitialization code here
// (SDI documents will reuse this document)
// Инициализировать начальный цвет черным
// Толщину линии 1
m_iColor = 0;
m_iWidth = 1;
return TRUE;
}
Добавьте в файл DrawSDIDoc.cpp таблицу цветов сразу после таблицы сообщений
Добавления таблицы цветов в файл DrawSDIDoc.cpp
BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
END_MESSAGE_MAP()
const COLORREF CDrawSDIDoc::m_Colors[] = {
RGB(0, 0, 0), // Black - черный
RGB(0, 0, 255), // Blue - синий
RGB(0, 255, 0), // Green - зеленый
RGB(0, 255, 255), // Cyan - голубой
RGB(255, 0, 0), // Red - красный
RGB(255, 0, 255), // Magenta - сиреневый
RGB(255, 255, 0), // Yellow - желтый
RGB(255, 255, 255) // White - белый
};
Измените функцию AddLine() класса CDrawSDIDoc так, чтобы перед сохранением объекта MyLine в него добавлялась информация о цвете и толщине линии
Добавления в функцию AddLine() файла DrawSDIDoc.cpp
// Добавить линию
MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
{
MyLine* pLine = NULL; // Создали указатель на стеке
try // Попытка
{
// Создать экземпляр объекта-линии на куче
pLine = new MyLine(pointBegin, pointEnd); // Конструктор
// Добавить информацию о текущем цвете и толщине линии
pLine->Set(m_Colors[m_iColor], m_iWidth);
// Добавить указатель на новую линию в массив объектов
m_Lines.Add(pLine);
// Отметить документ как измененный
SetModifiedFlag();
}
catch(CMemoryException* pErr) // Ловушка на недостаток памяти
{
// Сообщили пользователю
AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
// Уничтожим объект-линию, на котором сгенерировалось исключение
if(pLine){
delete pLine;
pLine = NULL;
}
// Удалить объект с исключением
pErr->Delete();
}
return pLine; // Вернуть указатель на объект-линию
}
Чтобы полностью завершить решаемую задачу, введем возможность установки цвета и толщины линии в меню. Для этого необходимо
Через вкладку Resource View вызовите ресурс меню на редактирование
Отредактируйте ресурс меню в соответствии с рисунками, которые получены для режима Edit IDs оболочки

Мастер создал Solution Explorer необходимо файл приложения Resource.h. Файл должен содержать, примерно, следующий код
Файл приложения Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by DrawSDI.rc
//
#define IDD_ABOUTBOX 100
#define IDP_OLE_INIT_FAILED 100
#define IDR_MAINFRAME 128
#define IDR_DrawSDITYPE 129
#define ID_COLOR_BLACK 32771
#define ID_COLOR_BLUE 32772
#define ID_COLOR_GREEN 32773
#define ID_COLOR_CYAN 32774
#define ID_COLOR_RED 32775
#define ID_COLOR_MAGENTA 32776
#define ID_COLOR_YELLOW 32777
#define ID_COLOR_WHITE 32778
#define ID_WIDTH_WIDTH 32779
#define ID_WIDTH_1 32780
#define ID_WIDTH_2 32781
#define ID_WIDTH_3 32782
#define ID_WIDTH_4 32783
#define ID_WIDTH_5 32784
#define ID_WIDTH_6 32785
#define ID_WIDTH_7 32786
#define ID_WIDTH_8 32787
...............................................
Сообщения о нажатиях пунктов меню должен принимать и обрабатывать класс документа. Поэтому именно для него мы добавим соответствующие обработчики. Обработчики сообщений о выборе пунктов меню можно добавить через оболочку двумя способами:
Первым способом является вызов в редакторе меню через команду Add контекстного меню соответствующего пункта

В появившемся диалоговом окне мастера добавления обработчика нужно выбрать класс, который будет обрабатывать сообщение, а также нужно выбрать тип (из двух) сообщения.
Вторым способом является выделение в панели Class View нужного класса, который будет принимать и обрабатывать сообщения от пунктов меню. Затем в панели Properties необходимо установить режим Events и для соответствующих идентификаторов пунктов меню создать обработчики, раскрыв узлы дерева событий

Первый тип сообщения ( COMMAND ) Windows генерирует каждый раз, когда пользователь выбирает соответствующий пункт меню. При этом сообщение передается со значением ID идентификатора пункта в качестве параметра сообщения. Сообщение типа UPDATE_COMMAND_UI не является чистым сообщением Windows, это запрос MFC на обновление внешнего вида отдельных пунктов меню перед их показом пользователю. Мы создадим обработчики для обеих сообщений каждого пункта, добавленного нами в меню.
Откройте вкладку Class View и выберите класс CDrawSDIDoc
В панели Properties установите режим Events и найдите идентификатор ID_COLOR_BLACK
Раскройте узел событий для пункта меню как объекта и для каждого из сообщений COMMAND и UPDATE_COMMAND_UI создайте обработчики, которые заполните так
Обработчики меню для выбора цвета Black
void CDrawSDIDoc::OnColorBlack()
{
m_iColor = 0;
}
void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
// Отобразить пункт меню (если TRUE) с флажком
pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
}
Аналогичным образом создайте обработчики для выбора всех других цветов
Обработчики меню для выбора остальных цветов
void CDrawSDIDoc::OnColorBlue()
{
m_iColor = 1;
}
void CDrawSDIDoc::OnUpdateColorBlue(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iColor == 1 ? TRUE : FALSE);
}
.........................................................
void CDrawSDIDoc::OnColorWhite()
{
m_iColor = 7;
}
void CDrawSDIDoc::OnUpdateColorWhite(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iColor == 7 ? TRUE : FALSE);
}
Подобным образом создайте обработчики для выбора через меню толщины линий рисования
Обработчики меню для выбора толщины линий
void CDrawSDIDoc::OnWidth1()
{
m_iWidth = 1;
}
void CDrawSDIDoc::OnUpdateWidth1(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iWidth == 1 ? TRUE : FALSE);
}
.....................................................
void CDrawSDIDoc::OnWidth8()
{
m_iWidth = 8;
}
void CDrawSDIDoc::OnUpdateWidth8(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
pCmdUI->SetCheck(m_iWidth == 8 ? TRUE : FALSE);
}
Мы видим, что количество обработчиков пунктов меню очень большое. Имеется возможность скомбинировать обработчики меню каждой категории, существенно уменьшив количество обработчиков и сделав код более изящным. Для этого применяют групповые сообщения Windows с параметрами.
Если посмотреть карту сообщений в файле DrawSDIDoc.cpp, то можно увидеть, что обработчики и идентификаторы пунктов меню связаны так
Карта сообщений в файле CDrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND(ID_COLOR_BLACK, OnColorBlack) ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack) ON_COMMAND(ID_COLOR_BLUE, OnColorBlue) ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue) ON_COMMAND(ID_COLOR_GREEN, OnColorGreen) ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen) ON_COMMAND(ID_COLOR_CYAN, OnColorCyan) ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan) ON_COMMAND(ID_COLOR_RED, OnColorRed) ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed) ON_COMMAND(ID_COLOR_MAGENTA, OnColorMagenta) ON_UPDATE_COMMAND_UI(ID_COLOR_MAGENTA, OnUpdateColorMagenta) ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow) ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow) ON_COMMAND(ID_COLOR_WHITE, OnColorWhite) ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite) ON_COMMAND(ID_WIDTH_1, OnWidth1) ON_UPDATE_COMMAND_UI(ID_WIDTH_1, OnUpdateWidth1) ON_COMMAND(ID_WIDTH_2, OnWidth2) ON_UPDATE_COMMAND_UI(ID_WIDTH_2, OnUpdateWidth2) ON_COMMAND(ID_WIDTH_3, OnWidth3) ON_UPDATE_COMMAND_UI(ID_WIDTH_3, OnUpdateWidth3) ON_COMMAND(ID_WIDTH_4, OnWidth4) ON_UPDATE_COMMAND_UI(ID_WIDTH_4, OnUpdateWidth4) ON_COMMAND(ID_WIDTH_5, OnWidth5) ON_UPDATE_COMMAND_UI(ID_WIDTH_5, OnUpdateWidth5) ON_COMMAND(ID_WIDTH_6, OnWidth6) ON_UPDATE_COMMAND_UI(ID_WIDTH_6, OnUpdateWidth6) ON_COMMAND(ID_WIDTH_7, OnWidth7) ON_UPDATE_COMMAND_UI(ID_WIDTH_7, OnUpdateWidth7) ON_COMMAND(ID_WIDTH_8, OnWidth8) ON_UPDATE_COMMAND_UI(ID_WIDTH_8, OnUpdateWidth8) END_MESSAGE_MAP()
Обратите внимание, что каждое Add , имеет в заголовке только два параметра. Но существуют
Возможная таблица сообщений с диапазоном в файле CDrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnUpdateWidthUI) END_MESSAGE_MAP()
Написанные нами ранее в приложении обработчики, на примере выбора одного цвета, имеют вид
Обработчики сообщений файла CDrawSDIDoc.cpp
void CDrawSDIDoc::OnColorBlack()
{
m_iColor = 0;
}
void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
// Отобразить пункт меню (если TRUE) с флажком
pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
}
....................................................
Мастер при создании первого обработчика типа OnColorBlack() не предусмотрел в нем передачу аргументов, однако этот обработчик может принимать аргумент в виде значения идентификатора нажатого пункта меню. Во второй обработчик типа OnUpdateColorBlack(CCmdUI *pCmdUI) в качестве аргумента передается указатель на объект, в свойстве pCmdUI->m_nID которого содержится значение идентификатора соответствующего пункта меню. Учитывая это, мы можем создать комбинированные обработчики для каждой категории меню.
Вначале расширим категорию меню Width добавлением новых элементов, позволяющих задавать существенную толщину линий.
Дополнительные пункты меню категории Width | |
|---|---|
| Название пункта | Толщина пера |
Medium (Средняя) |
16 |
(Толстое) |
24 |
Very (Очень толстое) |
32 |
Через панель Resource View вызовите редактор меню
Добавьте в конец категории Width три новых пункта так, чтобы вначале названия пунктов были введены цифрами 16, 24, 32. Это нужно для того, чтобы редактор автоматически сформировал имена идентификаторов похожими на предыдущие пункты
Измените названия уже сгенерированных пунктов на слова, приведенные в таблице вышеДолжно получиться что-то подобное в режиме Edit IDs редактора меню
Теперь займемся сокращением количества обработчиков пунктов меню. Вспомним, что интересующие нас обработчики прописаны в трех местах приложения:
CDrawSDIDoc в файле DrawSDIDoc.hDrawSDIDoc.cppCDrawSDIDoc в начале файла DrawSDIDoc.cppДля того, чтобы нам не организовывать в комбинированном обработчике условия проверки значений передаваемых идентификаторов, необходимо учесть, чтобы значения идентификаторов в каждой из категорий шли в порядке возрастания с шагом единица. Для этого:
Откройте через панель Solution Explorer файл Resource.h
Он должен иметь примерно такой вид
Идетификаторы пунктов меню в файле Resource.h ............................................... #define ID_COLOR_BLACK 32771 #define ID_COLOR_BLUE 32772 #define ID_COLOR_GREEN 32773 #define ID_COLOR_CYAN 32774 #define ID_COLOR_RED 32775 #define ID_COLOR_MAGENTA 32776 #define ID_COLOR_YELLOW 32777 #define ID_COLOR_WHITE 32778 #define ID_WIDTH_WIDTH 32779 #define ID_WIDTH_1 32780 #define ID_WIDTH_2 32781 #define ID_WIDTH_3 32782 #define ID_WIDTH_4 32783 #define ID_WIDTH_5 32784 #define ID_WIDTH_6 32785 #define ID_WIDTH_7 32786 #define ID_WIDTH_8 32787 #define ID_WIDTH_16 32788 #define ID_WIDTH_24 32789 #define ID_WIDTH_32 32790 ...............................................
В файле DrawSDIDoc.h уберите прототипы всех созданных ранее обработчиков для категорий меню Color и Width и введите прототипы комбинированных обработчиков отдельно для каждой категории в конец класса
Код должен выглядеть так
Прототипы комбинированных обработчиков в файле DrawSDIDoc.h
class CDrawSDIDoc : public CDocument
{
................................................
public:
// Объявление таблицы цветов
static const COLORREF m_Colors[8];
// Комбинированные обработчики Color и Width
afx_msg void OnColorCommand(UINT nID);
afx_msg void OnUpdateColorUI(CCmdUI *pCmdUI);
afx_msg void OnWidthCommand(UINT nID);
afx_msg void OnUpdateWidthUI(CCmdUI *pCmdUI);
};
В файле DrawSDIDoc.cpp карта сообщений после редактирования должна выглядеть так
Карта сообщений в файле DrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) END_MESSAGE_MAP()
Уберите в файле DrawSDIDoc.cpp код всех прежних обработчиков для категорий Color и Width и введите новый код обобщенных обработчиков так
Новый код обработчиков сообщений в файле DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Color
void CDrawSDIDoc::OnColorCommand(UINT nID)
{
m_iColor = nID - ID_COLOR_BLACK;
}
// Срабатывает для каждого пункта перед раскрытием меню Color
void CDrawSDIDoc::OnUpdateColorUI(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
bool check = pCmdUI->m_nID == m_iColor + ID_COLOR_BLACK;
// Какой цвет установлен, такой пункт
// меню и отобразить с флажком
pCmdUI->SetCheck(check ? TRUE : FALSE);
}
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
}
// Срабатывает для каждого пункта перед раскрытием меню Width
void CDrawSDIDoc::OnUpdateWidthUI(CCmdUI *pCmdUI)
{
// Отобразить пункт меню (по умолчанию TRUE) как доступный
pCmdUI->Enable();
bool check;
int pos = 0;
if(m_iWidth <= 8)
pos = m_iWidth - 1; //0-7
else if(m_iWidth == 16)
pos = m_iWidth - 8; //8
else if(m_iWidth == 24)
pos = m_iWidth - 15;//9
else if(m_iWidth == 32)
pos = m_iWidth - 22;//10
// Какая толщина установлена, такой пункт
// меню и отобразить с флажком
check = pCmdUI->m_nID == pos + ID_WIDTH_1;
pCmdUI->SetCheck(check ? TRUE : FALSE);
}
Постройте приложение и проверьте его работоспособность
Оформите только About, но ни в коем случае не оформляйте строку заголовка окна и не русифицируйте ресурс String Table. Может смениться кодировка и придется тексты интерфейсов переколачивать заново вручнуюВ этой части мы наделим наше приложение еще большими возможностями, добавив панель инструментов и строку состояния.
Строкой состояния в и документах называется информационное окно, расположенное, как правило, в нижней части рамки приложения и отражающее поясняющий текст (длинные подсказки) о назначении пунктов меню или кнопок панели инструментов. Панелями инструментов называются окна, содержащие наборы кнопок быстрого доступа к функциональным возможностям приложения.
Панели инструментов, как и меню, делают контекстными к данному режиму приложения, т.е. они умеют к нему подстраиваться. Панели инструментов тесно связаны с меню, поскольку предназначены для тех же целей. Для удобства панели инструментов могут делать свободно плавающими ( floating ) или приклеивающимися ( ) к рамке окна приложения. Панели инструментов снабжают короткими всплывающими подсказками о назначении кнопок. Панели инструментов и строка состояния относятся к группе элементов графического интерфейса пользователя ( GUI ).
Откройте вкладку Resource View и через контекстное меню на узле Toolbar добавьте новый ресурс Toolbar, нажав кнопку New

Появится окно графического редактора с заготовкой панели инструментов

Одновременно в узле Toolbar вкладки Resource View приложения появится новый ресурс с автоматически сгенерированным идентификатором

Присвойте идентификатору новое имя IDR_TBCOLOR
Присвойте пиктограммы каждой кнопке, т. е. заполните восемь кнопок новой панели восемью доступными в графическом редакторе оболочки цветами
Чтобы не работать с карандашом, который включен по умолчанию, можно переключить инструмент рисования на кисть, предварительно вызвав через контекстное меню панели инструментов оболочки панель

Убедитесь, что ресурс новой панели русифицирован, чтобы можно было заполнять в него тексты на русском
Выделите по очереди каждую кнопку новой панели и через вкладку оболочки Properties задайте свойства кнопок в соответствии с таблицей
Свойство Prompt состоит из длинной (отображаемой в строке состояния) и короткой (всплывающей, когда пользователь задерживает курсор над кнопкой на время около 2 секунд) подсказок, разделенных управляющим символом "новая строка".
ID |
Prompt |
|---|---|
ID_COLOR_BLACK |
Цвет рисования черный \n Черный |
ID_COLOR_BLUE |
Цвет рисования синий \n Синий |
ID_COLOR_GREEN |
Цвет рисования зеленый \n Зеленый |
ID_COLOR_CYAN |
Цвет рисования бирюзовый \n Бирюзовый |
ID_COLOR_RED |
Цвет рисования красный \n Красный |
ID_COLOR_MAGENTA |
Цвет рисования сиреневый \n Сиреневый |
ID_COLOR_YELLOW |
Цвет рисования желтый \n Желтый |
ID_COLOR_WHITE |
Цвет рисования белый \n Белый |
Убедитесь, и это очень важно, что мы только что присвоили кнопкам панели инструментов IDR_TBCOLOR точно такие же идентификаторы, что и соответствующим пунктам меню категории ColorОни и должны совпадать, поскольку предназначены для одного и того же. А это значит, что обработчики команд специально для панели инструментов нам создавать не нужно - для одинаковых идентификаторов будут использованы обработчики команд меню.
По умолчанию размер кнопок принят 16x15 пикселов. Использование изображений, размеры которых отличаются от принятых по умолчанию, вполне возможно, но об этом необходимо уведомить объект панели инструментов при помощи функции CToolBar::SetSizes().
Закройте окно графического редактора, чтобы случайно не испортить подготовленную панель инструментовЛюбая панель инструментов хранится в виде растрового рисунка формата bmp в каталоге res проекта. Ее всегда можно открыть и отредактировать другим внешним редактором.
Выделите во вкладке В нашем случае файл имеет имя toolbar1.bmp. Открытый во внешнем редакторе он имеет вид
Пока мы только заготовили ресурс панели инструментов. Теперь подсоединим его к приложению. Поскольку панели инструментов присоединяются к рамке, необходимо начать добавление и изменение кода именно в модуле рамки. Рамкой нашего приложения управляет класс CMainFrame, являющийся производным от базового класса CFrameWnd и расположенный в файлах MainFrm.h и MainFrm.cpp.
У нас уже есть одна панель инструментов, автоматически сгенерированная мастером при создании заготовки приложения в самом начале выполнения лабораторной работы. В файле MainFrm.cpp можно увидеть, что она создается и прикрепляется к рамке окна приложения в функции OnCreate() - члене класса CMainFrame
Функция OnCreate() файла MainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
return 0;
}
В этой функции мы, подобным образом, создадим и прикрепим нашу новую панель. Но вначале для этого добавим в класс CMainFrame новую переменную типа CToolBar, которая и будет содержать новую панель. Эта переменная должна быть защищенной и назовем ее m_wndColorBar.
Щелкните правой кнопкой мыши на классе CMainFrame во вкладке Class View, вызовите мастер Add Variable, который заполните так

Мастер добавит в объявление класса следующий код
Объявление переменной-панели в классе CMainFrame файла MainFrm.h
class CMainFrame : public CFrameWnd
{
........................................................
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
// Панель инструментов для цвета
CToolBar m_wndColorBar;
};
Добавьте к функции OnCreate() класса CMainFrame следующий код
Добавления в функцию CMainFrame::OnCreate() файла MainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0){
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to be dockable
// Что делать: Удалите эти три строки, если инструментальную панель не нужно прикреплять
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
Постройте приложение и убедитесь, что панель ведет себя как нужно и переключает цвета точно также, как это делается через меню ColorЭто происходит потому, что идентификаторы ресурсов кнопок и пунктов меню одни и те же.
Класс панели инструментов CToolBar является лишь одним из нескольких классов, которые создают окна, пригодные для ввода команды и вывода информации. Базовым классом для всей этой группы элементов управления CToolBar, CStatusBar, CControlBar, CReBar, COleResizerBar, известных под общим названием панели управления, является класс CControlBar, производный от класса CWnd. Это очень важно, поскольку означает, что все панели инструментов как наследники класса CWnd, имеют все необходимые API -функции для работы с окнами.
Класс CToolBar является наследником класса CControlBar, который в свою очередь является наследником класса CWnd. Соответственно класс CToolBar имеет все необходимые функциональные возможности для успешного управления программным способом панелями инструментов. Рассмотрим некоторые методы, в том числе и унаследованные, класса CToolBar.
Некоторые методы класса CToolBar | |
|---|---|
| Метод | Пояснения |
CreateEx() |
Создает панель инструментов |
LoadToolBar() |
Загружает ресурс панели инструментов |
CommandToIndex() |
Возражает индекс кнопки панели инструментов |
GetButtonStyle() |
Возвращает целочисленное значение стиля кнопки по ее индексу |
SetButtonStyle() |
Устанавливает стиль кнопки по ее индексу |
GetItemID() |
Возвращает идентификатор ресурса кнопки по ее индексу |
GetItenRect() |
Возвращает координаты размещения и размер кнопки |
GetButtonText() |
Возвращает содержимое текстовой подсказки кнопки по ее индексу в формате |
SetButtonText() |
Устанавливает для кнопки с индексом ее текстовую подсказку |
GetBarStyle() |
Возвращает стиль (переменную типа DWORD ), установленный ранее для панели инструментов |
SetBarStyle() |
Устанавливает стиль для панели инструментов |
GetCount() |
Возвращает количество элементов, расположенных на панели инструментов |
IsFloating() |
Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов плавающей |
IsVisible() |
Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов видимой |
GetDockingFrame() |
Возвращает указатель на фреймовое окно, которое является родительским для панели инструментов |
EnableDocking() |
Посылает уведомление панели инструментов и фреймовому окну о том, что пользователям разрешено самостоятельно перемещать панель инструментов и располагать ее в других частях фреймового окна. |
ShowWindow() |
Как член класса CWnd управляет видимостью панели инструментов как окном. При этом фон исчезнувшей панели инструментов не удается скрыть |
ShowControlBar() |
Управляет видимостью панели инструментов более умело с сокрытием и фона. Применяется совместно с функцией RecalcLayout() |
RecalcLayout() |
Заново создает макет в пределах рамки окна, т. е. перерасполагает заново все панели инструментов, строки состояния и все остальное в пределах рамки окна. Кроме того, данная функция передвигает созданную по умолчанию панель инструментов вверх и вниз при переключении состояния панели скрыть/отобразить |
GetButtonInfo() |
Возвращает информацию о кнопке |
SetButtonInfo() |
Устанавливает идентификатор, стиль и индекс пиктограммы для кнопки панели инструментов |
GetToolBarCtrl() |
Возвращает ссылку на объект класса CToolBarCtrl |
LoadBitmap() |
Загружает пиктограмму для панели инструментов |
SetBitmap() |
Устанавливает писктограмму для новой панели инструментов |
SetButtons() |
Устанавливает идентификаторы для кнопок панели инструментов |
SetHeight() |
Устанавливает высоту панели инструментов |
SetSizes() |
Устанавливает размеры кнопок панели инструментов |
Добавленный нами ранее в функцию CMainFrame::OnCreate() фрагмент кода
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
содержит две функции-члена класса CToolBar
CreateEx() - создает панель инструментов с нужными свойствами в окне-рамке класса CMainFrame для экземпляра CToolBar m_wndColorBar CreateEx() CRect, задающего желаемую ширину и высоту панели инструментов. По умолчанию для всех измерений прямоугольника задается нуль, а это означает, что фактические размеры панели инструментов жестко не устанавливаютсяID ) дочернего окна панели инструментов. По умолчанию он равен константе AFX_IDW_TOOLBAR, но при необходимости можно указать любой необходимый идентификатор| Стили размещения кнопок относительно окна (второй параметр) | |
|---|---|
| Стиль | Описание |
TBSTYLE_ALTDRAG |
Позволяет перемещать панель инструментов (перетаскивать ее при нажатии кнопки Alt ) |
TBSTYLE_CHECK |
Задает режим поведения кнопок панели как флажков. Получаются кнопки с фиксацией, которые залипает при щелчке на них. Для возврата в исходное состояние необходимо щелкнуть повторно |
TBSTYLE_GROUP |
Кнопки панели рассматриваются как группа |
TBSTYLE_CUSTOMERASE |
Генерирует сообщение NM_CUSTOMDROW при стирании фона панели инструментов и кнопок, позволяя выбирать, когда и каким образом управлять процессом стирания фона |
TBSTYLE_FLAT |
Создает плоскую панель инструментов. Текст кнопки располагается под растровым изображением |
TBSTYLE_LIST |
Текст кнопки располагается справа от растрового изображения |
TBSTYLE_REGISTERDROP |
Используется при перетаскивании и опускании объектов на кнопки панели инструментов |
TBSTYLE_TOOLTIPS |
Создает всплывающую подсказку (элемент управления ToolTip ), которая может использоваться для отображения текста описания кнопок |
TBSTYLE_TRANSPARENT |
Создает прозрачную панель инструментов |
TBSTYLE_WRAPABLE |
Создает панель инструментов, которая может иметь несколько рядов кнопок |
| Стили самой панели инструментов как окна (третий параметр) | |
|---|---|
| Стиль | Описание |
WS_CHILD |
Панель инструментов создается как дочернее окно |
WS_VISIBLE |
Создается видимая сразу панель инструментов |
CBRS_ALIGN_TOP |
Позволяет прикреплять панель инструментов к верхней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_BOTTOM |
Позволяет прикреплять панель инструментов к нижней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_LEFT |
Позволяет прикреплять панель инструментов к левой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_RIGHT |
Позволяет прикреплять панель инструментов к правой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_ANY |
Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна |
CBRS_BORDER_TOP |
Если панель инструментов не прикреплена, помещает границу на верхний край панели инструментов |
CBRS_BORDER_BOTTOM |
Если панель инструментов не прикреплена, помещает границу на нижний край панели инструментов |
CBRS_BORDER_LEFT |
Если панель инструментов не прикреплена, помещает границу на левый край панели инструментов |
CBRS_BORDER_RIGHT |
Если панель инструментов не прикреплена, помещает границу на правый край панели инструментов |
CBRS_FLOAT_MULTI |
Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов |
CBRS_TOOLTIPS |
Отображает всплывающие подсказки ( ToolTips ) для кнопок панелей инструментов |
CBRS_FLYBY |
Одновременно обновляет текст сообщения строки состояния и текст всплывающей подсказки ToolTip |
CBRS_GRIPPER |
Рисует область захвата на панели инструментов при ее перемещении |
CBRS_SIZE_DYNAMIC |
Позволяет изменять размеры окна панели инструментов |
Рассмотрим еще один фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0)
{
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
Вначале применяется функция-член CommandToIndex(), которая возвращает индекс элемента управления, под которым он числится в панели инструментов как экземпляре класса CToolBar. Учитывая, что кнопка с таким идентификатором имеет наименьшее значение ID при графической разработки панели инструментов, в экземпляре класса она имеет наименьшее значение индекса (значение 0).
Индексы всех кнопок панели как элементов объекта m_wndColorBar, идут в возрастающем порядке, поскольку в таком же порядке идут значения их идентификаторов в ресурсе панели. Значит можно перебрать все элементы экземпляра класса по порядку в цикле и установить для каждого необходимый стиль. Стиль устанавливается второй функцией-членом SetButtonStyle(). В данном случае устанавливается стиль поведения радиокнопок в группе. Список возможных стилей кнопок приведен в таблице.
| Список возможных индивидуальных стилей кнопок панели инструментов | |
|---|---|
| Стиль | Описание |
TBBS_AUTOSIZE |
Вычисляет ширину кнопки в зависимости от текста кнопки |
TBBS_BUTTON |
Создает стандартную кнопку (без фиксации) |
TBBS_CHECKBOX |
Создает кнопку, которая действует подобно флажку. Такая кнопка переключается из состояния "выбрана" в состояние "отменить выбор" и наоборот. Иными словами, это кнопка с фиксацией, которая залипает при щелчке на ней. Для возврата в исходное состояние необходимо щелкнуть на ней еще раз |
TBBS_CHECKGROUP |
Создает кнопку, которая действует подобно радиокнопкам (селективным кнопкам) - остается выбранной до выбора другой кнопки в группе. Фактически это комбинация стилей TBSTYLE_CHECK и TBSTYLE_GROUP |
TBBS_DROPDOWN |
Создается кнопку, представляющую собой раскрывающийся список |
TBBS_GROUP |
Создает кнопку, которая остается выбранной, пока не будет выбрана другая кнопка из группы |
TBBS_NOPREFIX |
Не связывает префикс |
TBBS_SEPARATOR |
Создает разделитель (небольшой интервал) между кнопками с обеих сторон |
Рассмотрим следующий фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!! EnableDocking(CBRS_ALIGN_ANY); DockControlBar(m_wndToolBar); DockControlBar(m_wndColorBar);// Наша!!!
По умолчанию положение панели инструментов можно изменить только программно. Но можно разрешить пользователям самостоятельно перемещать панели инструментов и располагать их в других частях фреймового окна. О том, что такая возможность разрешена, необходимо уведомить как саму панель инструментов, так и фреймовое окно. Для этого и вызываются функции CToolBar::EnableDocking() и CFrameWnd::EnableDocking() (разрешить прикрепление).
Обязательно фрейму и панели инструментов нужно передавать одинаковые стили, иначе прикрепить панель инструментов к фреймовому окну не удастся.
Последняя функция DockControlBar(m_wndColorBar) кода - это функция фреймового окна, дающая команду изначально прикрепить панель инструментов, адрес на которую ей передается, к фрейму перед первым появлением окна на экране.
Параметры, передаваемые функциями EnableDocking() | |
|---|---|
| Стиль | Описание |
CBRS_ALIGN_TOP |
Прикрепляет панель инструментов к верхней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_BOTTOM |
Прикрепляет панель инструментов к нижней стороне рабочей области обрамляющего окна |
CBRS_ALIGN_LEFT |
Прикрепляет панель инструментов к левой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_RIGHT |
Прикрепляет панель инструментов к правой стороне рабочей области обрамляющего окна |
CBRS_ALIGN_ANY |
Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна |
CBRS_FLOAT_MULTI |
Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов |
| 0 | Не позволяет прикреплять панель инструментов к рамке |
Расширим меню View нашего приложения и добавим в него пункт включения/выключения панели инструментов IDR_TBCOLOR. Затем нужно создать код отображения и сокрытия панели инструментов. Здесь можно применить два варианта, первый из которых использует методы базового класса CWnd панели инструментов, второй - методы класса CToolBar.
Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов содержит флаг WS_VISIBLE стиля ее окна. Для этого с помощью функции CWnd::GetStyle() необходимо получить сначала значение стиля окна панели инструментов, а затем сравнить его по маске со значением флага WS_VISIBLE.
// Выяснить текущее состояние видимости bool bVisible = (m_wndColorBar.GetStyle() WS_VISIBLE);
На основании информации о том, скрыта панель инструментов или отображена, можно применить соответствующее действие
// Показать или скрыть панель инструментов int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL; m_wndColorBar.ShowWindow(nShow);
После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()
// Переконфигурировать элементы всех панелей инструментов, // строки состояния и всего остального в пределах фрейма RecalcLayout();
Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов можно получить с помощью функции CControlBar::IsVisible(). Затем для отображения или скрытия панели инструментов можно применить функцию фреймового окна CFrameWnd::ShowControlBar(). Прототип этой функции
void ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay);
pBar - адрес переменной управляемой панели инструментовbShow - булева переменная, которая определяет, показывать или скрывать панель инструментов ( TRUE - показать, FALSE - скрыть)TRUE ) или немедленно ( FALSE )После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()
// Переконфигурировать элементы всех панелей инструментов, // строки состояния и всего остального в пределах фрейма RecalcLayout();
Для добавления возможности
Откройте ресурс меню на редактирование, русифицируйте его и добавьте в категорию
View ресурса меню ID (идентификатор)ID_VIEW_COLORBARCaption (надпись)Color BarPrompt (подсказка)\n Показать/ Скрыть
Во вкладке Class View выделите класс CMainFrame
Во вкладке Properties установите режим Events
Найдите узел событий ID_VIEW_COLORBAR, раскройте его и добавьте обработчики для событий COMMAND и UPDATE_COMMAND_UI
Заполните обработчики такОбработчики меню View пункта Color Bar
void CMainFrame::OnViewColorbar()
{
/***Студент Жук! Чтобы раскомментировать блок, добавьте спереди один слэш***
// Вариант 1
// Выяснить текущее состояние видимости
bool bVisible = (m_wndColorBar.GetStyle() WS_VISIBLE);
// Показать или скрыть панель инструментов
int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
m_wndColorBar.ShowWindow(nShow);
//*****************************************************/
//***Студент Жук! Чтобы закомментировать блок, уберите спереди один слэш***
// Вариант 2
// Показать или скрыть панель инструментов
ShowControlBar(m_wndColorBar
,!m_wndColorBar.IsVisible()
,FALSE);
//*****************************************************/
// Переконфигурировать элементы всех панелей инструментов,
// строки состояния и всего остального в пределах фрейма
RecalcLayout();// Можно и закомментировать, я разницу не нашел!
}
void CMainFrame::OnUpdateViewColorbar(CCmdUI *pCmdUI)
{
// В зависимости от видимости панели отобразить
// пункт меню с отметкой флажком или без флажка
pCmdUI->SetCheck(m_wndColorBar.IsVisible());
}
Проследите за тем, чтобы обработчики были членами класса CMainFrame и попали в MainFrm.cpp.В первом обработчике реализованы два варианта управления отображением панели, рассмотренные выше. Один из вариантов закомментирован. Проверьте оба варианта, попеременно раскомментируя один и закомментируя другой. Обратите внимание на способ быстрого комментирования нужного блока, авось пригодится где-нибудь в "Силиконовой долине", где время - деньги.
Добавим к панели инструментов комбинированный список, отвечающий за установку толщины пера рисования.
Прежде всего нужно вручную отредактировать исходный файл ресурсов, имеющий имя проекта с расширением .rc, чтобы зарезервировать место для размещения комбинированного списка. Сделать это автоматически с помощью оболочки не удается.
Перейдите на вкладку
Из контекстного меню этого ресурса откройте окно
Выберите текстовый редактор Source Code (, как показано на рисунке, и нажмите кнопку Open. Если появится окно сообщения с вопросом, хотим ли мы закрыть текущий редактор ресурсов (вкладку Resource View ),

щелкните по кнопке Да ( Yes )
А теперь будьте очень осторожными и случайно не испортите файл ресурсов, иначе это приведет к выводу из строя всего проекта приложения!!! (лучше вначале создать его копию)
Найдите раздел панели инструментов IDR_TBCOLOR и добавьте две строки SEPARATOR (разделитель) как показано нижеПрямое редактирование файла ресурсов DrawSDI.rc
....................................................
///////////////////////////////////////////////////////////
//
// Toolbar
//
IDR_TBCOLOR TOOLBAR 16, 15
BEGIN
BUTTON ID_COLOR_BLACK
BUTTON ID_COLOR_BLUE
BUTTON ID_COLOR_GREEN
BUTTON ID_COLOR_CYAN
BUTTON ID_COLOR_RED
BUTTON ID_COLOR_MAGENTA
BUTTON ID_COLOR_YELLOW
BUTTON ID_COLOR_WHITE
SEPARATOR
SEPARATOR
END
...................................................
Закройте окно редактора с сохранением измененийТеперь, если открыть вкладку Resource View, то можно увидеть предупреждение, что файл ресурсов редактировался напрямую через прочие редакторы

Сверните узел дерева с изображением папки и разверните вновь - предупреждение исчезнетПрямое редактирование файла ресурсов было выполнено по трем причинам:
Постройте приложение и обратите внимание, что в конце панели инструментов выбора цвета появились два вертикальных разделителя - это и есть добавленные нами сепараторыТеперь нужно добавить текстовые строки как ресурс, которые будут загружены в комбинированный список при его создании. Следует помнить, что каждая строка комбинированного списка является отдельным ресурсом и должна иметь уникальный идентификатор с уникальным значением. Текстовые строки добавим через таблицу строк String Table ресурса приложения.
Через вкладку Resource View откройте узел String TableЕсли вы не русифицировали этот ресурс и одновременно для кнопок панели инструментов вводили русские сообщения, то в файле ресурсов оболочка создала два раздела, относящиеся к таблице строк. Один из разделов с последними изменениями имеет вид
Дополнительный раздел таблицы строк в DrawSDI.rc
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
ID_COLOR_BLACK "Цвет рисования черный\nЧерный"
ID_COLOR_BLUE "Цвет рисования синий\nСиний"
ID_COLOR_GREEN "Цвет рисования зеленый\nЗеленый"
ID_COLOR_CYAN "Цвет рисования бирюзовый\nБирюзовый"
ID_COLOR_RED "Цвет рисования красный\nКрасный"
ID_COLOR_MAGENTA "Цвет рисования сиреневый\nСиреневый"
ID_COLOR_YELLOW "Цвет рисования желтый\nЖелтый"
ID_COLOR_WHITE "Цвет рисования белый\nБелый"
END
STRINGTABLE
BEGIN
ID_VIEW_COLORBAR "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
END
Двойным щелчком мыши на русифицированном ресурсе вызовите редактор таблицы строк. Добавляете новые строки в соответствии с таблицей, приведенной ниже. При этом следите за тем, чтобы значения поля
View ресурса меню ID Caption IDS_WIDTH_1IDS_WIDTH_2IDS_WIDTH_3IDS_WIDTH_4IDS_WIDTH_5IDS_WIDTH_6IDS_WIDTH_7IDS_WIDTH_8IDS_WIDTH_16MediumIDS_WIDTH_24Thick IDS_WIDTH_32Very Thick Должно получиться нечто подобное

Теперь в файле ресурсов соответствующий раздел выглядит так
Раздел таблицы строк в DrawSDI.rc после добавления
//////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
ID_COLOR_BLACK "Цвет рисования черный\nЧерный"
ID_COLOR_BLUE "Цвет рисования синий\nСиний"
ID_COLOR_GREEN "Цвет рисования зеленый\nЗеленый"
ID_COLOR_CYAN "Цвет рисования бирюзовый\nБирюзовый"
ID_COLOR_RED "Цвет рисования красный\nКрасный"
ID_COLOR_MAGENTA "Цвет рисования сиреневый\nСиреневый"
ID_COLOR_YELLOW "Цвет рисования желтый\nЖелтый"
ID_COLOR_WHITE "Цвет рисования белый\nБелый"
END
STRINGTABLE
BEGIN
ID_VIEW_COLORBAR "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
IDS_WIDTH_1 "1"
IDS_WIDTH_2 "2"
IDS_WIDTH_3 "3"
IDS_WIDTH_4 "4"
IDS_WIDTH_5 "5"
IDS_WIDTH_6 "6"
IDS_WIDTH_7 "7"
IDS_WIDTH_8 "8"
IDS_WIDTH_16 "Medium"
IDS_WIDTH_24 "Thick"
IDS_WIDTH_32 "Very Thick"
END
Прежде всего создадим идентификатор ID, который далее при создании списка программно система свяжет с самим списком и далее под этим ID будет учитывать этот список во всех других функциях управления списком.
Выберите вкладку
Нажмите кнопку Для программной поддержки комбинированного списка создадим переменную m_ctrlWidth типа CComboBox как член класса CMainFrame. Для этого:
Во вкладке
Убедитесь в том, что мастер добавил переменную именно в класс CMainFrame файла MainFrm.hДалее нам нужно будет выполнить такие действия:
ID заглушки-заполнителя комбинированного списка на панели инструментов равными соответственно ширине и идентификатору ID самого комбинированного спискаЧтобы упорядочить этот процесс и не захламлять функцию OnCreate() класса фреймового окна CMainFrame не свойственным ей кодом, создадим отдельную функцию-член фрейма, а в функции OnCreate() разместим вызов этой вспомогательной упаковки кода. Вспомогательную функцию назовем CreateColorBar().
Выделите во вкладке Class View класс CMainFrame
Вызовите мастер
Перенесите (это значит полностью уберите со старого места!!!) соответствующий код из функции OnCreate() файла MainFrm.cpp в эту новую функцию (файл тот же!), а также добавьте новый код для создания комбинированного спискаНиже приведен листинг функции, где красным выделен перенесенный код, а синим - добавленный новый код
Вспомогательная функция в файле MainFrm.cpp
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return FALSE; // fail to create
}
// Найти кнопку ID_COLOR_BLACK на инструментальной панели
int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
if(iTBCtrlID >= 0){
// Установить все кнопки так, чтобы
// они действовали как радиокнопки в группе
for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
}
// Добавить комбинированный список
int nWidth = 100;
int nHeight = 125;
// Выбрать конфигурацию заглушки-заполнителя комбинированного списка
m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
// Получить высоту панели инструментов выбора цвета
CRect rect;
m_wndColorBar.GetItemRect(9, rect);
rect.bottom = rect.top + nHeight;
// Создать комбинированный список
m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
,rect, m_wndColorBar, IDC_CBWIDTH);
// Заполнить комбинированный список текстами из ресурса String Table
CString str;
UINT id = IDS_WIDTH_1; // Важно, что значения идентификаторов
// расположены в порядке возрастания с шагом 1,
// поэтому можно организовать цикл
for(; id - IDS_WIDTH_1 < 11; id++) // Перебираем 11 элементов
if(str.LoadString(id))
m_ctrlWidth.AddString((LPCTSTR)str);
// Устанавливаем первый элемент видимым
m_ctrlWidth.SetCurSel(0);
return TRUE;
}
В приведенном коде используется функция
// Выбрать конфигурацию заглушки-заполнителя комбинированного списка m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
Первый параметр (9) - означает индекс размещаемого в панели элемента ( начинается с нуля ). Этим модифицируемым элементом-заглушкой будет ранее введенный нами последний сепаратор в файле ресурсов. Фрагмент файла ресурсов таков
Идентификаторы элементов строки состояния в файле ресурсов
/////////////////////////////////////////////////////////////////////////////
//
// Toolbar
//
IDR_TBCOLOR TOOLBAR 16, 15
BEGIN
BUTTON ID_COLOR_BLACK
BUTTON ID_COLOR_BLUE
BUTTON ID_COLOR_GREEN
BUTTON ID_COLOR_CYAN
BUTTON ID_COLOR_RED
BUTTON ID_COLOR_MAGENTA
BUTTON ID_COLOR_YELLOW
BUTTON ID_COLOR_WHITE
SEPARATOR
SEPARATOR // заглушка-заполнитель как элемент с индексом 9
END
Второй параметр - идентификатор ресурса комбинированного списка, для которого резервируется место на панели и за которым в дальнейшем функция Create() закрепит комбинированный список как приемник сообщений.
Третий параметр - стандартная константа TBBS_SEPARATOR библиотеки MFC, представляющая разделитель панели инструментов.
Четвертый аргумент - ширина заглушки-заполнителя для резервируемого места под комбинированный список.
Вновь дополните функцию CMainFrame::OnCreate() кодом вызова только что созданной упаковки CMainFrame::CreateColorBar(), после чего функция должна выглядеть такВызов вспомогательной функции из OnCreate()
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// Добавить инструментальную панель выбора цвета
// вместе с комбинированным списком выбора толщины пера
if(!CreateColorBar()){
TRACE0("Failed to create color toolbar\n");
return -1;
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
Постройте приложение и убедитесь, что список раскрывается, но ничего пока не делаетМы создали комбинированный список как немодальное окно с помощью функции Create, которая выглядит так
Функция Create в файле MainFrm.cpp
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
...............................................................
// Создать комбинированный список
m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
,rect, m_wndColorBar, IDC_CBWIDTH);
.......................................................................
return TRUE;
}
Первым аргументом этой функции является сочетание стилей комбинированного списка, определяющих его внешние свойства и поведение. Часть флажков относятся к оконным стилям WS_ (window style) , поскольку комбинированный список наследует класс CWnd, а часть флажков специфицирует стиль только списка ( CBS_ - ComboBox Style ). Перечислим значения флажков стилей списка
| Стили комбинированного списка | |
|---|---|
| Стиль | Описание |
WS_CHILD |
Указывает, что данное окно является дочерним (обязательный стиль) |
WS_VISIBLE |
Делает комбинированный список изначально видимым |
WS_DISABLED |
Отключает комбинированный список |
WS_VSCROLL |
Добавляет вертикальную прокрутку к раскрывающемуся списку |
WS_HSCROLL |
Добавляет горизонтальную прокрутку к раскрывающемуся списку |
WS_GROUP |
Группирует элементы управления |
WS_TABSTOP |
Включает комбинированный список в последовательность перехода по клавише Tab |
CBS_AUTOHSCROLL |
Обеспечивает прокрутку текста в поле редактирования при его заполнении |
CBS_DROPDOWN |
Подобен стилю CBS_SIMPLE, но не отображает список до тех пор, пока пользователь не выберет пиктограмму, следующую за полем редактирования |
CBS_DROPDOWNLIST |
Подобен стилю CBS_DROPDOWN. Однако заменяет поле редактирования статическим текстом, который отображает текущее выбранное значение элемента списка |
CBS_HASSTRINGS |
Указывает, что за перерисовку комбинированного списка отвечает владелец. Элементы в окне списка состоят из строк |
CBS_OEMCONVERT |
Текст, выводимый для отображения на экране в поле редактирования, конвертируется из набора символов ASCII в набор OEM, а затем, при передаче данных из комбинированного списка в программу - опять из набора OEM в набор символов ASCII. Набор отображаемых символов ASCII используется в MS-DOS, а OEM - в Windows. Символы с кодами 32-127 обычно совпадают в обеих системах. Другие же символы, используемые национальными кодировками, требуют конвертации. Для правильного отображения текста на экране в консольном режиме для этого используется функция CharToOem(char* strASCII, char* strOEM) |
CBS_OWNERDRAWFIXED |
Указывает, что владелец списка отвечает за перерисовку содержимого. Содержимое списка имеет фиксированную высоту |
CBS_OWNERDRAWVARIABLE |
Указывает, что владелец списка отвечает за перерисовку содержимого. Высота содержимого списка может изменяться |
CBS_SIMPLE |
Все время отображает список на экране |
CBS_SORT |
Автоматически сортирует строки в списке |
CBS_DISABLENOSCROLL |
Если список не содержит достаточно элементов для прокрутки, все равно отображается полоса прокрутки, хоть и в отключенном ( Disabled ) виде |
CBS_NOINTEGRALHEIGHT |
Указывает, что комбинированный список имеет точно указанный размер |
При нажатии на кнопки панели инструментов нам не нужно передавать из класса CMainFrame, которому принадлежит панель инструментов, значение выбранного цвета пера в закрытую переменную m_iColor класса CDrawSDIDoc. Это делается через обработчики событий меню, которые перехватываются классом CDrawSDIDoc, поскольку кнопки панели и пункты меню Color имеют одинаковые идентификаторы.
В случае с комбинированным списком нам нужно сделать так, чтобы при выборе опции комбинированного списка на панели инструментов выбранное значение толщины пера передавалось бы как значение в переменную m_iWidth класса CDrawSDIDoc. Поскольку в этом классе мы объявили эту переменную закрытой от клиента класса, то необходимо дополнить класс функцией доступа, которую назовем SetWidth().
Выделите класс CDrawSDIDoc во вкладке Class View и через контекстное меню вызовите мастер Add Function, который заполните так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Тело функции заполните такФункция доступа SetWidth() класса CDrawSDIDoc
// Установить толщину пера
void CDrawSDIDoc::SetWidth(int nWidth)
{
m_iWidth = nWidth;
}
Теперь, имея указатель на текущий документ, мы сможем с помощью этой функции передать в документ значение толщины пера, установленное в комбинированном списке панели инструментов.
Но как получить значение от комбинированного списка. Можно перхватывать события, которые генерирует комбинированный список при выборе пользователем нового элемента списка. Это событие имеет имя ON_CBN_SELCHANGE. Поскольку это сообщение генерируется в классе CMainFrame, перехватим его здесь. Для этого:
Введите вручную регистрацию события ON_CBN_SELCHANGE в карту сообщений класса, чтобы она выглядела такКарта сообщений класса CMainFrame в файле MainFrm.cpp BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar) ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar) ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth) END_MESSAGE_MAP()
Через мастер добавьте в класс
Заполните, обработчик кодом, как показано нижеОбработчик OnSelChangeWidth() в файле MainFrm.cpp
// Функция-обработчик выбора элемента комбинированного списка
void CMainFrame::OnSelChangeWidth(void)
{
int iIndex = m_ctrlWidth.GetCurSel();
if(iIndex == CB_ERR)// Если получили индекс
return;
int iWidth = iIndex + 1;
switch(iIndex + 1){ // Скорректировать толщину
case 9: iWidth = 16; break;
case 10: iWidth = 24; break;
case 11: iWidth = 32;
}
// Получить активный документ
CDrawSDIDoc* pDoc = (CDrawSDIDoc*)GetActiveDocument();
if(pDoc) // Если получили указатель
pDoc->SetWidth(iWidth); // Передать толщину в документ
}
Поскольку класс документа CDrawSDIDoc в файле MainFrm.cpp не известен, включите в начало этого файла инструкцию #include. Следует иметь ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный кодИнструкция включения файла DrawSDIDoc.h в файл MainFrm.cpp ......................................... ......................................... #include "DrawSDIDoc.h" // CMainFrame IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar) ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar) ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth) END_MESSAGE_MAP() .........................................
Постройте приложение и убедитесь, что список управляет толщиной линии, но при изменении толщины через меню значение списка синхронно не изменяетсяИсправим это...
Выбор толщины линии через меню обрабатывается функцией, расположенной в документе. Вот эта функция
Функция-обработчик в файле DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
}
Если ввести в этот обработчик вызов функции, которая во фреймовом окне обновит комбинированный список, то это и будет решением задачи. Но для этого из документа прежде нужно будет получить указатель на фреймовое окно-рамку, и уж через него воздействовать на комбинированный список панели инструментов, заставив его обновиться в соответствии с установленным в меню значением ширины пера.
Здесь необходимы следующие шаги:
View, поскольку сам документ не является оконным объектом и не происходит от базового класса CWndCMainFrame в документе не виденС учетом вышеизложенного выполните следующее:
Введите в документ функцию обновления комбинированного списка, назовите ее CDrawSDIDoc::UpdateComboBox(). Для этого для класса CDrawSDIDoc заполните мастер Add Function так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Мастер сгенерирует заготовку, которую заполните следующим кодомФункция UpdateComboBox() в файле DrawSDIDoc.cpp
// Функция обновления комбинированного списка
void CDrawSDIDoc::UpdateComboBox(int iIndex)
{
// Получить позицию активного вида в Z-последовательности видов
POSITION pos = GetFirstViewPosition();
if(!pos) // Позиция не получена, она нулевая
return;
// Получить указатель на верхнее представление (активный вид)
CView* pView = GetNextView(pos);
if(!pView) // Указатель не получен, он нулевой
return;
// Получить указатель на фреймовое окно с помощью вида
CMainFrame* pFrame = (CMainFrame*)pView->GetTopLevelFrame();
if(!pFrame) // Указатель не получен, он нулевой
return;
// Обновить комбинированный список на панели инструментов
pFrame->m_ctrlWidth.SetCurSel(iIndex);
}
Добавьте в начала файла DrawSDIDoc.cpp инструкцию препроцессору для подключения класса фреймового окна, объявление которого находится в файле приложения MainFrm.h. Имейте ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный кодИнструкция препроцессору в файле DrawSDIDoc.cpp ............................................................... #include "MainFrm.h" // CDrawSDIDoc IMPLEMENT_DYNCREATE(CDrawSDIDoc, CDocument) BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) END_MESSAGE_MAP() ...............................................................
Поместите вызов функции UpdateComboBox() в обработчике выбора пункта меню WidthВызов функции в обработчике меню файла DrawSDIDoc.cpp
// Срабатывает при нажатии на любой пункт меню Width
void CDrawSDIDoc::OnWidthCommand(UINT nID)
{
switch(nID){
case ID_WIDTH_16: m_iWidth = 16; break;
case ID_WIDTH_24: m_iWidth = 24; break;
case ID_WIDTH_32: m_iWidth = 32; break;
default: m_iWidth = nID - ID_WIDTH_1 + 1;
}
UpdateComboBox(nID - ID_WIDTH_1);
}
Постройте приложение и убедитесь, что все работает правильно!!!..., кроме одного - при создании нового документа комбинированный список не устанавливается в начало
Чтобы это исправить, разместите вызов UpdateComboBox(0) еще и в функции OnNewDocument() класса CDrawSDIDoc (сделайте это самостоятельно!!!)Есть еще одна неувязочка, которая может не понравиться ПОЛЬЗОВАТЕЛЮ. После прикрепления к левому или правому боку фреймового окна панель скрывает комбинированный список. На данном этапе исправим это, установив свойство панели прикрепляться только к верхнему краю, что не очень ограничит права пользователя. Для этого:
Измените в функции CMainFrame::OnCreate() соответствующий фрагмент кодаИзменение свойства прикрепления панели в файле CMainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
................................................................
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
m_wndColorBar.EnableDocking(CBRS_ALIGN_TOP);// Наша!!!
EnableDocking(CBRS_ALIGN_TOP);
DockControlBar(m_wndToolBar);
DockControlBar(m_wndColorBar);// Наша!!!
return 0;
}
И последнее. Если потянуть панель в плавающем состоянии за нижний край, то список опять исчезает. Разозлимся и отключим у пользователя свет, газ и воду, а у панели - возможность изменять размер.
Просто закомментируйте код, как показано в листингеОтключение свойства панели изменять размер
// Вспомогательная упаковка кода создания панели инструментов
bool CMainFrame::CreateColorBar(void)
{
// Создать инструментальную панель
// для выбора цвета и настроить ее
if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY /* | CBRS_SIZE_DYNAMIC */) ||
!m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
{
TRACE0("Failed to create toolbar\n");
return FALSE; // fail to create
}
..................................................
// Устанавливаем первый элемент видимым
m_ctrlWidth.SetCurSel(0);
return TRUE;
}
Строка состояния является еще одним элементом GUI, служащим для поддержки пользователя. Она, как правило, располагается в нижней части окна, и служит для отображения различных данных, специфичных для данного приложения. В приложениях и она создается автоматически мастером, и разработчику достаточно ее слегка модифицировать, в приложениях же на основе диалога ее нужно создавать самому разработчику, т.е. нам, с нуля (а этот процесс несколько сложнее). В данной лабораторной работе мы выполним только некоторую модификацию строки состояния.
Строка состояния делится на части, которые называются панелями. Элементы управления, входящие в строку состояния, называются индикаторами и каждый из них отображает определенную информацию о состоянии приложения и системы. Строка состояния настолько стандартна, что для нее даже не предусмотрен редактор ресурсов, как для других визуальных ресурсов, как например, диалоговые панели или панели инструментов. Достаточно только указать мастеру создания приложений о необходимости ее создания, и весь требуемый код будет создан автоматически.
Строка состояния MFC является окном немодального типа, наследуемым от класса CWnd, и поддерживается двумя классами:
CStatusBar - в основном используется в SDI и MDI приложенияхCStatusBar CommandToIndex()Create()GetItemID()GetItemRect()GetPaneInfo()GetPaneStyle()GetPaneText()GetPaneBarCtrl()CStatusBarCtrl SetIndicators()SetPaneInfo()SetPaneStyle()SetPaneText()GetTextExtent()Когда в нашем случае мастер создал приложение (или ), то мы видим, что сгенерированная им строка состояния содержит 4 панели (части). Первая панель предназначена для подсказок меню (или кнопок панели инструментов) и она самая длинная. Далее следуют еще три панели <. Соответствующий код поддержки находится в трех местах, рассмотрим его
Переменная-экземпляр класса CStatusBar - ее объявление находится в классе фреймового окна CMainFrame файла MainFrm.h
Объявление переменной-экземпляра класса CStatusBar
class CMainFrame : public CFrameWnd
{
......................................................
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
......................................................
};
Массив индикаторов - массив индикаторов, представленный идентификаторами ресурсов, которые будут отображать информацию в строке состояния. Этот массив определен как глобальный статический в начале файла MainFrm.cpp сразу после карты сообщений
Массив индикаторов в файле MainFrm.cpp
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
Порядок расположения идентификаторов в массиве соответствует позиции индикаторов в панели состояния. Первым в этом массиве всегда должен стоять ID_SEPARATOR, который имеет стандартное имя и сообщает среде выполнения, что с данной конкретной панелью строки состояния не связан ни один строковый ресурс. Фактически это означает, что первая панель строки состояния может принимать информацию от любых ресурсов, а в свободном состоянии отображать слово "Ready" (Готов).
Три последних идентификатора массива индикаторов можно найти в строковом ресурсе файла ресурсов DrawSDI.rc при открытии внешним редактором
Файл ресурсов DrawSDI.rc
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDR_MAINFRAME "DrawSDI\n\nDrawSDI\nDrawSDI Files (*.dhc)\n.dhc\nDrawSDI.Document\nDrawSDI.Document"
END
STRINGTABLE
BEGIN
AFX_IDS_APP_TITLE "DrawSDI"
AFX_IDS_IDLEMESSAGE "Ready"
END
STRINGTABLE
BEGIN
ID_INDICATOR_EXT "EXT"
ID_INDICATOR_CAPS "CAP"
ID_INDICATOR_NUM "NUM"
ID_INDICATOR_SCRL "SCRL"
ID_INDICATOR_OVR "OVR"
ID_INDICATOR_REC "REC"
END
Обратите внимание на текст, выделенный синим цветом. Кстати, если Вы этот текст в стороннем редакторе переместите в часть файла ресурсов String Table, поддерживающего русскую кодировку 1251, то вялое слово "Ready" можете заменить на свое, которое будет выдаваться как дежурное сообщение в первой панели строки состояния, когда эта панель простаивает.
Функция OnCreate() фреймового окна-рамки - содержит код MainFrm.cpp
Файл ресурсов DrawSDI.rc
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...................................................
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
...................................................
}
Строка состояния создается функцией Create() с единственным обязательным параметром, являющемся указателем на родительское окно ( pParentWnd ). Родительским окном для строки состояния является фреймовое окно, поэтому достаточно указать this - указатель на экземпляр класса CMainFrame фреймового окна-рамки. Вторая функция SetIndicators() присваивает массив indicators[], в котором содержаться значения идентификаторов ресурсов, состояние которых нужно отобразить.
Фактически функция Create() имеет три параметра, два из которых (последние) установлены по умолчанию.
CWnd* pParentWnd - указатель на родительское окно строки состоянияDWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | CBRS_BOTTOM - комбинация флагов, определяющих тип, видимость, форму границ, месторасположениеUINT nID = AFX_IDW_STATUS_BAR - стандартный идентификатор самой строки состоянияДля добавления в строку состояния нового индикатора прежде нужно создать его идентификатор в String Table файла ресурсов приложения. Идентификатор будет представлять в приложении закрепленный за ним индикатор. Чтобы в индикатор поместился любой закрепленный за ним текст, необходимо при создании соответствующего идентификатора ввести самый длинный текст из предполагаемых для отображения.
Вызовите на редактирование ресурс String Table и добавьте новую строку
В поле ID введите значение ID_INDICATOR_COLOR, поле Value оставьте по умолчанию, а в поле Caption введите строку Бирюзовый, как самую длинную из набора слов "Черный, Синий, Зеленый, Бирюзовый, Красный, Сиреневый, Желтый, Белый", подлежащих отображению
В массив индикаторов файла MainFrm.cpp добавьте новый идентификатор такМассив индикаторов в файле MainFrm.cpp
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_COLOR,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
Постройте приложение и убедитесь, что новый индикатор создан и в нем находится статический текст "Бирюзовый"Когда окно приложения требует обновления, всем дочерним окнам об этом подается сообщение по каналу сообщений с требованием обновить себя. Сообщение называется UPDATE_COMMAND_UI. В картах сообщений отправитель сообщения отыскивается по его идентификатору, вызывается обрабатывающая функция, в которую передается адрес окна, отправившего сообщение. Воспользуемся этим для обновления содержимого индикатора цвета в строке состояния.
Добавьте в класс CDrawSDIDoc новую функцию-обработчик, заполнив мастер так

Не забудьте нажать кнопку Add перед нажатием кнопки Finish
Введите вручную в карту сообщений класса CDrawSDIDoc регистрацию обработчика за нашим новым индикатором с идентификатором ID_INDICATOR_COLORКарта сообщений в файле DrawSDIDoc.cpp BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument) ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI) ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI) ON_UPDATE_COMMAND_UI(ID_INDICATOR_COLOR, OnUpdateIndicatorColor) END_MESSAGE_MAP()
Наполните обработчик кодомКод обработчика в файле DrawSDIDoc.cpp
// Обработчик обновления индикатора
void CDrawSDIDoc::OnUpdateIndicatorColor(CCmdUI* pCmdUI)
{
char* str[] = {"Черный",
"Синий",
"Зеленый",
"Бирюзовый",
"Красный",
"Сиреневый",
"Желтый",
"Белый"
};
pCmdUI->Enable(); // По умолчанию TRUE - доступный
pCmdUI->SetText((LPCTSTR)str[m_iColor]);
}
Постройте приложение и убедитесь, что все работает
Нарисуйте свой автопортрет, например, так
А он лучше оригинала...
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.