Программирование на Visual C++
Архив
Программирование на Visual C++
Выпуск №1
Добрый день всем!
Итак, свершилось: вы видите перед собой первый выпуск рассылки.
Я понимаю, что ведение такой рассылки – это огромная ответственность, принимая во внимание огромное количество сайтов по этой теме (кстати, именно вопиющее отсутствие рассылки о Visual C++ на горкоте и подвигнуло меня на ее создание). Это мой первый опыт такого рода, так что пока я прошу вас не судить слишком строго. Я же со своей стороны постараюсь сделать все от меня зависящее, чтобы рассылка была интересной, занимательной и полезной.
Прежде всего, я хотел бы уточнить тематику наших бесед. А именно то, что вы здесь не найдете учебника по программированию типа "шаг-за-шагом". По этому принципу построено очень много разных книг, сайтов в Рунете и других источников. На мой взгляд, такие учебники учат только тому, что в них заложено. Они предлагают решение какой-то задачи, но если вам нужно решить задачу немного отличную от описанной, то начинаются проблемы. Ведь очень часто авторы для упрощения изложения выбирают самые тривиальные решения, не подходящие для большинства реальных задач. И приходится выкручиваться своими силами – штудируя help, делая методом "тыка". И, хочу заметить, гораздо чаще такие вот искания оказываются полезнее тупого выполнения шагов учебника, потому что в процессе поиска выдумаете и решаете сами.
С другой стороны, не поймите меня неверно – я вовсе не отрицаю право таких учебников на существование (думаю, что на какой-то стадии они могут быть даже очень полезны), я лишь подчеркиваю их ограниченную применимость.
Именно поэтому в рассылке не будет учебников типа "пишем свою записную книжку".
Хочу подчеркнуть, что все вышеизложенное является моим личным мнением и вы вправе быть с ним не согласны. По правде говоря, я на это рассчитываю ;) Пишите мне – и мы это обсудим.
Итак, что же будет освещено в рассылке?
Во-первых, некоторые теоретические сведения – знание теории никогда и никому не вредило. Причем основной упор будет делаться вовсе не на C++ как язык программирования (хотя это я тоже не исключаю), а на такие вещи, как MFC и WinAPI. Во-вторых, разные полезные приемы программирования и хитрости, советы и трюки. Еще, объяснение некоторых английских терминов по программированию. Потом, обзор некоторых книг по данной теме, перевод интересных статей из интернета и, конечно же, ваши письма, вопросы, мнения.
Но этим я не хотел бы жестко ограничиваться. Пишите, что интересно Вам лично?
Я долго думал, с чего лучше всего будет начать. Ведь уровень у каждого из вас совершенно разный. И решил, что для начала это должно быть что-то "легкое", понятное всем, но и не совсем бесполезное в то же время.
Поэтому следующий материал к программированию как таковому не относится, он лишь позволяет сделать его более комфортным.
Пробовали ли Вы хоть раз менять стандартную синтаксическую подсветку в Visual C++ IDE? Если да, то знаете, что, к сожалению, в настройках доступно только 16 возможных цветов. Какая жалость! Ведь можно было бы сделать очень приятную цветовую схему, ярко выражающую вашу индивидуальность… ну или просто более приятную для глаз.
Неужели ничего нельзя поделать? Оказывается, можно! Для исполнения этого желания нам придется воспользоваться стандартным виртуальным "ломом" для Windows. Нет, я не имею ввиду дизассемблирование – избави бог! ;) В качестве лома в данном случае будет выступать просто редактор реестра (regedit.exe).
Запустите RegEdit и откройте ветвь
HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Source Window
В окне просмотра появится список значений. Каждому синтаксическому элементу соответствует определенный набор шестнадцатеричных чисел. Первые три из них – это цвет самого элемента в виде RGB. Потом одно число пропускается (там, как правило, 0) и три следующие числа – это цвет фона. Т.е. формат такой: RGB-0-RGB-0 (остальные числа лучше не трогать).
Вот и все – теперь для любого элемента программы ( комментария, строки, ключевого слова или др.) вы можете назначить любой из 16 млн. доступных цветов!
Q. Как изменить стили окон, создаваемых MFC AppWizard'ом по умолчанию?
A. Чтобы изменить стиль по умолчанию какого-нибудь окна, нужно перекрыть виртуальную функцию PreCreateWindow() класса этого окна. Эта функция позволяет приложению получить доступ к процессу созданию окна, который по умолчанию происходит в недрах MFC с помощью класса CDocTemplate. Библиотека вызывает PreCreateWindow() перед созданием окна. Этой функции передается параметр – указатель на структуру CREATESTRUCT. Путем изменения членов этой структуры вы можете влиять на стиль создаваемого окна.
Q. Как сделать так, чтобы положение элементов управления менялось, когда размер окна изменяется, т.е., например, чтобы они выравнивались по правому или нижнему краю?
А. Увы, это не так элементарно делается, как, скажем, в C++ Builder. Но и здесь есть свои плюсы – вы получаете больший контроль. Пусть, скажем, у Вас есть кнопка, которую Вам нужно выровнять по правому краю, и соответствующая переменная m_Btn типа CButton в классе вашего окна или вида. Тогда в функции обработки сообщения WM_SIZE – OnSize().
void CMyView::OnSize(UINT nType, int cx, int cy) {
CFormView::OnSize(nType, cx, cy);
.
.
// ... добавьте вот это:
if (::IsWindow(m_Btn.m_hWnd)) // условие на корректность
m_Btn.MoveWindow(cx - BtnWidth - 10, BtnY, cx - 10, BtnY + BtnHeight, 0); // двигаем кнопку
// конец кода для добавления.
.
.
}
Здесь вместо BtnY вставьте желаемую Y-координату кнопки, BtnWidth и BtnHeight – соответственно целевые ширина и высота кнопки.
Параметр cx, передаваемый в функцию - это новая ширина окна. Данный код изменяет положение кнопки, чтобы она оставалась ширины Btn_Width и отстояла от правого края окна на 10 единиц. Функция MoveWindow() меняет размер и положение кнопки. Если вы не знаете BtnY|Width|Height, то их можно определить с помощью функции m_btn.GetClientRect(), ведь любой элемент управления - это, в принципе, тоже окно.
Выравнивание по нижнему краю производится аналогично, просто по смыслу меняются параметры MoveWindow().
Ну вот, на сегодня пока все. Жду ваших писем с замечаниями, предложениями и пожеланиями. До скорого!
Программирование на Visual C++
Выпуск №2 от 20/6/2000
Приветствую вас, уважаемые подписчики!
Очень рад, что число вас растет, несмотря на то, что рассылка еще не вышла из категории "Рассылки для каждого" и не была официально анонсирована. Если новоподписавшимся интересно узнать, о чем мы в рассылке беседуем, они могут посмотреть в архиве первый выпуск.
Сегодня мы немного поговорим об устройстве MFC, а также рассмотрим один интересный вопрос.
Как известно, основой всех основ в MFC является класс CObject. Основным назначением этого класса является предоставление некоторых базовых возможностей всем своим наследникам, а именно доступ к информации о классе во время выполнения и поддержка сериализации, т.е. сохраняемости объектов.
Однако уровень предоставляемых возможностей варьируется в зависимости от вашего выбора; он зависит от включения определенных макросов объявления и реализации при создании классов – наследников CObject. Без сомнения, вы с этими макросами уже сталкивались, например в коде, который генерируют Wizard'ы. Пришла пора разобраться с ними более детально.
Итак, на характер вашего класса, производного от CObject, вы можете влиять с помощью нескольких макросов. Существуют определенные пары макросов — один включается в объявление класса (имеет префикс DECLARE_), а соответствующий ему — в реализацию (префикс IMPLEMENT_).
Первая пара макросов — это DECLARE_DYNAMIC|IMPLEMENT_DYNAMIC. С помощью включения этих макросов в код вашего класса вы можете включить одну из базовых функций CObject — способность узнавать класс объекта прямо во время выполнения программы. Для этого вы можете пользоваться функцией IsKindOf() в связке с макросом RUNTIME_CLASS, который возвращает указатель на структуру CRuntimeClass (где хранится вся информация о классе: имя, размер, версия, информация о базовом классе, указатель на конструктор объекта и т.д.)
Следующая пара — DECLARE_DYNCREATE|IMPLEMENT_DYNCREATE аналогична первой, но к возможности получать информацию о классе добавляется еще и возможность создавать объекты этого класса во время выполнения.
Объект создается функцией CreateObject структуры CRuntimeClass. Вот пример:
CRunTimeClasspClass = RUNTIME_CLASS(СMyObject);
// получаем ук-ль на структуру CRunTimeClass
CObjectpNewObject= pClass->CreateObject();
// создаем новый объект нужного класса
ASSERT(pNewObject->IsKindOf(RUNTIME_CLASS(CMyObject));
// проверяем класс объекта
И, наконец, мы подошли к последней паре макросов DECLARE_SERIAL| IMPLEMENT_SERIAL. Преобразование в последовательную форму и обратно — сериализация — дает программисту возможность сохранения и восстановления объектов. Для того, чтобы воспользоваться этой возможностью, в классе-наследнике нужно перекрыть виртуальную функцию Serialize().
Из нее обязательно нужно сначала вызвать родительскую версию. Одна и та же функция используется как для сохранения, так и для восстановления объекта. Какую операцию нужно произвести, она определяет из своего единственного параметра ar типа CArchive. Вот пример:
void CMyObject::Serialize(CArchive ar) {
CObject::Serialize(ar); // вызываем версию базового класса
if (ar.IsStoring()) // если сохраняем,
{
ar << something; // то сохранить что-то
} else // а иначе
{
ar >> something; // восстановить
}
}
Заметьте, что DECLARE_SERIAL|IMPLEMENT_SERIAL помимо сериализации включают и те возможности, которые дают две первые пары — это естественно, ведь если вы восстанавливаете объект, то вам понадобится возможность создать его во время выполнения программы. Например, приложению нужно сохранять и восстанавливать некоторый набор объектов различного типа. А для вызова соответствующего конструктора при восстановлении объекта нужно знать его тип. Механизм сериализации сохраняет информацию об объекте вместе с теми данными, что вы записываете явно в функции Serialize().
Следующий вопрос поступил от одного из подписчиков:
Q При программировании элементов ActiveX, в этой технологии есть возможность структурного хранения данных на диске, т.е. создание в файле так называемых хранилищ и потоков (использование интерфейсов IStream и IStorage), проще говоря – представление файла данных в виде иерархической системы внутренних каталогов и файлов, которые там (в данном файле данных) имеют свои строковые имена. Есть ли в MFC возможность структурного хранения, скажем, используя объект класса CArchive в переопределяемой функции Serialize(CArchive) класса, производного от CDocument, ну и так далее? Конечно, этого можно добиться, создав свои собственные наработки (а как хороша эта идея, я имею в виду использование потоков и хранилищ), но все таки хочется знать, есть ли такие возможности в MFC, чтобы не тратить зря время.
A Насколько мне известно, поддержки такой иерархической системы в MFC нет. Во всяком случае, я ее не обнаружил. CArchive и Serialize для этой цели явно не предназначены: в них важную роль играет последовательность записи, т.е. в каком порядке вы что-то записали, в таком нужно это и прочитать. Так что, скорее всего, придется писать свой класс для этой цели. Конечно, это не слишком обнадеживает, но зато этот класс можно будет использовать во всех дальнейших программах, где потребуется такая форма хранения данных. Или – как вариант – можно сделать просто класс-обертку для интерфейсов IStorage и IStream. Конечно, в этом случае придется подключать библиотеку COM (которую в MFC-приложениях, в принципе, никто не запрещает использовать). Впрочем, если кто знает что-нибудь о существовании такого механизма в MFC – пожалуйста, поделитесь с нами.
Всего наилучшего и чтоб программы ваши не знали ошибок.
Программирование на Visual C++
Выпуск №3 от 23/06/2000
Здравствуйте!
Да, это должно было произойти и это произошло! Рассылка получила официальный статус "обычной некоммерческой рассылки" (причем гораздо быстрее, чем я ожидал), с чем я себя и всех вас и поздравляю!
Хочу извиниться перед подписчиками HTML-версии: во втором выпуске случилось некоторое искажение исходного кода (я упустил из виду, что при автоматической генерации HTML сервер Гор. Кота звездочки (*) интерпретирует как указание сделать шрифт жирным), в результате чего были потеряны указатели. Вот как этот код должен был выглядеть на самом деле:
CRunTimeClass *pClass = RUNTIME_CLASS(СMyObject);
// получаем ук-ль на структуру CRunTimeClass
CObject *pNewObject = pClass->CreateObject();
// создаем новый объект нужного класса
ASSERT(pNewObject->IsKindOf(RUNTIME_CLASS(CMyObject));
// проверяем класс объекта
Меня удивило, что большинство из вас подписывается именно на HTML – я думал, наши люди как никакие другие считают каждый килобайт. Но, видимо, времена меняются – в лучшую сторону. Дай бог! Так что по вышеописанным причинам я решил поднапрячься и сработать собственный HTML-вариант. То, что получилось, сейчас перед вами, уважаемые HTML-подписчики. Тех, кто выписывает текстовый вариант, уговаривать подписаться на HTML я не буду, потому что прекрасно их понимаю ;) Оба варианта я буду делать лично, никакой автоматической генерации. Enjoy.
Мне пришло интересное письмо на тему предыдущего выпуска. Хочу предложить его вашему вниманию:
Приветствую!
Я только что обнаружил эту рассылку, подписался и 2 первых выпуска прочитал в архиве. Очень надеюсь, что смогу оказать посильную помощь автору и читателям рассылки, так как около 30 лет занимаюсь программированием и последние 3-4 года – Visual C++. На работе сейчас я программирую именно на этом [языке – AJ], Visual Studio 6.0
В отношении вопроса Броника. Конечно, система сериализации для ActiveX имеется. Для этого рекомендуется использовать класс COLEControl (порожденный из CWnd –>CCMDTarget->CObject).
Вот пример из MSDN (у меня есть этот хелп и на работе и дома)
void CMyCtrname = "note" :Serialize(CArchive& ar) {
DWORD dwVersion = SerializeVersion(ar, MAKELONG(_wVerMinor, _wVerMajor));
SerializeExtent(ar);
SerializeStockProps(ar);
if (ar.IsLoading()) {
// загрузить свойства
} else {
// сохранить свойства
}
}
Пару замечаний относительно сериализации. Вот пример, как можно поддерживать версию при сериализации. Это важная вещь, поскольку в классы регулярно заносятся новые данные, и чтение-сериализация должны поддерживать старые версии распространенных среди пользователей Вашей программы файлов.
IMPLEMENT_SERIAL(CMyClass, CObject, VERSIONABLE_SCHEMA | 2)
// 2- Номер текущей (новой) версии. 1 - номер старой версии
void CMyClass::Serialize(CArchive& ar) {
if (ar.IsLoading()) {
UINT Version;
ar.ReadClass(RUNTIME_CLASS(CMyClass), &Version);
switch(Version) {
case 2:
// чтение по-новому
break;
case 1:
// чтение по-старому
break;
}
}
if (ar.IsStoring()) {
ar.WriteClass( RUNTIME_CLASS(CMyClass));
// запись по-новому
}
}
Понятное дело, при продвижении версии номер в IMPLEMENT_SERIAL возрастает, и добавляется новый случай case только при чтении соответствующих версий.
Еще одно важное замечание. При использовании механизма сериализации мы платим некоторую цену: не допускается никаких абстрактных классов – забудьте, что это существует!
Что ж, огромное спасибо Борису за комментарии и дополнения. Я надеюсь, он и в будущем будет нам посильно помогать. Что касается вопроса Броника – думаю, он все-таки спрашивал не о том, как сделать сериализацию для ActiveX(хотя это тоже очень интересный момент), а как организовать структурированное хранение данных в файле , наподобие того, что присутствует в ActiveX. Ответа на этот вопрос, за исключением предложенного мной в предыдущем выпуске, пока нет.
Просьба: когда пишете мне, пожалуйста оговаривайте ваше отношение к публикации вашего e-mail адреса. Я оставляю за собой право решать, какие из ваших писем появятся в рассылке. По умолчанию адрес публиковаться не будет. Если вы хотите связаться с человеком, письмо которого вы прочитали в рассылке, но чей адрес не был указан, пишите мне с пометкой в subject'e для кого это письмо.
Q. Идея рассылки и её тематика очень понравились, даже добавлять или изменять ничего не хочется, как по заказу. И даже уже вопрос созрел. При первом знакомстве с MFC помню была одна проблема. Никак не получалось сменить пиктограмку курсора во время выполнения программы. Т.е. последовательность стандартных действий LoadCursor и SetCursor не срабатывала, хотя при создании окна этих действий хватало. В связи с этим вопрос: Какие ещё действия надо выполнить для смены пиктограмки курсора во время работы приложения. Сейчас, к сожалению, интересы лежат не в области C++ и MFC. Поэтому на разрешение вопроса своими силами просто нет времени.
A. Спасибо за добрые слова о рассылке. По вопросу – проблема здесь в том, что система автоматически при каждом движении мыши восстанавливает тот курсор, который был указан при регистрации класса окна. Вообще я знаю три способа изменить курсор в MFC-приложении, причем два из них имеют некоторые ограничения: один используется, в самом деле, при создании окна, а второй работает только с одним курсором – стандартными песочными часами. Думаю, что стоит описать все три способа, для того, чтобы вы могли выбрать наиболее для вас подходящий. Итак: