Помощ за VC++ 5.0 / Help for VC++ 5.0/P>
AppWizard генерира кода за едно MFC приложение. Това работещо приложение показва един празен прозорец с прикачено меню. В последствие в него ще добавим код за чертане. За да се изгради приложението следвай тези стъпки:
1. Стартирай AppWizard за да генерира изходен код за SDI приложение.
Избери
New от менюто File на Developer Studio и превключи на страницата Projects.Избери опцията
MFC AppWizard. Напиши името в Project name EX03A.От следващия екран избери опцията
Single document. Следващите екрани в случая не се променят така, че може да натиснеш бутона Finish. Появява се още един джам на който натискаш бутона ОК. Генерират се директории и малко файлове:
EX03A.DSP |
Проектен файл, който позволява на Developer Studio да изгради твоето приложение |
EX03A.DSW |
Файл за работното пространство, който съдържа информация за проектите. (В случая само за EX03A.DSP |
EX03AView.CPP |
Файл с изходен код, съдържащ имплементацията на клас СЕ x03aView (дефинициите на неговите член функции) |
EX03AWiew.H |
Заглавен файл, съдържащ декларацията на класа СЕ x03aView |
EX03A.OPT |
Двоичен файл, указващ на Developer Studio кои файлове от проекта са отворени и как са разположени техните прозорци |
ReadMe.TXT |
Текстов файл, обясняващ целта на генерираните файлове |
RESOURCE.H |
Заглавен файл, съдържащ # define константни дефиниции |
EX03A. РС |
Файл с описание на ресурсите на ASCII код |
Отвори файловете
EX03AView.CPP и EX03AView.H и погледни техния код. Един обект от глас EX03AView, съответства на прозореца на изгледа (view window) на приложението където се развива главното действие.2. Компилирай и свържи генерирания код.
Освен че генерира код
AppWizard създава специфичен проектен файл за твоето приложение и файл за работното пространство. Проектния файл EX03A.DSP определя всички зависимости между файловете, заедно с флаговете на опциите за компилация и свързване. Понеже новосъздадения проект става текущ проект в Developer Studio, може вече да изградиш приложението, като избереш Build EX03A.EXEот менюто Build. Ако всичко е минало добре ще се създаде файла EX03A.EXЕ в директорията Debug.View - класът Cex03AView
AppWizard
създаде “изходния” клас CE03aView, който е специфичен за приложението EX03A. (AppWizard създава класове въз основа на името, което е въведено в първия диалогов прозорец). Cex03aView е крайно звено в дълга верига от наследявания на класове на MFC библиотеката. Този клас взема член функции и член-променливи от класовете от цялата верига. Може да научиш повече за тези класове от online-версията или от печатната версия на справочника Microsoft Foundation Class Reference, но трябва да погледнеш описания за всеки базов клас.Чертане в прозореца на изгледа - интерфейсът към графично устройство (GDI) за Windows
Функцията OnDraw
Напиши кода на функцията OnDraw във файла EX03AView.CPP. OnDraw e виртуална функция на класа Cview, която приложната среда извиква всеки път когато прозорецът на изгледа трябва да бъде пречертан. Един прозорец се нуждае от пречертаване, ако потребителя промени неговите размери или разкрие част от него. Ако функцията на твоята програма променя данните, тя трябва да информира Windows за тази промяна, като извика наследената член-функция Invalidate (или InvalidateRect) на изгледа. Това позволява на Invalidate в последствие да предизвика обръщение към OnDraw.
Въпреки, че можеш да чертаеш в един прозорец, по всяко време, препоръчително е да оставиш промените в този прозорец да се натрупват, след което ще може да реагира и на събития, генерирани по програмен път, и на събития, генерирани от Windows, като промяна на размерите и др.
Контекстът на устройството (
device context) в Windows.Windows не позволява директен достъп до хардуера на дисплея, а комуникира чрез една абстракция, наречена “контекст” на устройството”, която е асоциирана с прозореца. В MFС библиотеката, контекстът на устройство е С++ обект от клас
CDC, който се предава (чрез указател) като параметър на OnDraw. След като имате указателя към контекст на устройство, може да извикаш маса функции на CDC, които осъществяват самото чертане.Добавяне на код за чертане към програмата EXO3A
За целта проектът
EXO3A трябва да е отворен в Developer Studio. Може да използваш ClassView от прозореца Workspace, за да намериш кода на функцията (щракни двукратно с мишката върху OnDraw)¸ à може да отвориш файла с изходния код на EX03AView.CPP от FileView и сам да намериш функцията.1. Редактирай функцията
OnDraw от EX03AView.CPP. Намери генерираната от AppWizard функцията OnDraw в EX03Avew.CPP:pDC->TextOut (0, 0, “Hello world”); // print is default font & , top left comer
pDC->SelectStockObject (GRAY_BRUSH); //selects a brush for the circle interior
pDC->Ellipse (CRect(0, 20, 100, 120)); // draws a gray circle 00 units in diameter
GetDocument
се премахва защото още не се занимаваме с документи. Функциите TextOUT, SelectStockObjet и Ellipse са член-функции на класа за контекст на устройството (CDC), който е клас от приложната среда. Функцията Ellipse чертае окръжност, когато дължината и ширната на обхващащия правоъгълник са равни.MFC
библиотеката предлага един клас CRect, който е удобно средство за работа с правоъгълници в Windows. Един временен обект от клас CRect служи за аргумент от тип “обхващащ правоъгълник” на функцията за чертане на елипса.2. Прекомпилирай и тествай
EX03A. От менюто Project избери Build и ако няма грешки при компилация, тествай приложението отново. Сега програмата прави това:Преглед на ресурсните редактори
Ресурсния файл
EX03A.RC определя външния вид на изображението. Сочи към следните ресурси:
Accelerator |
Дефиниции на клавиши симулиращи избор от меню или от панел с инструменти. |
Dialog |
Оформление и съдържание на прозорци. EX03A има само диалоговия прозорец About |
Icon |
Икони |
Menu |
Менюто в прозореца |
String table |
Символни низове, които не са част от С++ код |
Toolbar |
Бутоните под главното меню |
Version |
Описание на програмата, номер на версия, език и т.н. |
EX03A
съдържа и изразите:#include “afresh”
#include “afxres.h”
Тези ресурси включват символни низове, графични бутони и елементи, необходими за печат и
OLE#include “resource.h”
Вмъква трите
#define константи на приложението IDR_MAINFRAME (идентифицираща менюто, икона, списъка от символни низове и таблицата с клавиатурните акселератори), IDR_EX03ATYPE (определя иконата на документа по подразбиране) и IDD_ABOUTBOX (диалоговия прозорец About). С редактора на ресурси може да се добавят още дефиниции в ресурсния файл.Стартиране на редактора на диалогови ресурси
1. Отвори
RC файла на проекта; Натисни бутона Resource View от прозореца Workspace.2. Погледни ресурсите на изображението;
3. Модифицирай прозореца
IDD_ABOUTBOX4.
Прекомпилирай проекта5. Тествай новата версия
Win32 -
работна срещу Win32 - крайна версияПо подразбиране
AppWizard генерира проект, той по подразбиране създава две отделни версии с различни настройки. Тези настройки са:
Release Build |
Debug Build |
|
Трасиране в изходния код |
Забранено |
Разрешено и за компилатора и за линкера |
Макроси на диагностиката |
Забранени ( NDEBUG е дефинирано) |
Разрешени (дефинирано е _ DEBUG) |
Библиотечно свързване |
MFC Release библиотека |
MDC Debug библиотека |
Оптимизация при компилация |
Оптимизация на скоростта (липсва в Leering Editor |
Няма оптимизации (по-бърза компилация) |
Изпълнимия файл създаден в режим
Release, ще бъде по-малък и по-бърз, при условие, че си коригирал всички грешки. Конфигурацията се избира от прозореца за избор на версия за изграждане, който се намира в панелата с инструменти Build. По подразбиране, изходните Debug файлове се намират в поддиректорията на Debug проекта, а Release файловете в директорията Release. Тези директории се променят от страницата General на прозореца Project Setting.Собствени настройки се създават като се избере
Configurations от менюто Build на Developer Studio.Глава 4
Елементарна обработка на събития, режими на съпоставяне и изглед за скролиране
При натискане на левия бутон на мишката в прозореца на един изглед, се изпраща съобщението
WM_LBUTTONDOWNкъм този прозорец. Ако трябва да обработиш събитието то твоя клас трябва да има функция:Void SmyView::OnLButtonDown (UNT nFlags, CPoint point)
{
//event processing code here
}
Заглавния файл трябва да съдържа:
Afx_msg void OnLButtonDown (UNT nPage, CPoint pint);
Нотацията
afx_msg предупреждава, че това е прототип на функцията за обработка на съобщения. Във файла с изходния код трябва да има макрос за асоцииране на съобщения, които свърза функцията OnLButtonDown с приложната среда:BEGIN_Message_MAP () // entry specifically for OnButtonDown other message map entries
END MESSAGE_MAP()
C
++ предлага средство, ClassWizard, което автоматизира писането на код за повечето функции за обработка на събития.Записване състоянието на изгледа - член променливи на класовете
OnDraw
чертае изображение въз основа на текущото “състояние” на изгледа, а действията от страна на потребителя могат да променят това състояние. В едно напълно завършено MFC приложение, обектът-документ съдържа състоянието на приложението. Ще използваме две член-променливи (data members) на класа за изглед (view-класа) - m_rectEllipse и m_nColor. Първата е от клас CRect, който съдържа текущия обхващащ правоъгълник на една елипса, а втората е цяло число, съдържащо стойността на текущия цвят на елипсата.По конвенция, имената на не-статичните член-променливи на класовете
MFC библиотеката започват с m_.Първоначалните стойности на променливите в случая се задават в конструктора на изгледа, а цветът се променя във функцията
OnLButtonDown.Инициализиране на член-променливи на изгледен клас
Най-подходящото място е в конструктора:
CmyView:CmyView(): m_rectEllipse (0, 0, 200, 200) {…}
Теорията за невалиден правоъгълник
При смяна на стойностите на цвета (m_nColor) от функцията OnLButtonDown, но функцията OnDraw няма да бъде извикана (ако не се сменят размерите на прозореца от потребителя). OnLButtonDown трябва да извика функцията InvalidateRect (член функция, която view-класът наследява от Cwind). InvalidateRect предизвиква Windows съобщението WM_PAINT, което в класа Cview e асоциирано с натуралната функция OnDraw. Ако е необходимо OnDraw може да получи достъп до “невалидния триъгълник”, предаден като параметър на InvalidateRect.
Има два начина за оптимизиране на рисуването. Първо
Windows обновява пикселите, които се намират вътре в невалидния правоъгълник. Колкото е по-малък, толкова по-бързо ще се изчертае. Второ, губене на време е да се изпълняват инструкции за чертане извън правоъгълника, обявен за невалиден.OnDraw
може да извика CDC функцията GetClipBox, за да определи невалидния правоъгълник, след което може да бъде избегнато чертането на обекти извън него.Та значи: OnDraw се извиква след позоваване на InvalidateRect и при промяна размерите на прозореца.
Клиентската област на прозореца
Всеки прозорец има една правоъгълна клиентска област, която не включва рамката, лентата със заглавието, областта на менюто и скачващите се панели с инструменти. Функцията GetClientRect, член на класа CWind, предоставя размерите на клиентската област. По принцип там не може да се чертае, а и повечето съобщения от мишката се появяват когато нейния курсор се намира върху нея.
Класовете CRect, CPoint и CSize аритметика
Тези класове са производни на структурите от тип RECT, POINT и SIZE на Windows и наследяват следните public целочислени член-променливи:
CRect |
Left, top, right, bottom |
CPoint |
X, Y |
CSize |
EX, SY |
K
ласът CRect има член-функции, които се отнасят за класовете Csiize и CPoint. Например функцията TopLeft връща обект от клас CSize. Оттук може да стигнеш до извода, че един обект от клас CSize представлява между два обекта от клас CPoint” и че може да “повлияе” един обект от клас CRect, посредством обект от клас CPoint.Проверка за точка в правоъгълник
Класът
CRect има член функция PtInRect, която проверява дали дадена точка се намира в даден правоъгълник. Втория параметър на OnLButtonDown - point е обект от клас CPoint, който представлява положението на курсора в клиентската област на прозореца. Ако искаш да знаеш дали тази точка се намира в правоъгълника m_rectEllipse, може да използваш PtInRect по следния начинif ( m_rectEllipse.PtInRect(point) ) {
//point is inside rectangle
}
Това действа само ако се прилагат устройствени координати (
device coordinates).Оператора
LPRECT на CRectCWnd::InvalidateRect приема параметър от тип LPRECT. Параметър от тип CRect е позволен, защото класът CRect дефинира оператор LPRECT(), който връща адреса на обект от клас Crect, еквивалентен на адреса на един RECT обект. По този начин компилатора конвертира типа на аргументите от CRect в LPRECT, когато е необходимо. Функциите могат да се извикват все едно, че имат параметри, псевдоними на CRect:
CRgn rgn;
Rgn.CreateEllipticRgnIndirect m_rectEllipse);
if rgn.PtinRegion(point) ) {
//point is inside ellipse
}
Проверка за точка в елипса
Кодът на EXO4A определя дали натискането на бутона на мишката става, когато нейният курсор се намира в пределите на правоъгълника. За да установиш дали бутона е натиснат върху елипсата трябва да създадеш обект от клас CRgn, който съответства на елипсата и след това да използваш функцията PtInRegion, вместо PtInrect:
CRgn rgn;
rgn.CreateEllipticRgnIndirect (m_rectEllipse);
if ( rgn.PtinRegion (point) ) {
// pint is inside ellipse
}
Примера
EX440AВ този пример кръглата елипса променя цвета си, при натискане на левия бутон на мишката когато показалеца е върху нея. Използват се член-променливите на
view-класа, за съхраняване състоянието на изгледа. Използва се и InvalidateRect, за пречертаване на изгледа. Чертането се прави от три функции написани ръчно (включително и конструктора) и две член-променливи. Направените промени във функцията AppWizard са дадени на сив (жълт) фон.EX04AView.h
// EX04AView.h : interface of the CEX04AView class
//
/////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_EX04AVIEW_H__72B1758D_1334_11D9_A568_F0414BC10000__INCLUDED_)
#define AFX_EX04AVIEW_H__72B1758D_1334_11D9_A568_F0414BC10000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CEX04AView : public CView
{
protected: // create from serialization only
CEX04AView();
DECLARE_DYNCREATE(CEX04AView)
// Attributes
public:
CEX04ADoc* GetDocument();
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEX04AView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CEX04AView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CEX04AView)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// add handle
private:
int m_nColor;
CRect m_rectEllipse;
};
#ifndef _DEBUG // debug version in EX04AView.cpp
inline CEX04ADoc* CEX04AView::GetDocument()
{ return (CEX04ADoc*)m_pDocument; }
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_EX04AVIEW_H__72B1758D_1334_11D9_A568_F0414BC10000__INCLUDED_)
EX04AView.CPP
// EX04AView.cpp : implementation of the CEX04AView class
//
#include “stdafx.h”
#include “EX04A.h”
#include “EX04ADoc.h”
#include “EX04AView.h”
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CEX04AView
IMPLEMENT_DYNCREATE(CEX04AView, CView)
BEGIN_MESSAGE_MAP(CEX04AView, CView)
//{{AFX_MSG_MAP(CEX04AView)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// 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)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CEX04AView construction/destruction
CEX04AView::CEX04AView(): m_rectEllipse (0, 0, 200, 200)
{
// TODO: add construction code here
m_nColor = GRAY_BRUSH;
}
CEX04AView::~CEX04AView()
{
}
BOOL CEX04AView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CEX04AView drawing
void CEX04AView::OnDraw(CDC* pDC)
{
CEX04ADoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->SelectStockObject (m_nColor);
pDC->Ellipse (m_rectEllipse);
}
/////////////////////////////////////////////////////////////////////////////
// CEX04AView printing
BOOL CEX04AView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}
void CEX04AView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CEX04AView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
/////////////////////////////////////////////////////////////////////////////
// CEX04AView diagnostics
#ifdef _DEBUG
void CEX04AView::AssertValid() const
{
CView::AssertValid();
}
void CEX04AView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CEX04ADoc* CEX04AView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEX04ADoc)));
return (CEX04ADoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CEX04AView message handlers
void CEX04AView::OnLButtonDown (UINT nFlags, CPoint point)
{
if ( m_rectEllipse.PtInRect (point) ) {
if ( m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect (m_rectEllipse);
}
}
Използване на
AppWiazrd и ClassWizard съвместо1.
Създай EX04A2. Добави променливите m_rectEllipse и m_nColor към CEX04A .
Избери страницата ClassView от прозореца Workspace и натисни левия бутон на мишката върху CEX04Aview. Избери AddMember Variable и въведи следните променливи
private
Crect m_rectEllipse:
int m_nColor;
Този код може да се напише в декларацията на класа във файла
EX04AView.h3. Използвай
ClassWizard за добавяне функция за съобщенията към класа CEX04AViewИзбери ClassWizard от менюто View Developer Studio или натисни десния бутон на мишката в някой прозорец с изходен код. Избери EX04Aview от Class name. Избери от Object IDs; EX04Aview. От прозореца Messages избери WM_LBUTTONDOWN. Името на функцията OnLButtonDown трябва да се появи с удебелен шрифт в списъка Member Function.
4.
Редактирай и кодаvoid CEX04AView::OnLButtonDown (UINT nFlags, CPoint point)
{
if ( m_rectEllipse.PtInRect (point) ) {
if ( m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect (m_rectEllipse);
}
}
5. Редактирай конструкторите и функцията
OnDraw в EX4AView.cppCEX04AView::CEX04AView(): m_rectEllipse (0, 0, 200, 200)
{
// TODO: add construction code here
m_nColor = GRAY_BRUSH;
}
….
void CEX04AView::OnDraw(CDC* pDC)
{
CEX04ADoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->SelectStockObject (m_nColor);
pDC->Ellipse (m_rectEllipse);
}
6.
Компилирай и стартирай програматаРежими на съпоставяне (
Mapping modes)Чертожните единици в примерите до сега са пиксели
MM_TEXT. ИзразътPDC-> Rectangle (Crect (0, 0, 200, 200) ); //
чертае квадрат с размерност 200 на 200 пиксела започващи от горния ляв ъгъл на клиентската част прозореца.Този квадрат ще изглежда по-малък на монитор с по-висока разделителна способност. Отпечатването на различни принтери също ще бъде различно. (Погледни
Print Preview на EX04A)Ако квадрата трябва да бъде с размери 4 на 4 см. независимо от типа на монитора трябва да се използват други режими на съпоставяне. Координатите на съпоставяне в текущия режим се наричат логически координати. Ако избереш режим
MM_HIMETRIC една логическа единица ще бъде 1/100 мм, вместо един пиксел. В този режим оста У е в посока, противоположна на тази при режим MM_TEXT, т.е. колкото по-надолу се отива, толкова по-ниски стават стойностите. Сега квадрата 4 на 4 см. се чертае:PDC->Rectangle ( Crect (0, 0, 4000, -4000) );
Режима на съпоставяне
MM_TEXTКоординатите се съпоставят с пиксели, стойностите на Х се увеличават надясно а на У надолу. Може да се промени началото на координатите чрез извикване на функциите
SetViewportOrg и SetWindowOrg на CDC.Следващия пример указва началото на координатната система на прозореца да бъде (100, 100) в логическите координати, след което чертае квадрат с размери 200 на 200 пиксела с отместване (100, 100)
Void CmyView::OnDraw (CDC *pDC) {
pDC->SetMapMode (MM_TEXT);
pDC->SetWindowOrg (CPoint (100, 100));
pDC->Rectangle (CRect(100, 100, 300, 300));
}
Режими на съпоставяне с фиксиран мащаб
При различните мащаби разликата е в мащабните фактори:
Режим |
Логическа единица |
MM_LOENGLISH |
0.01 инча |
MM_HIENGLISH |
0.001 инча |
MM_LOMETRIC |
0.1 mm |
MM_NIMETRIC |
0.001 mm |
MM_TWIPS |
1/1440 инча |
Последния най-често се използва с принтери. Един
twip се равнява на 1/20 от точката която се равнява на 1/72 инча. За направа на 12 точков тип установи височината на символа 12х20, или 240 twips единици.Режими на съпоставяне с променлив мащаб
Това са:
MM_ISOTROPIC и MM_ANISOTROPIC. В тези режими чертежите променят размерите си при промяна размера на прозореца. В режим MM_ISOTROPIC винаги се спазва съотношение 1:1. Когато се променя мащаба един кръг винаги си остава кръг, а в режима MM_ISOTROPIC осите Х и У могат да се променят независимо една от друга. Ето пример за елипса която точно се побира в прозореца://Това се преписва във функцията
OnDrawwCRect rectClient;
GetClientRect (rectClient);
pDC->SetMapMode (MM_ANISOTROPIC);
pDC->SetWindowExt ( 1000, 1000);
pDC->SetViewportExt (rectClient.right,
rectClient.bottom);
pDC->SetViewportOrg ( rectClient.right / 2,
rectClient.bottom / 2);
pDC->Ellipse (CRect (-500, -500, 500, 500));
Функциите SetWindowsExt и SetViewportExt задават мащаба въз основа на текущия клиентски правоъгълник върнат от функцията
GetClientRect. Размерите на получения прозорец са точно 1000 на 1000 логически единици. Функцията SetViewportOrg поставя началото в центъра на прозореца. Така, една центрирана елипса с радиус 500 логически единици точно запълва прозореца:Това са формулите за превръщане от логически единици в устройствени:
X scale factor = x viewport extent / x window extent
Y scale factor = y voewport extent / y window extent
device x = logical x y x scale factor + x origin offset
device y = logical y x y scale factor + y origin offset
Ако заместиш
MM_ANISOTROPIC с MM_ISOTROPIC елипсата ще стане кръгПреобразуване на координати
Ако получиш координатите на курсора от
Windows съобщение за мишката (параметъра point на OnLButtonDown), вече работиш с устройствени координати. Много функции на MFC, особено от клас Crect работят коректно само с устройствени координати.Превръщането на физически в логически координати е твоя работа, но превръщането то логически в устройствени координати сее поема от
GDI функциите на Windows. CDC функциите LPtoDP и DPtoLP превръщат от една система в друга, при условия, че режима на съпоставяне и съответните параметри са вече зададени.Ако трябва да знаеш дали курсора на мишката се намира върху даден правоъгълник при натискане на левия бутон:
//Добавяне на код за друг пример
//CRect rect = m_rect; //rect is a temporary copy ot m_rect
CClientDC dc(this) //This is how we get a device context for SetMapMode and LPtoDP - more in next chapter
dc.SetMapMode (MM_LOENGLISH);
dc.LPtoDP(rect); //rect is now in device coordinates
if (rect.PtinRect(point)) {
TRACE (“Mouse cursor is inside the rectangle \n”);
}
Пример
EX04B - Преобразуване режима на съпоставяне MM_HIMETRICИ този пример прави проверка за попадение когато се натисне левия бутон на мишката върху обхващащия елипсата правоъгълник.
Та първо добави функцията
onPrepareDC от джама Workspace натискаш бутона ClassWiew, избери CE04Bview, натисни върху него десния бутон на мишката и от менюто избери Add Virtual Function. В последствие добави реда в жълтия фон:void CEX04BView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: Add your specialized code here and/or call the base class
pDC->SetMapMode (MM_METRIC);
CView::OnPrepareDC(pDC, pInfo);
}
Сега коригирай и тази функция:
CEX04BView::CEX04BView():m_rectEllipce (0, 0, 400, -400)
{
// TODO: add construction code here
m_nColor = GRAY_BRUSH;
}
Напиши и кода в:
CEX04BView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
OnPrepareDC (&dc);
CRect rectDevice = m_rectEllipce;
dc.LPtoDP (rectDevice);
if (rectDevice.PtInRect(point)) {
if (m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect (rectDevice);
}
}
Прозорец с изглед за скролиране
Видимата част не е задължително да бъде “закотвена” в горния ляв ъгъл на прозореца. Чрез употребата на функциите
ScrollWindow и SetWindowOrg, класътSCrollView позволява местене на видимата част.Функциите на
ScrollView обработват съобщенията WM_HSCROLL и WM_VSCROLL. Изпращани към изгледа от лентите за скролиранеПриемане на вход от клавиатурата
Прави се на две стъпки.
Windows изпраща съобщенията WM_KEYDOWN и WM_KEYUP, с виртуални кодове на клавиши към даден прозорец, но преди да достигнат прозореца, те биват преведени Ако е натиснат ANSI символ (предизвикващ съобщение WM_KEYDOWN_, превеждащата функция проверява shrift статуса на клавиатурата и след това изпраща съобщение WM_CHAR, със съответния код. Клавишите за курсора и функционалните нямат символни кодове така, че не се налага превеждане. Прозорецът получава само съобщенията WM_KEYDOWN и WIN_KEYUP.Това може да се асоциира с
Class Wizard. Ако очакваш символ асоциирай WM_CHAR, а за друг клавиш WM_KEYDOWN.Скролиране – пример EX04C
Базовия клас Cview се променя на ScroolView
Добави променливите:
Модифицирай функцията
OnInitialUpdate:void CEX04CView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
//CSize sizeTotal;
// TODO: calculate the total size of this view
//sizeTotal.cx = sizeTotal.cy = 100;
//SetScrollSizes(MM_TEXT, sizeTotal);
CSize sizeTotal (2000, 3000); // 20 by 30 cm
CSize sizePage (sizeTotal.cx / 2, sizeTotal.cy / 2);
CSize sizeLine (sizeTotal.cx / 50, sizeTotal.cy / 50);
SetScrollSizes (MM_HIMETRIC, sizeTotal, sizePage, sizeLine);
}
Добави функция за обработка на събитието
WM_KEYDOWN. Class Wizard генерира член-функцията OnKeyDown, заедно с необходимите декларации и прототипи за асоцииране на съобщенията:void CEX04CView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
switch (nChar) {
case VK_HOME:
OnVScroll (SB_TOP, 0, NULL);
OnHScroll (SB_LEFT, 0, NULL);
break;
case VK_END:
OnVScroll (SB_BOTTOM, 0, NULL);
OnHScroll (SB_RIGHT, 0, NULL);
break;
case VK_UP:
OnVScroll (SB_LINEUP, 0, NULL);
break;
case VK_DOWN:
OnVScroll (SB_LINEDOWN, 0, NULL);
break;
case VK_PRIOR:
OnVScroll (SB_PAGEUP, 0, NULL);
break;
case VK_NEXT:
OnVScroll (SB_PAGEDOWN, 0, NULL);
break;
case VK_LEFT:
OnHScroll (SB_LINELEFT, 0, NULL);
break;
case VK_RIGHT:
OnHScroll (SB_LINERIGHT, 0, NULL);
break;
default:
break;
}
CScrollView::OnKeyDown(nChar, nRepCnt, nFlags);
}
Редактирай конструктора и функцията
OnDraw.CEX04CView::CEX04CView(): m_rectEllipse (0, 0, 4000, -4000)
{
// TODO: add construction code here
m_nColor = GRAY_BRUSH;
}
void CEX04CView::OnDraw(CDC* pDC)
{
CEX04CDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->SelectStockObject (m_nColor);
pDC->Ellipse (m_rectEllipce);
}
Асоциирай съобщението
WM_LBUTTONDOWN и направи промени в кода на функцията OnLButtonDown:Други съобщения в
WindowsWM_CREATE
Изпраща се при извикване на функцията
Create на прозореца от приложната среда, така че създаването на прозореца не е завършено и той не е видим. Функцията onCreate не може да извиква функции на Windows, които зависят от условието прозореца да бъде напълно функциониращ.WM_CLOSE
Изпраща се при затваряне на прозореца от системното меню и когато някой родителски прозорец бива затварян. Процеса на затваряне може да бъде контролиран чрез функцията
onClose.WM_QUERYENDSESSION
Изпраща се на всички работещи приложения, когато потребителя излиза от
Windows. Обработва се от функцията OnQueryEndSessio. Ако напишеш функция за WM_CLOSE напиши и една за WM_QUERYENDSESSION.WM_DESTROY
Изпраща се след съобщението
WM_CLOSE и функцията OnDestory го обработва. Прозореца е вече невидим но още “жив” както и дъщерните му прозорци.WM_NCDESTROY
Изпраща се при унищожаването на един прозорец. Всички дъщерни прозорци за вече унищожени. Обработва се от функцията
OnNcDestroyГлава 5
Създаване и унищожаване на
CDC обектиУнищожаването на обекти освобождава памет. Най-лесния начин за унищожаване на обекта за контекст на устройство е да се създаде в стека, по следния начин:
void CEX04CView::OnLButtonDown(UINT nFlags, CPoint point) {
CRect rect:
CClientDC dc(this); //constructs dc on the stack
dc.GetClipBox(rect);//retrieves the clipping rectangle
Конструктора
CclientDC приема за параметър указател към прозорец. Деструкторът се извиква, когато горната функция завърши изпълнението си. Деструкторът се извика,когато горната функция завърши изпълнението си. Може да получиш указател към контекст на устройство, като използваш функцията CWnd:GetDC. Накрая се извиква ReleaseDC, за освобождаване на памет, заделена за контекста на устройството.void CEX04CView::OnLButtonDown(UINT nFlags, CPoint point) {
CRect rect:
CDC* pDC = GetDC();// a pointer to internal c
pDC->GetClipBox(rect);//retrives the clipping rectangle
ReleaseDC (pDC);
Не трябва да унищожаваш
CDC обект предаден на OnDraw чрез указател.Състояние на контекста на устройството
Контекстът е необходим за чертане. Вида на начертаното и печатаното зависи от състоянието на контекста на устройството. Това състояние включва следното:
- Прикачени
- Режимът на съпоставяне, който определя мащаба на елементите при тяхното изчертаване;
- Различни детайлно, като параметри за подравняване на текста и режими за запълване на многоъгълници.
Класът
CPaintDCИзползва се когато се предефинира функцията
OnPoint на изгледа. По подразбиране тя извиква OnDraw със зададен контекст на устройството. Понякога трябва код за чертане специфичен за монитора.Ето една примерна функция
OnPaint:void CmyView::OnPaint() {
CPaintDC dc (this);
OnPrepareDC (&dc);
dc.TextOut (0, 0, “for the display, not the printer”);
OnDraw (&dc);
}
GDI обекти
Представлява клас производен на CGdiObject.
CBitmap – масив от битове, в който един или повече бита съответстват на всеки пиксел от екрана. Използват се за изображения и четки;
CBrush
– четка дефинира шаблони от пиксели, който е в стил bitmap. Използва се за запълване на области с определен цвят;CFont –
шрифт;CPalette
– Палитра с цветове за дадено приложение;CPen
– Писалка;CRgn
– Регионът е област от фигура. Може да се използва за запълване, отразяване и проверки за попадение.Следене на
GDI обектПърво трябва да го отделиш от контекста на устройството. Ето как става: Една функция, член на семейството функции
CDC::SelectObject, служи за избиране на обекта в контекста на устройството, като връща указател към предишния избран обект (който пък става “неизбран”). Не може да се премахне избора на един обект без да е избран друг. Обектите трябва а се проследяват. За целта се запазва първоначалния обект.void CEX5View::OnDraw(CDC* pDC)
{
CEX5Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CPen newPen (PS_DASHDOTDOT, 2, (COLORREF) 0); //black pen 2 pixeis wide
CPen* pOldPen = pDC->SelectObject (&newPen);
pDC->MoveTo (10, 10);
pDC->LineTo (110, 10);
pDC->SelectObject (pOldPen); //neewPen is deselected and automatically destroyed on exit
}
Готови
GDI обектиCDC::SelectStockObject
избира готов обект в контекста на устройството и връща указател към предишния. Ето пример за използване на нов обект като алтернатива на старияУказателя към
GDI обект трябва да се конвертира в Windows манипулатор (handle) с помощта на функцията GetSafeHdc преди да бъде съхранен.m_printFont->CreateFont (30, 10, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRESIC, EFAULT_QUALITY, DEDFAULT_PITCH|FF_MODERN, "Courir New");
pDC->SelectObject (CFont::FromHandle (m_hOldFont));
Шрифтове
Биват два чишита – машинно независими TrueType и останалите. Та 1 точка = 1/72 от инча; 1 инч = 2.554 см. Ако работиш с точки използвай MM_TWIPS. Един 8.5 точков шрифт е8.5 * 20 = 170 twip единици, като това е височината на символа.
Логически и физически размери на монитора
Връщат се от функцията CDC::eteviceCaps. Ето и стойностите които връща:
Индекс |
Описание |
Стойност |
HORSIZE |
физическа ширина в милиметри |
320 |
VERTSIZE |
физическа височина в милиметри |
240 |
HORZRES |
Ширина в пиксели |
640 |
VERTRES |
височина в растерни линии |
480 |
LOGPIXELSX |
хоризонтални точки за логически инч |
96 |
LOGPIXELSY |
вертикални точки за логически инч |
96 |
Ето и кода които установява режима за съпоставяне в логически
twip-единици:pDC->SetMapMode (MM_ANISOTROPIC);
pDC->SetWindowExt (1440, 1440);
pDC->SetViewportExt (pDC->GetDeviceCaps (LOGPIXELSX), pDC->GetDeviceCaps (LOGPIXELSY));
Изчисляване височината на символите
Функцията CDCGetTextMetric дава достъп до 5 параметъра за мерки на височината на шрифт, но само три от тях са по-важно.
tmHeight
– пълна височина на шрифта;tmExternalLeading
– разстоянието между горната част на диакритичния знак и долната част на камшичетата от горния ред. Тази стойност може да бъде и 0.tmInternalLeadin
Примерът ЕХ05А
1. Направи проекта
;2. Избери
Single Document;3.
Изключи опцията Printing and Print Preview;4. Другото е по подразбиране.
5. Добави функцията
OnPrepareDC;void CEX05AView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: Add your specialized code here and/or call the base class
pDC->SetMapMode (MM_ANISOTROPIC);
pDC->SetWindowExt (14440, 1440);
pDC->SetViewportExt (pDC->GetDeviceCaps (LOGPIXELSX), pDC->GetDeviceCaps (LOGPIXELSY));
CView::OnPrepareDC(pDC, pInfo);
}
6.
Добави помощната private функция ShowFont към класа на изгледа.- Добави прототипа
void ShowFont (CDC* pDC, int& nPos, int nPoints);
Сега добави и самата функция:
void CEX05AView::ShowFont (CDC* pDC, int& nPos, int nPoints)
{
void CEX05AView::ShowFont(CDC* pDC, int& nPos, int nPoints)
{
TEXTMETRIC tm;
CFont fontText;
CString strText;
CSize sizeText;
fontText.CreateFont (-nPoints * 20, 0, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, "Arial");
CFont* pOldFont = (CFont*) pDC->SelectObject (&fontText);
pDC->GetTextMetrics (&tm);
TRACE ("points = %d, tmHeight = %d, tmInternalLeading = %d,"
"tmExternalLeading = %d/n", nPoints, tm.tmHeight,
tm.tmInternalLeading, tm.tmExternalLeading);
strText.Format ("This is %d - point Arial", nPoints);
sizeText = pDC->GetTextExtent (strText);
TRACE ("string with = %d string height = %d\n", sizeText.cx, sizeText.cy);
pDC->TextOut (0, nPos, strText);
pDC->SelectObject (pOldFont);
nPos += tm.tmHeight + tm.tmExternalLeading;
}
Замени текста във функцията
OnDraw със следния:void CEX05AView::OnDraw(CDC* pDC)
{
//CEX05ADoc* pDoc = GetDocument();
//ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
int nPosition = 0;
int i;
for (i = 6; i < 24; i ++) {
pDC->TextOut (0,i * 200, i+50);
ShowFont (pDC, nPosition, i);
}
TRACE ("LOGPIXELSX = %d, LOGPIXELSY = %d\n",
pDC->GetDeviceCaps (LOGPIXELSX),
pDC->GetDeviceCaps (LOGPIXELSY));
TRACE ("HORZSIZE = %d, VERTSIZE = %d\n",
pDC->GetDeviceCaps (HORZSIZE),
pDC->GetDeviceCaps (VERTSIZE));
TRACE ("HORZRES = %d, VERTRES = %d/n",
pDC->GetDeviceCaps (HORZRES),
pDC->GetDeviceCaps (VERTRES));
}
Стартирай приложението през трасиращата програма
(debugger), за да видиш резултата от TRACE. Избери GO от подменюто Start Debug на менюто Build или натисни бутона от инструменталния панелПрограмни елементи на примера
Приложната функция
OnPrepareDC се извиква преди функцията OnDraw.Функцията
ShowFont се извиква 10 пъти за да създаде шрифта, покаже символен низ и след това де селектира шрифтаИзвиква се и
CFont::CreateFont. Това позоваване иска маса параметри. Първите два са височината и ширината на шрифта. Стойност 0 за ширината означава, че ще се използва ширината зададена на шрифта. Ако искаш шрифта да има специфичен размер в точки, параметърът за височина трябва да има отрицателна стойност. Ако се използва режим на съпоставяне MM_TWIPS за принтера, стойност –240 ще гарантира 12 точков шрифт. Последния параметър задава името на шрифта.Примерът
EX05BТова показва различни шрифтове. Режимът на съпоставяне е
MM_ANISOTROPIC.Та създай проекта а в последствие и функцията
OnPrepareDCvoid CEX05BView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: Add your specialized code here and/or call the base class
CRect clientRect;
GetClientRect (clientRect);
pDC->SetMapMode (MM_ANISOTROPIC); //+y = down
pDC->SetWindowExt (400, 450);
pDC->SetViewportExt (clientRect.right, clientRect.bottom);
pDC->SetViewportOrg (0, 0);
CView::OnPrepareDC(pDC, pInfo);
}
Това ти е функцията
OnDraw:void CEX05BView::OnDraw(CDC* pDC)
{
CEX05BDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CFont T1, T2, T3, T4;
CFont* pOldFont = pDC->SelectObject (&T1);
T1.CreateFont (50, 0, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, "Arial");
TraceMetrics (pDC);
pDC->TextOut (0, 0, "This is Arial, default wits");
T2.CreateFont (50, 0, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH|FF_MODERN, "Courier");
pDC->SelectObject (&T2);
TraceMetrics (pDC);
pDC->TextOut (0, 100, "This is Courier default width");
T3.CreateFont (50, 0, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH|FF_ROMAN, NULL);
pDC->SelectObject (&T3);
TraceMetrics (pDC);
pDC->TextOut (0, 200, "This is Roman variable width");
T4.CreateFont (50, 0, 0, 0, 400, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH|FF_MODERN, "LinePrinter");
pDC->SelectObject (&T4);
TraceMetrics (pDC);
pDC->TextOut (0, 300, "This is Line Pointer default width");
pDC->SelectObject (pOldFont);
}
При промяна размерите на прозореца някой шрифтове си сменят размера.
Примерът
EX05CЧертае се кръг и квадрат, Кръгчето може да се мести с мишата. Използва се четка (
pattern brush)Стартирай
App Wizard. Задай ScroolView за базов клас.Декларирай променливите:
private:
BOOL m_bCaptured;
CSize m_sizeOffset;
CPoint m_pointTopLeft;
const CSize msizeEllipse;
С помоща на
ClassWizard прибави три член функции към клас CEX05CView:
Съобщение |
Функция |
WM_LBUTTONDOWN |
OnLButtonDown |
WM_LBUTTONUP |
OnLButtonUp |
WM_MOUSEMOVE |
OnMouseMove |
Попълни кода във функциите:
void CEX05CView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CRect rectEllipse (m_pointTopLeft, m_sizeEllipse);
CRgn circle;
CClientDC dc(this);
OnPrepareDC (&dc);
dc.LPtoDP(rectEllipse);// Now it's in device coordinates
circle.CreateEllipticRgnIndirect (rectEllipse);
if (circle.PtInRegion(point)) {
// Capting the mouse ensures subsequent LButtonUp message
SetCapture ();
m_bCaptured = TRUE;
CPoint pointTopLeft (m_pointTopLeft);
dc.LPtoDP (&pointTopLeft);
m_sizeOffset = point - pointTopLeft; //device coordinates
//New mouse cursor is active while mouse is capture
::SetCursor (::LoadCursor (NULL, IDC_CROSS));
}
CScrollView::OnLButtonDown(nFlags, point);
}
void CEX05CView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
::ReleaseCapture();
m_bCaptured = FALSE;
CScrollView::OnLButtonUp(nFlags, point);
}
void CEX05CView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_bCaptured) {
CClientDC dc(this);
OnPrepareDC (&dc);
CRect rectOld (m_pointTopLeft, m_sizeEllipse);
dc.LPtoDP (rectOld);
InvalidateRect (rectOld, TRUE);
m_pointTopLeft = point - m_sizeOffset;
dc.LPtoDP (&m_pointTopLeft);
CRect rectNew (m_pointTopLeft, m_sizeEllipse);
dc.LPtoDP (rectNew);
InvalidateRect (rectNew, TRUE);
}
CScrollView::OnMouseMove(nFlags, point);
}
CEX05CView::CEX05CView(): m_sizeEllipse (100, -100), m_pointTopLeft (0, 0), m_sizeOffset (0, 0)
{
// TODO: add construction code here
m_bCaptured = FALSE;
}
void CEX05CView::OnDraw(CDC* pDC)
{
CEX05CDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CBrush brushHatch (HS_DIAGCROSS, RGB (255, 0, 0));
CPoint point (0, 0); // logical (0, 0)
pDC->LPtoDP (&point); //in device coordinates,
pDC->SetBrushOrg (point); // align the grush with the windwos origin
pDC->SelectObject (&brushHatch);
pDC->Ellipse (CRect (m_pointTopLeft, m_sizeEllipse));
pDC->SelectStockObject (BLACK_BRUSH); //Deselect brushHatch
pDC->Rectangle (CRect (100, -100, 200, -200)); //Text invalid rect
}
Променливата
m_sizeEllipse съхранява размера на кръга и позицията на горния му ляв ъгъл (m_pointTopLeft). При придвижване, програмата преизчислява m_pointTopLeft.Променливата
m_sizeOffset когато OnMouseMove движи елипсата съхранява отместването на мишката от горния ляв ъгъл.Функцията
SetCapture “прихваща” мишката, така че съобщенията от нейното движение да се изпращат към този прозорец дори и ако курсора се намира извън него. Това е неприятно понеже елипсата може да се изгуби. Необходимо е да се използва понеже всички съобщения на мишката се следят от нея.Когато първия параметър е
NULL, функцията LoadCursor създава курсорен ресурс от указания предифиниран курсор на мипката, . SetCursor активира указания курсорен ресурс. Този курсор остава активен, докато мишката е прихваната.Глава 6
Модалния диалог
Двата вида диалози са модален (modal) и немодален (modeless).
Базовия клас CDialog поддържа модални и немодални диалози. С модален диалог се работи докато не бъде затворен. С немодален може да се работи и в друго приложение, докато диалога остане на екрана. Пример за модален прозорец е прозореца Find and Replace на Microsovf Word.
Един диалог съдържа определени елементи наречени контроли. Диалоговите контроли включват:
Пример EX06A
Създай проекта, премахни опцията Printing And Print Preview.
Създай нов диалогов ресурс с идентификатора
IDD_DIALOG1. От менюто Resource, щракни с десен бутон на Dialog и натисни бутона NewЗадай стил на диалога: Stile – Popup, Border – Thin
Добави контролите в диалога посредством палитрата
Контрола за “статичен текст отпечатва символи на екрана.
Прибави още една Edit контрола като нейното ID направи ID_SSN. По-късно с Class Wizard ще го направиш числово поле.
Edit
контрола Bio (биография) е многоредово текстово поле след това промени характеристиките му:Group box
Групата
Insurance (застраховка). Този контрол съдържа три полета за отметки (check box). Въведи заглавието Insurance. Постави три check box контроли в групата. Характеристиките са по подразбиране. Смени идентификаторите на: IDC_LIFE, IDC_DIS и IDC_MED.Прибави
combo box контрола Educe (образование). Промени идентификатора му на IDC_EDUC. Постави три степени на образование.Постави контрола
List Box Dept. (департамент). Замени ID със IDC_DEPT. По-късно ще се въвеждат елементите.Постави
Combo box контрол Lang (език). ID:IDC_LANG. Избери страницата Style и задай стойност Drop List на опцията Type (да ама с тази опция не може да се избере CString тип за това избери dropdown). Прибави в страницата дата English, French и Spanish.Постави
Scroll bar контролите Loyalty и Reliability. Промени идентификаторите им на IDC_LOYAL и IDC_RELY.Постави
бутоните ОК, Cancel ако не са поставени и Special. Промени идентификатора му на IDC_SPECIALВъв всеки диалог може да се постави един битмап, при условие, че са дефинирани в ресурсите (
resource script).Иконата:
Може да се постави икона или битмап във всеки диалог при условие, че са дефинирани в описанието на ресурсите (resource script). Използвай програмната икона на MFC, идентифицирана като IDR_MAINNAME. Избери Icon за опцията Type и IDR_MAINFRAME за опцията Image. Остави идентификатора като IDC_STATIC.Може да се промени реда на обхождане на контролите. Избери Layout->Tab order или натисни
Ctrl+D. В последствие промени реда с натискане на мишката върху контролата. Ако сбъркаш частично използвай Ctrl и левия бутон на мишката върху последния правилно избран контрол.Натисни ОК.
Въведи името CE06Adialog
Добави променливите на
CE06ADialog. Щракни върху етикета Member Variables:
Control ID |
Data Member |
Type |
IDC_BIO |
MstrBio |
CString |
IDCC_ACT |
m_nCat |
int |
IDC_DEPT |
m_strDept |
CString |
IDC_DIS |
m_bInsDi |
BOOL |
IDC_EDUC |
m_strEduc |
CString |
IDC_LANG |
m_strLang |
CString |
IDC_LIFE |
m_bInsLife |
BOOL |
IDC_LOYAL |
m_nLoyal |
int |
IDC_MED |
m_bInsMed |
BOOL |
IDC_NAME |
m_strName |
CString |
IDC_RELY |
m_nRely |
int |
IDC_SKILL |
m_strSkill |
CString |
IDC_SSN |
m_nSsn |
int |
Когато избираш контроли в
Class Wizard, в долната му част се появяват различни текстови полета. Ако избереш променлива от тип CString, може да зададеш максималния брой символи; ако избереш числова променлива може да зададеш долна и горна граница. Задай минималната стойност на IDC_SSN да бъде 0, а максималната 99999999.Класът
CDialog асоциира целочислена (integer) променлива с всяка група радио бутони, като първия има стойност 0, втория 1 и т.н.Добави функция за обработка на съобщения за бутона
Special. Извикай Class Wizard, избери страницата Message Maps, в списъка Objet IDs намери реда IDC_SPECIAL и натисни BN_CLICKED. Извикай създадената функция с два пъти клик и въведи кода:void EX06ADialog::OnSpecial()
{
TRACE ("EX06ADialog::OnSpecial")
}
С помощта на
Class Wizard прибави функцията OnInitDialog. Class Wizard генерира код, който инициализира контролите на диалога. Този код DDX няма да инициализира редовете на списъка (list box choices), така че трябва да предефинираш функцията CDialog:: OnInitDialog. Class Wizard генерира скелет на тази функция въпреки, че тя е виртуална, ако решиш да обработиш съобщението WM_INITDIALOG в производния клас. Избери EX06ADialog от списъка Object ID, след което двукратно клик върху съобщението WMINITDIALOG от списъка Messages. Натисни бутона Edit Code и редактирай функцията OnInitDialog.BOOL EX06ADialog::OnInitDialog()
{
CListBox* pLB = (CListBox*) GetDlgItem (IDC_DEPT);
pLB->InsertString (-1, "Documentation");
pLB->InsertString (-1, "Accounting");
pLB->InsertString (-1, "Human Relations");
pLB->InsertString (-1, "Security");
return CDialog::OnInitDialog();}
Може да използваш същата техника за инициализация на комбинираните полета, вместо да ги инициализираш в ресурса.
Свързване на диалога към изгледа
За целта ще се използва
WM_LBUTTONDOWN:1. Избери класа
CE06AVew от Class Wizard;2.
Добави функцията OnLButtonDown като щракнеш два пъти върху съобщението WM_LBUTTONDOWN.5.
Добави към EX06AWiew#include "stdafx.h"
#include "EX06A.h"
#include "EX06Dalog.h"
4. Напиши кода
:void CEX06AWiew::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CEX06Dalog dig;
dig.m_strName = "Shakespeare, Will";
dig.m_nSsn = 307606636;
dig.m_nCat = 1; // 0 = Hourly, 1 = Salary
dig.m_strBio = "This person is not a well-motivated tech writer";
dig.m_bInsLife = TRUE;
dig.m_bInsMed = TRUE;
dig.m_strDept = "Documentation";
dig.m_strSkill = "Writer";
dig.m_strLang = "English";
dig.m_strEduc = "Средно";
dig.m_nLoyal = dig.m_nRely = 50;
int ret = dig.DoModal();
TRACE ("DoModal return = %d\n", ret);
TRACE ("name = %s, ssn = %d, cat = %d\n",
dig.m_strName, dig.m_nSsn, dig.m_nCat);
TRACE ("dept = %s, skill = %s, lang = %s, educ = %s\n",
dig.m_strDept, dig.m_strSkill, dig.m_strEduc);
TRACE ("life = %d, dis = %d, med = %d, bio = %s\n",
dig.m_bInsLife, dig.m_bInsDis, dig.m_bInsMed, dig.m_strBio);
TRACE ("loyalty = %d, realiability = %d\n",
dig.m_nLoyal, dig.m_nRely);
CView::OnLButtonDown(nFlags, point);
}
5.
Добави кода:void CEX06AView::OnDraw(CDC* pDC)
{
pDC->TextOut (0, 0, "Prees the left mouse button here");
}
6.
Тествай примера. Въведи във всеки контрол и натисни ОК. Погледни резултатите в TRACE джама.Когато някоя функция извика
DoMOdal, управлението се връща в след затваряне на диалога.OnInitDialog
и DoDataExchange са виртуални функции, предефинирани в диалоговия клас CEX06ADialog.Widnws извиква OnInitDataExchange, като част от процеса на инициализации това предизвиква извикване на DoDataEchange.Контролиране на изхода при
OnOKПри натискане на
Enter Windows проверява върху кой бутон се намира фокуса. Ако няма бутон във фокуса Windows търси бутона по подразбиране указан в програмата или в ресурса. Ако няма такъв се извиква виртуалната функция OnOK дори и ако няма бутон ОК.Може да се забрани изхода при натискане на
Enter ако се промени кода на функцията CE06ADialog::OnOK, която не прави нищо и добавиш код за изход от диалога към нова функция, която отговаря на натискането на бутона ОК.Може да забраниш изхода при натискане на
Enter, като напишеш функцията CEX06ADialog::OnOK. Следвай стъпките:1. Със
Class Wizard прикачи бутона IDOK към виртуалната функция OnOK. Избери IDOK от списъка с идентификаторите на обекти (Object IDs) на CEX06ADialog, после BN_CLIKED. Това генерира скелета на OnOK;2.
3. Създай функцията OnClickedOk. Тази член-функция е асоциирана със съобщението BN_CLICKED на контрола IDC_OK;
4. Редактирай функциите:
4. Редактирай функциите
void CEX06Dalog::OnOK()
{
// TODO: Add extra validation here
TRACE ("CEX06ADalog::OnOK\n");
}
void CEX06Dalog::OnClickedOk()
{
// TODO: Add your control notification handler code here
TRACE ("CEX06ADalog::OnClickedOk\n");
CDialog::OnOK();
}
5. Тествай примера. Сега при всяко натискане на
Enter в прозореца Debug се появява резултата а диалога не се затваря.При натискане на
Esc се извиква функцията OnCancel като DoModal връща IDCCANCEL. Това може да се заобиколи посредством фалшива функция подобна на предишната.Закачване на scroll bar контролите
Те имат стойности за обхват и позиция, които могат да бъдат четени и записани. Функцията
SCrollBar::SetScrollPos също задава позицията на плъзгача. Scroll bar контролите изпращат съобщения WM_HSCROLL и WM_VSCROLL на диалога, при преместване на плъзгача или щракване с мишката върху стрелките.1. Добави
enum изрази за минимума и максимума на обхвата на скролиране. В EX06ADialog.h се добавя:enum {nMin = 0};
enum {nMax = 100};
2.
Редактирай функцията OnInitDialog, за инициализация на обхватите на скролиране. Променливите задават стойностите в проценти:CScrollBar* pSB = (CScrollBar*) GetDlgItem (IDC_LOYAL);
pSB->SetScrollRange (nMin, nMax);
pSB = (CScrollBar*) GetDlgItem (IDC_RELY);
pSB->SetScrollRange (nMin, nMax);
3.
Добави функция за обработка на съобщенията към CEX06ADialog. Избери съобщението WM_HSCROLL и прибави функцията OnHScroll:void CEX06Dalog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
int nTemp1, nTemp2;
nTemp1 = pScrollBar->GetScrollPos();
switch (nSBCode) {
case SB_THUMBPOSITION:
pScrollBar->SetScrollPos (nPos);
break;
case SB_LINELEFT:
nTemp2 = (nMax - nMin) / 10;
if ( (nTemp1 - nTemp2) > nMin ){
nTemp1 -= nTemp2;
}
else {
nTemp1 = nMin;
}
pScrollBar->SetScrollPos (nTemp1);
break;
case SB_LINERIGHT:
nTemp2 = (nMax - nMin) / 10;
if ( (nTemp1 + nTemp2) < nMax ) {
nTemp1 += nTemp2;
}
else {
nTemp1 = nMax;
}
pScrollBar->SetScrollPos (nTemp1);
break;
}
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
Задаване на цвета за фон на диалога и за контролите
Член променливите m_hYellowBrush и m_hRedBrush са тип HBRUSH, които се инициализират във функцията OnInitDialog на диалога. Параметъра nCtlColor указва типа на контролата, а параметъра pWnd служи за идентифициране на съответния контрол На диалога родител се изпраща съобщение WM_CTLCOLOR за всеки контрол преди показването. Ако се асоциира това съобщение с член функция за негова обработка може да се сменя цвета на фона. Ако искаш да зададеш цвят на отделен контрол, трябва да конвертираш pWin в идентификатор на дъщерен прозорец.
HBRUSH CEX06Dalog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if (nCtlColor == CTLCOLOR_EDIT) {
pDC->SetBkColor (RGB (255, 255, 0)); //RED
//return m_hYellowBrush;
}
if (nCtlColor == CTLCOLOR_DLG) {
pDC->SetBkColor (RGB (255, 0, 0)); //RED
//return m_hRedBrush;
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
Диалога извиква
Win332 функцията Send Message, за да подаде (send) съобщението директно. Така функцията може да върне параметър в случая манипулатор (handle) за четка.Рисуване в прозореца на диалога
Може да се рисува в клиентската област на диалоговия прозорец, но трябва да се рисува само в отделните контроли. Ако искаш да покажеш само текст използвай диалоговия редактор и създай празен статичен текст с уникален идентификатор, след което извикай функцията
CWnd::SetDlgItemText в тялото на някоя член функция на диалоговия клас (като OnInitDialog) за да поставиш текст в контролата.За показване на графика трябва да се добави член-функцията
OnPaint към диалога. Тази функция преобразува идентификатора на статичния контрол в CWnd указател и да извлича неговия контекст на устройството. Трябва да се чертае в съответния контрол без да се позволи на Windows да го изтрие. Това се прави с последователността Invalidate \ Update window.void CEX06Dalog::OnPaint()
{
CWnd* pWind = GetDlgItem (IDC_Paint); //IDC_STATIC1 specified
CDC* pControlDC = pWind->GetDC();
pWind->UpdateWindow();
pControlDC->SelectStockObject (BLACK_BRUSH);
pControlDC->Rectangle (0, 0, 10, 10); //black square bullet
pWind->ReleaseDC (pControlDC);
}
Тази функция може да се извика от друга функция с:
Initialidate();
Добавяне на контроли по време на изпълнение
1. Добави вградена член-променлива от тип “прозорец на контрол” към диалогов клас . В MFC, класовете за прозорци на контролите включват CButton, CEdit, CListBox и CComboBox. Един С++ обект от тип “вграден контрол” (embedded control) се създава и унищожава заедно с обекта-диалог;
2. Избери Resource Symbols от меню View на Developer Studio. Добави ID константата за новия контрол;
3.
Използвай Class Wizard, за да асоциираш съобщението WM_INITDIALOG , и следователно предефинирай CDialog::OnInitDialog. Тази функция трябва да извика член-функцията Create на прозореца на диалога. Windows ще унищожи прозореца на контрола, когато унищожава прозореца на диалога;4. Във производен диалогов клас добави ръчно необходимите функции за обработка на нотифициращи съобщения за новия контрол.
Индикатор на прогрес (
progress indicator)Инициализира се с член-функциите
SetRange и SetPos във функцията OnInitDialog. SetPos може да се извиква по всяко времеTrackbar –
контрола (плъзгач)Позволява задаване на аналогова стойност. От клас CSliderCtrlю. По-ефективна е от scroll bar. Може да се поставят графични означения (по програмен път) съответстващ на интервалите на придвижване. По подразбиране нямат обхват. Функцията GetPos връща стойността на контролата.
Бутон за превъртане (spin button)
Съставен е от две стрелки. Потребителя може да увеличава и намалява стойността в текстовото поле. Ако програмата използва целочислени стойности се избягва писането на код. Използвай
Class Wizard за прикачване на член-променливата от целочислен тип към edit контролата и задай обхвата на превъртане във функцията OnInitDialog. Избери Auto Buddy и Set Buddy Integer от страницата Styles. Може да извикаш функциите SetRange и SetAccel от OnInitDialog, за промяна обхвата и профила за ускоряванеАко искаш текстовото поле да показва нецелочислена стойност, като време или часа с плаваща запетая, трябва да асоциираш неговите съобщения
WM_VSCROLL (или WM_HSCROLL) и да напишеш кода на функцията за обработка на тези съобщения. която да преобразува целочислената стойност на spin-бутона в стойността, която “приятеля” трябва да съдържа.Контролът за списъчен изглед
Може да съдържа текст и изображения. Елементите са подредени в мрежа от точки. При избиране на елемент контрола изпраща нотифициращо съобщение, което асоциираш в диалоговия клас. Така може да се определи кой елемент е избран. Елементите са асоциирани с целочислен индекс започващ от нула.
Списъчния и дървовидния изглед вземат своите изображения от един елемент на контролите с общо предназначение, наречен списък с изображения (клас
CImageList). OnInitDialog е подходящо място за създаване и свързване на списъка, както и за задаване на символи низове4 за заглавия на елементи от списъка. За тази цел служи InserItem.Дървовиден изглед
Той е от клас CTreeCtrl. Използва HTREEITEM манипулатор (handle) вместо целочислен индекс. За добавяне на елементи се извиква InsertItem, но първо се създава структура TV_INSERTSTRUCT. Има неограничени възможности за потребителска настройка.
Съобщението WM_NOTIFY
Контролите изпращат своите нотификации в
WM_COMMAND. Стандартните съобщения wParam и IParam не са достатъчни за информацията, която един контрол с общо предназначение трябва да изпрати на своя родител. За това се дефинира и съобщението WM_NOTIFY.Примерът
EX06B1. Избери
Single Document и изключи опцията Print And Print Preview.2.
Ред на обхождане |
Тип контрол |
ID на дъщерен джам |
1 |
Статичен текст |
IDC_STATIC |
2 |
Индикатор за прогрес |
IDC_PROGRESS1 |
3 |
Статичен текст |
IDC_STATIC |
4 |
Track bar |
IDC_TRACKBAR1 |
5 |
Статичен текст |
IDC_STATIC_TRACK1 |
6 |
Статичен текст |
IDC_STATIC |
7 |
Track bar |
IDC_TRACKBAR2 |
8 |
Статичен текст |
IDC_STATIC_TRACK2 |
9 |
Статичен текст |
IDC_STATIC |
10 |
Текстово поле |
IDC_BUDYSPIN1 |
11 |
Бутон за превъртане |
IDC_SPIN1 |
12 |
Статичен текст |
IDC_STATIC |
13 |
Статичен текст |
IDC_STATIC |
14 |
Списъчен преглед |
IDC_LISTVIEW1 |
15 |
Статичен текст |
IDC_STATIC_LISTVIEW1 |
16 |
Статичен текст |
IDC_STATIC |
17 |
Дървовиден изглед |
IDC_TREEVIEW1 |
18 |
Статичен текст |
IDC_STACI_TREEVIEW1 |
19 |
Бутон |
IDOK |
|
Бутон |
IDCANCEL |
3.
Оправи реда за тяхното обхождане;4. Създай нов клас CEX056BDialog, производен на CDialog с помощта на
Class Wizard. Асоциирай съобщенията WM_HSCROLL и WM_VSCROLL;5.
Напиши кода за индикатора на прогрес. Добави public член-променлива от тип integer, наречена m_nProgress в декларацията на класа CEX06BDialog. Задай и стойност в конструктора. Добави и следния код във функцията OnInitDialog на съобщението WM_INITDIALOG:BOOL CEX06BDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
CProgressCtrl* pPRog = (CProgressCtrl*) GetDlgItem (IDC_PROGRESS1);
pPRog->SetRange (0, 100);
pPRog->SetPos (m_nProgress);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
6.
Напиши кода за плавно движещ се плъзгач. Добави public целочислена променлива с име m_nTrackbar1 към декларацията на клас CEX06BDialog и я инициализирай със стойност 0 в конструктора. В последствие добави следния код във функцията OnInitDialog, за задаване обхват на плъзгача, инициализирай неговата позиция и настой съседния статичен контрол спрямо неговата стойност.CString strText1;
CSliderCtrl* pSlide1 = (CSliderCtrl*) GetDlgItem (IDC_TRACKBAR1);
pSlide1->SetRange (0, 100);
pSlide1->SetPos (m_nTrackbar1);
strText1.Format ("%d", pSlide1->GetPos());
SetDlgItemText (IDC_STATIC_TRACK1,strText1);
За да поддържаш състоянието на статичния контрол в съответствие с положението на плъзгача, трябва да асоциираш съобщението
WM_HSCROLL, което trackbar-контрола изпраща на диалога:void CEX06BDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;
strText.Format ("%d", pSlide->GetPos());
SetDlgItemText (IDC_STATIC_TRACK1, strText);
}
Налага се да се обновява променливата
m_nTrackbar1 при натискане на ОК и Cancel. За това добави във функцията DoDataExchange:if (pDX->m_bSaveAndValidate) {
TRACE ("Updating rackbar data members\n");
CSliderCtrl* PSlide1 = (CSliderCtrl*) GetDlgItem (IDC_TRACKBAR1);
m_nTrackbar1 = PSlide1->GetPos();
}
7. Напиши кода за плъзгача с дискретни стойности.
Добави public целочислената променлива m_nTrackbar2 към декларацията на CEX06BDialog и й задай стойност 0 в конструктора. Тази променлива е с нулево базиран индекс за dValue – масива от числа (4, 5.6, 8, 11, 16), които trackbar-контрола може да представя. В EX06BDialog.h дефинирай private член-променливата dValue като статичен масив от тип double, а в EX06BDialog.cpp добави:double CEX06BDialog::dValue [5] = {4.0, 5.6, 8.0, 11.0, 16.0};
За задаване на първоначална стойност и обхвата на плъзгача добави в член-функцията
OnInitDialog:CString strText2;
CSliderCtrl* pSlide2 = (CSliderCtrl*)
GetDlgItem (IDC_TRACKBAR2);
pSlide2->SetRange (0, 4);
pSlide2->SetPos (m_nTrackbar2);
strText2.Format ("%3.1f", dValue [pSlide2->GetPos()]);
SetDlgItemText (IDC_STATIC_TRACK2, strText2);
Функцията
OnHScrooll трябва да различава двете trackbar контроли. За целта се добавя:void CEX06BDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;
switch (pScrollBar->GetDlgCtrlID() {
case IDC_TRACKBAR1:
strText.Format ("%d", pSlide->GetPos());
SetDlgItemText (IDC_STATIC_TRACK1, strText);
break;
case IDC_TRACKBAR2:
strText.Format ("%3.1f", dValue [pSlide->GetPos()]);
SetDlgItemText (IDC_STATIC_TACK2, strText);
break;
}
}
Trackbar
контрола се нуждае от маркировка. За целта включи характеристиките Tick Marks и Auto Tick. Добави към DoDataExchange на диалоговия клас в рамките на блока на условния оператор if:CSliderCtrl* pSlide2 = (CSliderCtrl*) GetDlgItem (IDC_TRACKBAR2);
m_nTrackbar2 = pSlide2->GetPos();
Задай характеристиката
Point и на двата плъзгача в състояние Bottom/Right. Избери Right за характеристиката Align Text на IDC_STATIC_ТRACK1 и IDC_STATIC_TRACK_28. Напиши кода за
spin бутона. Контрола за превъртане зависи от неговия приятел, разположен преди него в реда на обхождане на диалога. Добави член-променливата m_dSpin от тип double, за един контролата IDC_BUDDY_SPIN1 c Class Wizard. Обхвата на текстовото поле ще бъде 0.0 до 10.0, но самия бутон иска целочислен обхват. За задаване обхвата на бутона от 0 до 1000 и началната му стойност m_dSpin * 10 добави към OnInitDialog:CSpinButtonCtrl* pSpin = (CSpinButtonCtrl*) GetDlgItem (IDC_SPIN1);
pSpin->SetRange (0, 100);
pSpin->SetPos((int) (m_dSpin * 10));
За показване текущата стойност в “приятелския”
edit-контрол трябва да асоциираш съобщението WM_VSCROLL, което spin-контрола изпраща в диалога:void CEX06BDialog::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
if ( nSBCode == SB_ENDSCROLL ) {
return; //Reject spurious messages
}
if ( pScrollBar->GetDlgCtrlID() == IDC_SPIN1 ) {
CString strValue;
strValue.Format ("%3.1f", (double) nPos / 10.0);
((CSpinButtonCtrl*) pScrollBar)->GetBuddy()->SetWindowText (strValue);
}
}
Не се добавя код в
OnOK или в DoDataChange, защото кодът за обмен на данни на диалога обработва съдържанието на edit-контролът. От диалоговия редактор, избери характеристиката Buddy за спин бутона и характеристиката Read-only за “приятеля” на бутона.9.
Задай списък с изображения. Използвай графичния редактор за да добавиш икони към RC файла на проекта. Прибави 8 икони. Към декларацията на класа CEX06BDialog добави променливата от клас CImageList m_ImageList, след което към OnInitDialog следния код:HICON hIcon[8];
int n;
m_ImageList.Create (16, 16, 0, 8, 8); // 32, 32 for large icons
hIcon[0] = AfxGetApp()->LoadIcon(IDI_ICON1);
hIcon[1] = AfxGetApp()->LoadIcon(IDI_ICON2);
hIcon[2] = AfxGetApp()->LoadIcon(IDI_ICON3);
hIcon[3] = AfxGetApp()->LoadIcon(IDI_ICON4);
hIcon[4] = AfxGetApp()->LoadIcon(IDI_ICON5);
hIcon[5] = AfxGetApp()->LoadIcon(IDI_ICON6);
hIcon[6] = AfxGetApp()->LoadIcon(IDI_ICON7);
hIcon[7] = AfxGetApp()->LoadIcon(IDI_ICON8);
for ( n = 0; n < 8; n++ )
m_ImageList.Add (hIcon[n]);
10.
Задай
Border от More Styles. Добави към OnInitDialog:static char* color[] = {"white", "black", "red", "blue", "yellow", "cyan", "purple", "green"};
CListCtrl* pList = (CListCtrl*) GetDlgItem (IDC_LISTVIEW1);
pList->SetImageList (&m_ImageList, LVSIL_SMALL);
for ( n = 0; n < 8; n++ )
pList->InsertItem (n, color[n], n);
pList->SetBkColor (RGB (0, 255, 255)); // UGLYI
pList->SetTextBkColor (RGB (0, 255, 255));
Ако асоциираш съобщението
LVN_ITEMCHANGED на обекта IDC_LISTVIEW1 може да следиш избора на елементи от потребителя.void CEX06BDialog::OnItemchangedListview1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CListCtrl* pList = (CListCtrl*) GetDlgItem(IDC_LISTVIEW1);
int nSelected = pNMListView->iItem;
if (nSelected>= 0) {
CString strItem = pList->GetItemText (nSelected, 0);
SetDlgItemText (IDC_STATIC_LISTVIEW1, strItem);
}
*pResult = 0;
}
11.
След това добави следния код към
OnInitDialog:CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem (IDC_TREEVIEW1);
pTree->SetImageList (&m_ImageList, TVSIL_NORMAL);
TV_INSERTSTRUCT tvinsert;
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_TEXT;
tvinsert.item.hItem = NULL;
tvinsert.item.state = 0;
tvinsert.item.stateMask = 0;
tvinsert.item.cchTextMax = 6;
tvinsert.item.iSelectedImage = 1;
tvinsert.item.cChildren = 0;
tvinsert.item.lParam = 0;
//top level
tvinsert.item.pszText = "Home";
tvinsert.item.iImage = 2;
HTREEITEM hDag = pTree->InsertItem (&tvinsert);
tvinsert.item.pszText = "Marge";
HTREEITEM hMon = pTree->InsertItem (&tvinsert);
//second level
tvinsert.hParent = hDag;
tvinsert.item.pszText = "Bart";
tvinsert.item.iImage = 3;
pTree->InsertItem (&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem (&tvinsert);
//second level
tvinsert.hParent = hMon;
tvinsert.item.pszText = "Bar";
tvinsert.item.iImage = 4;
pTree->InsertItem (&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem (&tvinsert);
tvinsert.item.pszText = "Dilbert";
HTREEITEM hOther = pTree->InsertItem (&tvinsert);
//thirdlevel
tvinsert.hParent = hOther;
tvinsert.item.pszText = "Dogbert";
tvinsert.item.iImage = 7;
pTree->InsertItem (&tvinsert);
tvinsert.item.pszText = "Ratbert";
pTree->InsertItem (&tvinsert);
Този код задава индексите на изображенията и текстовете от
TV_INSERTSTRUCT и извиква InsertItem за добавяне възли към дървото.Накрая асоциирай съобщението
TVN_SELCHANGED, на контролата за дървовиден изглед. Ето кода за показване на избрания текст в статичен контрол:void CEX06BDialog::OnSelchangingTreeview1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem (IDC_TREEVIEW1);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if ( hSelected != NULL ) {
char text [31];
TV_ITEM item;
item.hItem = hSelected;
item.pszText = text;
item.cchTextMax = 32;
VERIFY (pTree->GetItem(&item));
SetDlgItemText (IDC_STATIC_TREEVIEW1, text);
}
*pResult = 0;
Структурата
NMTREEVIEW има поле, наречено itemView, което съдържа информацията за избрания възел, а манипулаторът на този възел е itemNew.hItem. Функцията GetItem извлича данните на възела, като съхранява текста използвайки указател, предоставен в структурата TV_ITEM. Променливата mask указва на Windows, че манипулаторът hItem е валиден и че се изисква текстов изход.Добави и:
void CEX06BView::OnDraw(CDC* pDC)
{
pDC->TextOut (0, 0, "Натисни левия бутон на мишката");
}
Добави в началото на файла
CEX06BView директивата:#include "EX06BDialog.h"
Добави с
Class Wizard и OnLButtonDown чрез съобщението WM_ONLBUTTONDOWN.CEX06BDialog dig;
dig.m_nTrackbar1 = 20;
dig.m_nTrackbar2 = 2; //indec for 8.0
dig.m_nProgress = 70; //write only
dig.m_dSpin = 3.2;
dig.DoModal();
Функционалността на индикатора за прогрес ще се прави в 11 глава.
Немодален диалог
Модален диалог се създава като се използва конструктора
CDialog, който приема като параметър идентификатор на ресурсен шаблон. Показва се, чрез извикването на функцията DoModal(). Прозореца престава да съществува когато завърши изпълнението на DoModal.Немодалните диалози са по-сложни. Започва се с извикването на
CDialog, за създаването на диалоговия обект но се извиква функцията CDialog::Create вместо DoModal.Дефинирани от потребителя съобщения
Има две възможности за изпращане на съобщения в Windows: функцията CWnd::SendMessage или PostMessage. Първата предизвиква незабавно извикване на функцията за обработка на съобщения, а втората изпраща съобщение в опашката със съобщения на Windows. При използване на втората изгледа ще получи съобщение с малко закъснение. Диалоговия клас трябва да следи своя изглед посредством член-променливи.
Примерът
EX07AМоже да се конвертира модален диалог в немодален. Но в примера се създава нов диалог.
1.Създай диалога както е правено до сега;
2. Създай нов диалог
. Промени заглавието му на Modeless Dialog. Добави статичен текст и текстово поле със заглавие IDC_EDIT1;3.
Създай класа CEX07ADialog. Добави функциите:
ID обект |
Съобщение |
Функция |
IDCANCEL |
BN_CLICKED |
OnCancel |
IDOK |
BN_CLICKED |
OnOK |
4. Добави променлива към класа
CEX07ADialog. Към контролата IDC_EDIT1 с Class Wizard добави променливата m_strEdit1 от клас CString;5.
Редактирай CEX07Dialog.h, за да добавиш указател към изглед и прототипи на функции. Въведи следния код към декларацията на класа CEX07ADialog:private
CView* m_pView
Добави и следните прототипи на функции:
public
CEX07ADialog (CView*, pView);
BOOL Create();
Употребата на клас
CView вместо на CEX07AView позволява на диалоговия клас да бъде използван с който и да е изгледен клас.6. Добави към
EX07ADialog.h идентификатор на съобщение, наречен WM_GOODBYE#define WM_GOODBYE WM_USER + 5
7.
Добави и:CEXO7ADialog::CEX07ADialog(CView *pView)
{
m_pView = NULL;
}
Трябва да добавиш към модалния конструктор следния ред:
m_pView = NULL;
Компилатора прави разлика между немодален конструктор
CEX07ADialog (CView*) и модалния – CEX07ADialog (CWnd*). При срещане на първия се генерира немодален конструктор а при втория модален.8. Добави и:
BOOL CEXO7ADialog::Create()
{
return CDialog::Create (CEXO7ADialog::IDD);
}
9.
Редактирай функциите OnOK и OnCancel от EX07ADialog.cppvoid CEXO7ADialog::OnCancel()
{
// TODO: Add extra cleanup here
if (m_pView != NULL) {
//modeless case - do not call base class OnCance
m_pView->PostMessage (WM_GOODBYE, IDCANCEL);
}
else {
CDialog::OnCancel();// modal case
}
CDialog::OnCancel();
}
void CEXO7ADialog::OnOK()
{
// TODO: Add extra validation here
if ( m_pView != NULL ) {
//modeless case - do not call base class OnOK
UpdateData (TRUE);
m_pView->PostMessage (WM_GOODBYE, IDOK);
}
else {
CDialog::OnOK(); //modal case
}
CDialog::OnOK();
}
Ако диалога е немодален той изпраща на изгледа дефинираното от потребителя съобщение
WM_GOODBYE.За немодалния диалог трябва да си сигурен че не извикваш функциите
CDialog::OnOK или CDialog::OnCancel. Трябва да предефинираш тези виртуални функции във производния клас. В противен случай, при натискането на Enter и Esc ще доведе до извикването на функциите на базовия клас извикващи функцията EndDialog. В еди немодален диалог вместо да извикваш функцията DestoryWindow, ако е необходимо извикай UpdateData, за осъществяване трансфер на данните от контролите на диалога в член-променливите на диалоговия клас.10.
Добави към EX07AView.h указател за съхраняване на диалогаprivate:
CEXO7ADialog* m_nDlg;
Ако добавиш
forward декларациятаclass CEXO7ADialog;
в началото на
EX07AView.h, няма да се налага да включваш EX07ADialog.h във всеки модул, който включва EX07AVew.h.11.
Модифицирай конструктора и деструктора на CEX07AView във файла EX07AView.cpp. Класът CEX07AView има променлива m_pDlg, която сочи към обекта от клас CEX07ADialog на изгледа. Конструкторът на изгледа създава диалоговия обект в динамичната памет, а деструкторът го изтрива. Добави:CEX07AView::CEX07AView()
{
// TODO: add construction code here
//m_pDlg = new CEXO7ADialog (this); //Това не пише къде да го декларирам.
}
CEX07AView::~CEX07AView()
{
//delete m_pDlg //destroy not already destroyed
}
12. Добави към виртуалната функция
OnDraw в EX07AView.cppvoid CEX07AView::OnDraw(CDC* pDC)
{
// TODO: add draw code for native data here
pDC->TextOut (0, 0, "Press left mouse button here");
}
13. Добави функции за обработка на мишката с
Class Wizard. Използвай съобщенията WM_LBUTTONDOWN и WM_RBUTTONDOWN. В последствие добави кода.void CEX07AView::OnLButtonDown(UINT nFlags, CPoint point)
{
// create the dialog if not created already
if (m_pDlg->GetSafeHwnd() == 0) {
m_pDlg->Create(); //display the dialog window
}
CView::OnLButtonDown(nFlags, point);
}
void CEX07AView::OnRButtonDown(UINT nFlags, CPoint point)
{
m_pDlg->DestroyWindow();
CView::OnRButtonDown(nFlags, point);
}
14.
Добави в EX07AVew:#include "EX07AView.h"
#include "EXO7ADialog.h"
15.
Добави собствен код за съобщението WM_GOODBYEПонеже
Class Wizard не поддържа дефинирани от потребителя съобщения, трябва сам да напишеш кода.В
EX07AVew.cpp добави следния ред след израза BEGIN_MESSAGE_MAP, но извън “скобите” (обхващащите коментари AFX_MSG_MAP:В същия файл добави и функцията:
LRESULT CEX07AView::Goodbye(WPARAM wParam, LPARAM lParam)
{
//message recived in response to modeless dialog OK and Cancel buttons.
TRACE ("CEX07AVew::OnGoodbye %x, %lx\n", wParam, lParam);
TRACE ("Dialog edin1 contents = %s\n", (const char*) m_pDlg->m_strEdit1);
m_pDlg->DestroyWindow();
return 0L;
}
В
EX07AVew.h добави прототип на функцията, преди израза за DECLARE_MESSAGE_MAP() но извън обхващащите коментари AFX_MSG:afx_msg LRESULT OnGoodbye (WPARAM wParam, LPARAM lParam);
Параметрите
wParam и lParam са средства за предаване на данни със съобщенията. Например при съобщение, генерирано от натискане на бутон на мишката, координатите на курсора са пакетирани в стойността lParam. При MFC библиотеката, данните на съобщенията се предават чрез по-смислени параметри. Позицията на мишката се предава като обект от клас CPoint. Дефинираните от потребителя съобщения трябва да използват параметрите wParam и lParam. В този пример идентификатора за бутона е сложен във wParam.16.
Тествай приложението. Натисни левия бутон, после десния (курсора на мишката трябва да бъде извън диалога при натискане на десния бутон).Диалози с общо предназначение
Това са група стандартни диалози за общо предназначение. Те се поддържа и от класовете на
MFC библиотеката. Всички класове за диалози с общо предназначение са производни на един общ базов клас - CCommonDialog. В таблицата е даден списък на класовете
Клас |
Предназначение |
CColorDialog |
Избиране на цвят |
CFileDialog |
Избиране на файл |
CFindReplaceDialog |
Търсене и заместване на символни низове |
CPageSetupDialog |
Въвеждане параметри на страницата |
CFontDialog |
Избиране на шрифт |
CPrintDialog |
Настройване на принтера и отпечатване |
Диалозите събират информация от потребителя но друго не правят.
Директна употреба на класа
CFileDialogСледващия сорс отваря файл който е избран от диалога:
CFileDialog dlg (TRUE, "bmp", "*.bmp");
if (dlg.DoModal() == IDOK) {
CFile file;
VERIFY (file.Open (dlg.GetPathName(), CFile::modeRead));
}
Първия параметър на конструктора (
TRUE) указва, че този обект е “File Open” диалог. Разширението на файла по подразбиране е bmp, a *.bmp се появява в текстовото поле за име на файл. Функцията CFileDialog::GetPathName връща обект от клас CString съдържащ пълния път на файла.Примерът EX07B
Тука ще се прави диалог за изтриване. Към диалог се добавя бутон
Delete All Matching Files.1. Създай проекта Е
X07BНатисни бутона Chance и промени имената на SpecFileDlg.h и SpecFileDlg.cpp
4. Промени реда на файла
SpecFileDialog.hclass CSpecialFileDialog : public CDialog
на
class CSpecialFileDialog : public CFileDialog
Добави следните
public член променливи:public:
BOOL m_bDeleteAll;
CString m_strFilename;
Редактирай декларацията на конструктора
CSpecialFileDialog(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL);
5. Замени
CDialog c CFileDialog в SpecFileDlg.h. От менюто Edit избери Replace и направи глобална замяна на това име6.
Редактирай конструктора CSpecialFileDialog в SpecFileDlg.cpp. Деструкторът на производния клас трябва да извика конструктора на базовия клас и да инициализира полето m_bDeleteAll. Освен това трябва да задава стойности на някой от полетата на член-променливата m_ofn на базовия клас CFileDialog, която е инсталация на Win32 структурата OPENFILENAME. Полетата Flags и lpTeplateName контролират свързването към твоя шаблон IDD_FILESPECIAL, а полето lpstrTitle променя заглавието на диалоговия прозорец. Редактирай конструктора по следния начин:CSpecialFileDialog::CSpecialFileDialog(BOOL bOpenFileDialog,
LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags,
LPCTSTR lpszFilter, CWnd* pParentWnd)
: CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
dwFlags, lpszFilter, pParentWnd)
{
//{{AFX_DATA_INIT(CSpecialFileDialog)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
m_ofn.Flags |= OFN_ENABLETEMPLATE;
m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILESPECIAL);
m_ofn.lpstrTitle = "Delete File";
m_bDeleteAll = FALSE;
}
7. Асоциирай съобщението WM_INITDIALOG в класа CSpecialDilaog. Член функцията OnInitDialog трябва да променя заглавието на бутона Open от Open на Delete. Идентификаторът на дъщерния прозорец е IDOK.
BOOL CSpecialFileDialog::OnInitDialog()
{
BOOL bRet = CFileDialog::OnInitDialog();
if (bRet == TRUE) {
GetParent()->GetDlgItem(IDOK)->SetWindowText("Delete");
}
return bRet;
}
8.
Асоциирай бутона IDC_DELETE (Delete All Matching Files) в класа CSpecialDialog. Член-функцията OnDelete задава флага m_bDeleteAll и след това принуждава главния диалог да се затвори, все едно че е бил натиснат бутона Cancel. Програмата клиент (изгледа) взима върнатата от DoModal стойност IDCANCEL и прочита флага, за да разбере дали трябва да изтрие всички файлове:void CSpecialFileDialog::OnDelete()
{
m_bDeleteAll = TRUE;
// 0x480 is the child window ID of the File Name edit control
// (as determined by SPYXX)
GetParent()->GetDlgItem(0x480)->GetWindowText(m_strFilename);
GetParent()->SendMessage(WM_COMMAND, IDCANCEL);
}
9.
Добави към OnDrawpDC->TextOut (0, 0, Press the left mouse button here”);
10.
Добави функцията за обработка на съобщенията OnLButtonDown:void CEx07bView::OnLButtonDown(UINT nFlags, CPoint point)
{
CSpecialFileDialog dlgFile(TRUE, NULL, "*.obj");
CString strMessage;
int nModal = dlgFile.DoModal();
if ((nModal == IDCANCEL) && (dlgFile.m_bDeleteAll)) {
strMessage.Format(
"Are you sure you want to delete all %s files?",
dlgFile.m_strFilename);
if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
HANDLE h;
WIN32_FIND_DATA fData;
while((h = ::FindFirstFile(dlgFile.m_strFilename, &fData))
!= (HANDLE) 0xFFFFFFFF) { // no MFC equivalent
if (::DeleteFile(fData.cFileName) == FALSE) {
strMessage.Format("Unable to delete file %s\n",
fData.cFileName);
AfxMessageBox(strMessage);
break;
}
}
}
}
else if (nModal == IDOK) {
CString strSingleFilename = dlgFile.GetPathName();
strMessage.Format(
"Are you sure you want to delete %s?", strSingleFilename);
if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
CFile::Remove(strSingleFilename);
}
}
}
Пак ти напомням, че диалозите с общо предназначение само събират данни. В този пример изгледа има върната стойност от
DoModal (IDOK или IDCANCE) и стойността на public променливата m_bDelete и може да извика най-различни функции на CFileDialog, като GetPathName. Ако DoModal върне IDCANCEL и флагът е TRUE. функцията прави необходимите системни позовавания за изтриването на всички файлове, отговарящи на посочените от потребителя критерии. Ако DoModal върне IDOK, функцията може да опилва член-функциите на MFC класа CFile, за да изтрие отделен файл.Използването на глобалната функция
AfxMessageBox е удобен начин да изведеш на екрана един прост диалог, който показва някакъв текст и изисква от потребителя да отговори с натискането на бутон Yes или No. Справочникът Microsoft Foundation Class Reference обяснява всички варианти и опции на прозорците със съобщения (message boxes).Трябва да включиш израза
#include "ex07bView.h"
#include "SpecFileDlg.h"
Active X контроли
Могат да се пишат на С++ с помощта на
MFC библиотека или на библиотеката с Active X шаблони (ATL). Те се базират на Component Object Model (COM). Ще се разгледат подробно в глави 23 и 24.Active X контролите имат характеристики и методи. Характеристиките (наричани и свойства) имат символни имена, на които са съпоставени целочислени индекси. На всяка характеристика (
property), създателят на контролата преписва име и тип. Има и бит мап контроли. Програмата-клиент може да зададе стойност на дадено свойство (характеристика) на един Active X контрол, като укаже неговия целочислен индекс и съответната стойност. Програмата може да извлече вярната стойност като укаже неговия индекс.Методите са като функциите. Един метод има символично име, набор от параметри и връщана стойност. Извикването на метода става чрез извикването на функцията на класа, представяш контрола.
Active X
контрола не извиква съобщения а предизвиква събития (events). Едно събитие може да има произволна последователност от параметри. Събитията не връщат резултати.За съхраняване на контроли се използва
DLL който обикновено има разширение OCX.Инсталиране на
Active X контролиПърво копирай DLL файла. Регистрирай контролата в Windows Registry. Контролът може да се инсталира сам, когато една програма клиент извика специална “експортна” функция. След инсталацията трябва да регистрираш контрола във всеки проект, който го използва. За целта избери от менюто Project Add To Project, Components Add Controls. Маркирай Registered ActiveX Controls. Появява се списъка на всички контроли.
Контролът Calendar
Името му е
MSCal.OCX.
Характеристики |
Методи |
Събития |
||
Back Color |
About Box |
After Update |
||
Day |
Nex Day |
Before Update |
||
Day Font |
Next Month |
Click |
||
Day Length |
Nex Week |
Dbl Click |
||
First Day |
Previous Day |
Key Down |
||
Grid Cell Effect |
Previous Month |
Key Press |
||
Grid Lines Color |
Refresh |
|||
Month Lenght |
||||
Show DateSelection |
||||
Show Days |
||||
Show Title |
||||
Show Vertical Grid |
||||
Title Font |
||||
Title Font Color |
||||
Value |
||||
Values Null |
|
|||
Year |
Всяко едно свойство, събитие и метод имат целочислен идентификатор.
Програмиране на контейнери на
Active X контролиПо ефективно е да се използва член-променлива. Ако трябва да се прочете характеристиката
Value се пише:COleVariant var = m_calenadar.GetValue();
За да се установи датата на календара на 5-ти ръчно първо се добавя член-променливата
m_sCalDay към диалоговия клас, която ще съответства на характеристиката Day (тип short integer) на контролата. После добави:DDX_OCShort (pDX, ID_CALENDAR1, 0x11, m_sCalDay);
Третия параметър е целочисления индекс на свойството
Day (неговия DispID dispatch идентификатор) може да се открие във функциите GetDay и SetDay. Ето как се създава и показва диалога:CMyDialog dlg;
dlg.m_sCalDay = 5;
dlg.DoModal();
За да може контролът да се презарежда при всяко следващо активиране с цел ускоряване работата на програмата се използва:
AfxOleLOckControl (m_calendar.GetClsid());
Примерът
EX08A1. Провери дали контролът
Calendar е регистриран. Ако контрола не се появи в страницата Registered Active X Controls от галерията с компоненти и контроли на Developer Studio, копирай файловете MSCal.och, MSCal.hlp и MSCal.cnt в системната директория на твоя диск и регистрирай контролата, като стартираш програмата RECCOMP.2. Създай проекта EX08A.
Следвай до сегашните указания. Увери се, че в стъпка 3 е избрана опцията Active X Controls. To си е така по подразбиране.3. Инсталирай контролата
Calendar в проекта. От меню Project избери Add To Project -> Components And Controls ->Registered Active X Controls и след това избери Calendar Control 8.0. Генерират се два класа.4. Редактирай класа на контролата
Calendar за да обработва съобщения за помощна информация. Добави към Calendar.cpp следния код://///////////////////////////////////////////////////////////////////////////
// CCalendar
IMPLEMENT_DYNCREATE(CCalendar, CWnd)
BEGIN_MESSAGE_MAP(CCalendar, CWnd)
ON_WM_HELPINFO()
END_MESSAGE_MAP()
Добави и функцията:
BOOL CCalendar::OnHelpInfo(HELPINFO* pHelpInfo)
{
// Edit the following string for your system
::WinHelp(GetSafeHwnd(), "c:\\winnt\\system32\\mscal.hlp",
HELP_FINDER, 0);
return FALSE;
}
Декларирай в
Calendar.h асоциирането на съобщения:protected:
afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
DECLARE_MESSAGE_MAP()
Функцията
OnHelpInfo се извиква при натискане на F1, когато фокусът се намира върху контролата Calendar. Трябва ръчно да се добави код за асоцииране на съобщения защото Class Wizard не модифицира генерираните класове за Active X.5.
Създай нов диалог. Смени му идентификатора с IDD_ACTIVEXDIALOG и заглавието с Active X Dialog. Включи характеристиката Context Help от страницата More Styles. Бутоните ги остави по подразбиране. Добави другите контроли.
Контрол |
ID (идентификатор) |
Calendar control |
IDC_CALENDAR1 |
Select Date |
IDC_SELECTDATE |
Edit control |
IDC_DAY |
Edit control |
IDC_MONTH |
Edit control |
IDC_YEAR |
Next Week |
IDC_NEXTWEEK |
6. Създай класа
CActiveXDialog. Задай името на класа CActiveXDialog. Останалото е по подразбиране. Добави по-долу изброените функции от страницата Message Maps на Class Wizard.
ID обект |
Съобщение |
Член-функция |
||
CActiveXDialog |
WM_INITDIALOG |
OnInitDialog(виртуална) |
||
IDC_CALENDAR1 |
NewMonth (събитие) |
OnNewMonthCalendar1 |
||
IDC_SELECTDATE |
BN_CLICKED |
OnSelectDate |
||
IDC_NEXTWEEK |
BN_CLICKED |
OnNextWeek |
||
IDOK |
BN_CLICKED |
OnOK ( виртуална) |
7. Добави към класа
public:
COleVariant m_varValue;
unsigned long m_BackColor;
CActiveXDialog::CActiveXDialog(CWnd* pParent /*=NULL*/)
: CDialog(CActiveXDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CActiveXDialog)
m_sCalendar = 0;
m_sMonth = 0;
m_sYear = 0;
//}}AFX_DATA_INIT
m_BackColor = 0x800000F;
}
void CActiveXDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CActiveXDialog)
DDX_Control(pDX, IDC_CALENDAR1, m_calendar);
DDX_Text(pDX, IDC_DAY, m_sCalendar);
DDX_Text(pDX, IDC_MONTH, m_sMonth);
DDX_Text(pDX, IDC_YEAR, m_sYear);
//}}AFX_DATA_MAP
DDX_OCColor(pDX, IDC_CALENDAR1, DISPID_BACKCOLOR, m_BackColor);
}
BOOL CActiveXDialog::OnInitDialog()
{
CDialog::OnInitDialog();
m_calendar.SetValue (m_varValue);// no DDX for VARIANTs
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CActiveXDialog::OnSelectdate()
{
// TODO: Add your control notification handler code here
CDataExchange dx(this, TRUE);
DDX_Text(&dx, IDC_DAY, m_sDay);
DDX_Text(&dx, IDC_MONTH, m_sMonth);
DDX_Text(&dx, IDC_YEAR, m_sYear);
m_calendar.SetDay(m_sDay);
m_calendar.SetMonth(m_sMonth);
m_calendar.SetYear(m_sYear);
}
void CActiveXDialog::OnNextWeek()
{
// TODO: Add your control notification handler code here
m_calendar.NextWeek();
}
Функцията
OnSelectDate се извиква при натискане на бутона Select Date. Тя взема стойностите на деня, месеца и годината от трите текстови полета и ги прехвърля в характеристиките на контрола. Class Wizard не добавя DDX код за характеристиката BackColor, затова се прави на ръка. DDX не съществува за типовете VARIANT затова се добавя код към функциите OnInitDialog и OnOK за задаване и извличане датата чрез характеристиката Value на контролата.9. Свържи диалога към изгледа. Асоциирай съобщението
WM_LBUTTONDOWN и редактирай функцията:void CActiveXDialog::OnLButtonDown(UINT nFlags, CPoint point)
{
CActiveXDialog dlg;
dlg.m_BackColor = RGB(255, 251, 240); // light yellow
COleDateTime today = COleDateTime::GetCurrentTime();
dlg.m_varValue = COleDateTime(today.GetYear(), today.GetMonth(),
today.GetDay(), 0, 0, 0);
if (dlg.DoModal() == IDOK) {
COleDateTime date(dlg.m_varValue);
AfxMessageBox(date.Format("%B %d, %Y"));
}
CDialog::OnLButtonDown(nFlags, point);
}
Цвета на фона се установява в светложълто и датата – в днешна дата, показва се модалния диалог и докладва датата, върната от контрола
Calendar. Напиши и:#include "ActiveXDialog.h"
Напиши във функцията
OnDraw:Active X
контролите в HTMLфайловеСъщия този контрол може да се използва и за
HTML файлове при условие, че потребителя разполага с инсталиран контрол Calendar.<OBJECT CLASSID = "clsid:8E27C92B-1264-101C-8A2F-040224009C02" WIDTH = 300 HEIGHT = 200 HSPACE = 5 ID=calendar>
<PARAM NAME = "DAY" VALUE = 11>
<PARAM NAME = "MONTH" VALUE = 10>
<PARAM NAME = "YEAR" VALUE = 1999>
</object>
Ако <
PARAM NAME...> не се зададе се приема текуща стойност. Атрибутът CLASSID служи за идентифициране на контрола Calendar в Registry. Един браузър може да свали (download) даден Active X контрол.Създаване на
Active X контроли по време на изпълнениеСледвай тези стъпки:
1. Вкарай компонента във проект.
Class Wizard ще създаде файловете за клас-обвивка;2. Добави вградена член-променлива, която да е обект от класа-обвивка на
Active X контрола към диалогов клас;3. Избери
View -> Resource Symbols и добави нова ID константа за новия контрол;4. Ако родителския прозорец е диалог , използвай
Class Wizard , за асоцииране на съобщението WM_INITDIALOG, като предефинираш CDialog::OnInitDialog. За други прозорци асоциирай WM_CREATE. Новата функция трябва да извиква член-функцията Create на класа на вградения контрол. Това показва контролата в диалога. Контролът се унищожава при унищожаването на родителския прозорец;5. В класа на родителският прозорец ръчно добави необходимите прототипи и функции за обработка на събития за новия контрол. Добави макроси за асоциирането на събития (
event sink map macros).Примерът EX08B – Active X контролът Web Browser
Разделяне браузъра на два екрана. В левия се показва машината за търсене, а в десния намерената страница.
Прозореца на този изглед съдържа
Web Browser контролата, която се оразмерява в цялата клиентска област. При избиране на тема в контрола за търсене (в дясно) програмата прихваща командата и я пренасочва към контрола за показване на намерена страница (левия прозорец).2. Създай проекта. Настройките са същите;
3. Инсталирай контролата
Web Browser. Project -> Add To Project -> Components. Посочи Registered Active X Control и избери Microsoft Web Browser Control;4.
Добави две член-променливи към клас CEX08BView от клас CWebBrowser;private:
CWebBrowser m_search;
CWebBrowser m_target;
5. За двата контрола добави
ID константи за дъщерни прозорци. От View -> Resource Symbols добави символите ID_BROWSER_SEARCH и ID_BROWSER_TARGET;6.
Добави член-променлива статичен масив от символи за URL (Uniform Resource Locator) на Alta-Vista. Към EX08BVew.hprivate:
static const char s_engineAltavista[];
Ако трябва добави и:
#include "webbrowser.h"
Добави в
CEX08B.cpp извън тялото на която и да е функция:const char CEX08bView::s_engineAltavista[] =
"http://altavista.digital.com/";
7. Асоциирай съобщенията
WM_CREATE и VM_SIZE. Редактирай функциите:int CEX08BView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
DWORD dwStyle = WS_VISIBLE | WS_CHILD;
if (m_search.Create(NULL, dwStyle, CRect(0, 0, 100, 100),
this, ID_BROWSER_SEARCH) == 0) {
AfxMessageBox("Unable to create search control!\n");
return -1;
}
m_search.Navigate(s_engineAltavista, NULL, NULL, NULL, NULL);
if (m_target.Create(NULL, dwStyle, CRect(0, 0, 100, 100),
this, ID_BROWSER_TARGET) == 0) {
AfxMessageBox("Unable to create target control!\n");
return -1;
}
m_target.GoHome(); // as defined in IE3 options
return 0;
}
void CEX08BView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
CRect rectClient;
GetClientRect(rectClient);
CRect rectBrowse(rectClient);
rectBrowse.right = rectClient.right / 2;
CRect rectSearch(rectClient);
rectSearch.left = rectClient.right / 2;
m_target.SetWidth(rectBrowse.right - rectBrowse.left);
m_target.SetHeight(rectBrowse.bottom - rectBrowse.top);
m_target.UpdateWindow();
m_search.SetLeft(rectSearch.left);
m_search.SetWidth(rectSearch.right - rectSearch.left);
m_search.SetHeight(rectSearch.bottom - rectSearch.top);
m_search.UpdateWindow();
}
Функцията
OnCreate създава два браузър прозорци в прозореца на изгледа. Десния прозорец показва главната страница на Alta Vista, а левия зададената от вас стартова “home” страница. Функцията OnSize се извиква при промяна размера на прозореца, гарантира пълното припокриване на прозореца. Член-функциите SetWidth и SetHeight на SWebBrowser задават свойствата Width и Height на съответния браузър.8. Добави макроси за асоцииране събитията във файловете на
CEX08BVew. Добавят се ръчно в EX08BVew.hprotected:
afx_msg void OnBeforeNavigateExplorer1(LPCTSTR URL,
long Flags, LPCTSTR TargetFrameName,
VARIANT FAR* PostData, LPCTSTR Headers, BOOL FAR* Cancel);
afx_msg void OnTitleChangeExplorer2(LPCTSTR Text);
DECLARE_EVENTSINK_MAP()
9. След това добави в
EX08BView.cppBEGIN_EVENTSINK_MAP(CEX08BView, CView)
ON_EVENT(CEX08BView, ID_BROWSER_SEARCH, 100, OnBeforeNavigateExplorer1, VTS_BSTR VTS_I4 VTS_BSTR VTS_PVARIANT VTS_BSTR VTS_PBOOL)
ON_EVENT(CEX08BView, ID_BROWSER_TARGET, 113, OnTitleChangeExplorer2, VTS_BSTR)
END_EVENTSINK_MAP()
9.
Добави две функции за обработка на събитияvoid CEX08BView::OnBeforeNavigateExplorer1(LPCTSTR URL,
long Flags, LPCTSTR TargetFrameName,
VARIANT FAR* PostData, LPCTSTR Headers, BOOL FAR* Cancel)
{
TRACE("CEx08bView::OnBeforeNavigateExplorer1 -- URL = %s\n", URL);
if (!strnicmp(URL, s_engineAltavista, strlen(s_engineAltavista))) {
return;
}
m_target.Navigate(URL, NULL, NULL, PostData, NULL);
*Cancel = TRUE;
}
void CEX08BView::OnTitleChangeExplorer2(LPCTSTR Text)
{
// Careful! Event could fire before we're ready.
CWnd* pWnd = AfxGetApp()->m_pMainWnd;
if (pWnd != NULL) {
if (::IsWindow(pWnd->m_hWnd)) {
pWnd->SetWindowText(Text);
}
}
}
Функцията
OnBeforeNavigateExplorer1, се извиква при щракване върху някоя връзка в страницата за търсене. Функцията сравнява този URL, върху който е щракнато (параметър URL от символен низ), с URL-то на машината за търсене. В противен случай навигацията се отменя и се извиква метода Navigate за прозореца на ненамерената страница. Функцията OnTitleChangeExplorer2 обновява заглавието на прозореца на EX08B, за да съвпада със заглавието на намерената страница.VirtualAlloc –
ангажирана и резервирана паметКогато паметта се резервира се заделя съседна виртуална адресна област. Ако искаш твоята програма да извика единичен блок от 5 МВ (известен като регион) но няма да го използваш целия се вика
VirtualAlloc с параметър MEM_RESERVE и параметър 5МВ. Може да окажеш и начален адрес на паметта.Когато се нуждаеш от по-сериозно количество памет извикваш
VirtualAlloc с параметър MEM_COMMIT . Сега началния и крайния адрес на региона се закръгляват до 4 килобайтони граници и се заделят съответните страници за swap-файла. Блокът може да бъде read-only или read-write.За освобождаване на резервирана памет се използва
VirtualFree.Примерът
EX10A//
Това нещо се ебава, като стартирам проекта от диска на книгата върви, а като го създавам .....Този пример показва на екрана ресурсно базиран битмап в един скролиран изглед с режим на съпоставяне
MM_LOENGLISH. Програмата използва логиката на StretsBlt.1. Създай проекта
2. Импортирай битмапа
Cold Weave. Избери Insert -> Resource. Избери файла Weave.bmp от директорията Windows. Сложи идентификатор IDB_GOLDWEAVE и запиши промените.3. Добави към класа
CEX10AVew.private:
CDC* m_pdcMemory;
CBitmap m_pBitmap;
CSize m_SizeSource, m_sizeDest;
Обекти от класа
CSize са размерностите на източника (битмапа) и целта (монитора);4. Редактирай функциите:
CEX10AView::CEX10AView()
{
m_pdcMemory = new CDC;
m_pBitmap = new CBitmap;
}
CEX10AView::~CEX10AView()
{
// cleans up the memory device context and the bitmap
delete m_pdcMemory; // deselects bitmap
delete m_pBitmap;
}
Функцията
OnDraw извиква два пъти StretchBit – веднъж използвайки специалните изчислителни размери и втори път съпоставяйки всеки бит с квадрат с размери 0.1 на 0.1 инча:void CEX10AView::OnDraw(CDC* pDC)
{
pDC->SetStretchBltMode(COLORONCOLOR);
pDC->StretchBlt(20, -20, m_sizeDest.cx, -m_sizeDest.cy,
m_pdcMemory, 0, 0,
m_sizeSource.cx, m_sizeSource.cy, SRCCOPY);
pDC->StretchBlt(350, -20, m_sizeSource.cx, -m_sizeSource.cy,
m_pdcMemory, 0, 0,
m_sizeSource.cx, m_sizeSource.cy, SRCCOPY);
}
Функцията
OnInitiallUPdate инициализира битмапа и контекста на устройството за паметта и изчислява изходните размерности, които съпоставят всеки бит с пиксел от екрана.void CEx10aView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(800, 1050); // 8-by-10.5 inches
CSize sizeLine = CSize(sizeTotal.cx / 100, sizeTotal.cy / 100);
SetScrollSizes(MM_LOENGLISH, sizeTotal, sizeTotal, sizeLine);
BITMAP bm; // Windows BITMAP data structure; see Win32 help
if (m_pdcMemory->GetSafeHdc() == NULL) {
CClientDC dc(this);
OnPrepareDC(&dc); // necessary
m_pBitmap->LoadBitmap(IDB_GOLDWEAVE);
m_pdcMemory->CreateCompatibleDC(&dc);
m_pdcMemory->SelectObject(m_pBitmap);
m_pBitmap->GetObject(sizeof(bm), &bm);
m_sizeSource.cx = bm.bmWidth;
m_sizeSource.cy = bm.bmHeight;
m_sizeDest = m_sizeSource;
dc.DPtoLP(&m_sizeDest);
}
}
В следващия пример ще се демонстрира плавно движение върху екрана. Първо се чертае битмапа а после се хвърля на екрана.
Пример
EX10B1.
Създай проекта;2. Добави във
CEx10bView функции за обработка на следните съобщения;WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_PAINT
3.
Добави променливите;private:
const CSize m_sizeEllipse;
CPoint m_pointTopLeft;
BOOL m_bCaptured;
CSize m_sizeOffset;
CDC* m_pdcMemory;
CBitmap* m_pBitmap;
4.
Напиши в конструктора и деструктораCEx10bView::CEx10bView() : m_sizeEllipse(100, -100),
m_pointTopLeft(10, -10),
m_sizeOffset(0, 0)
{
m_bCaptured = FALSE;
m_pdcMemory = new CDC;
m_pBitmap = new CBitmap;
}
CEx10bView::~CEx10bView()
{
delete m_pBitmap; // already deselected
delete m_pdcMemory;
}
5. Добави и код за функцията
OnInitialUpdate; Тука се създават съответните обекти съвместими с контекста на екрана dc. Трябва да се зададе режима за съпоставяне на контекста на устройството.void CEx10bView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(800, 1050); // 8-by-10.5 inches
CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2);
CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50);
SetScrollSizes(MM_LOENGLISH, sizeTotal, sizePage, sizeLine);
// creates the memory device context and the bitmap
if (m_pdcMemory->GetSafeHdc() == NULL) {
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectMax(0, 0, sizeTotal.cx, -sizeTotal.cy);
dc.LPtoDP(rectMax);
m_pdcMemory->CreateCompatibleDC(&dc);
// makes bitmap same size as display window
m_pBitmap->CreateCompatibleBitmap(&dc, rectMax.right,
rectMax.bottom);
m_pdcMemory->SetMapMode(MM_LOENGLISH);
}
}
И ми писна да го пиша щото пак
SetScrollSizes липсва. ЧАО.Примерът
EX10DПоставяне на битмапи върху бутони
1. Създай проекта: по стандартните правила но избери
Context-Sensitive Help щото тя копира някой битмап файлове в поддиректорията \hlp на проекта.2. Модифицирай диалоговия ресурс
ADD_ABOUTBOX; за икономия да не създаваш нов. Добави три бутона: IDC_BUTTON1, IDC_BUTTON2 и IDC_BUTTON3. Размера на бутона не е от значение щото той се коригира отИзбери характеристиката Owner Draw и за трите бутона.
3. Вмъкни три битмапа от поддиректорията
\hlp на проекта; От Insert -> Resource и натисни Import. Започни с EditCopy.bmp. Задай му заглавието “COPYU”. Кавичките са задължителни за да се идентифицира ресурса по име на по ID.Направи същото и за битмапите
EditCut и EditCopy.4.
Редактирай кода на класа CAboutDlg. Добави член променливитеprivate:
CBitmapButton m_editCopy;
CBitmapButton m_editCut;
CBitmapButton m_editPaste;
Асоциирай съобщението
WIM_INITDIALOG за класа CAboutDlg.BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
VERIFY(m_editCopy.AutoLoad(IDC_BUTTON1, this));
VERIFY(m_editCut.AutoLoad(IDC_BUTTON2, this));
VERIFY(m_editPaste.AutoLoad(IDC_BUTTON3, this));
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Функцията
AutoLoad свързва всеки бутон с двата съответстващи ресурса. Макросът VERIFY показва съобщение ако си кодирал правилно имената на битмапите.5. Добави и
void CEX10CView::OnDraw(CDC* pDC)
{
//CEX10CDoc* pDoc = GetDocument();
//ASSERT_VALID(pDoc);
pDC->TextOut (0, 0, "Chose About from the Help menu");
}
6.
Тествай примера и избери Help -> AboutПримерът ЕХ11А
Еднонишкова програма съдържаща изчислителен цикъл за натоварване на процесора.
1. Създай проекта;
2. Създай диалогов ресурс
IDD_COMPILE. Промени идентификатора на бутона ОК на IDC_START. Сложи контролата PROGRESS;3. Създай класа
CComputeDlg. Асоциира съобщението WM_TIMER. Асоциирай съобщенията BN_CLICKED за двата бутона;4. Добави член-променливи към класа
CComputeDlg.private:
int m_nTimer;
int m_nCount;
enum (nMaxCount = 1000);
5.
Добави код за инициализация на конструктора CComputeDlg. Във файла ComputeDlg.cpp добави следния ред в конструктора за да може да работи бутона Cancel, ако не е започнал изчислителния процес;m_nCount = 0;
Този ред трябва да е извън комнетара //
{{AFX_DATA_UNIT,6. Напиши кода на функцията
OnStart в ComputeDlg.cppvoid CComputeDlg::OnStart()
{
MSG message;
m_nTimer = SetTimer(1, 100, NULL); // 1/10 second
ASSERT(m_nTimer != 0);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
volatile int nTemp;
for (m_nCount = 0; m_nCount < nMaxCount; m_nCount++) {
for (nTemp = 0; nTemp < 10000; nTemp++) {
// uses up CPU cycles
}
if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
}
CDialog::OnOK();
}
PeekMessage
позволява на другите съобщения, да бъдат обработени. Извикването на EnableWindow (FALSE) забранява бутона Start по време на изчисляването;7. Напиши кода и на
OnTimervoid CComputeDlg::OnTimer(UINT nIDEvent)
{
CProgressCtrl* pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);
pBar->SetPos(m_nCount * 100 / nMaxCount);
}
8.
В последствие иvoid CComputeDlg::OnCancel()
{
TRACE("entering CComputeDlg::OnCancel\n");
if (m_nCount == 0) { // prior to Start button
CDialog::OnCancel();
}
else { // computation in progress
m_nCount = nMaxCount; // Force exit from OnStart
}
}
9.
Редактирай класа CEX11AViewvoid CEX11AView::OnDraw(CDC* pDC)
{
pDC->TextOut (0, 0, "Press the left mouse botn here");
}
Добави и
#include "ComputeDlg.h"
Обработка по време на бездействие (
On-Idle Processing)При многонишково програмиране приложната среда извиква функцията
OnIdle на клас CWinApp, която може да се предефинира за обработване на заден план. OnIdle се извиква от цикъла на приложната реда за обработка на съобщения . След като OnIdle завърши своето изпълнение, тя се извиква отново, докато опашката от съобщения не се изпразни. OnIdle на базовия клас обновява бутоните на панела с инструменти и индикаторите за статус и изчиства различни указатели към временни обекти. Функцията се изпълнява когато има съобщения.OnIdle
не се извиква ако потребителя работи в модален диалог или използва подменю. Ако се налага обработка на заден план за модален диалог и за менюта, трябва да се добави функция за обработка на съобщението WM_ENTERIDLE в класа на рамката.Многонишково програмиране
Windows
предлага два вида нишки – работни нишки (worker threads) и нишки за потребителски интерфейс (user interface threads).Една работна нишка за потребителски интерфейс има прозорци, а следователно и собствен цикъл за съобщения. Една работна нишка няма прозорци и не се налага обработването на съобщения.
Дори и еднонишковото приложение има една главна нишка (
main thread). В йерархията на MFC CWinApp в производен на CWinThread.Преди да стартираш една работна нишка трябва да напишеш глобална функция в главната програма на твоята нишка. Тази функция трябва да връща
UINT и трябва да приема единствена 32 битова стойност за параметър (декларирана като LPVOID)За да стартираш нишка с име на функция
CmputeThreadProc, твоята програма прави следното:CWinThread* PThread = AfxBeginThread (ComputeThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);
Кодът на нишката извършващ изчисления изгледа така:
UNIT ComputeThreadProc (LDVOID pParam)
{
//Do thread processing;
return 0;
}
Функцията
AfxBEginThread се връща незабавно. Връщаната стойност е указател към новосъздадения обект-нишка. Може да използваш този указател, за да прекъсваш и възстановяваш изпълнението на нишката. (SWinThread::SuspendThread и CWinThread::ResumeThread), но обекта нишка няма член-функция за спиране на нишката. Втория параметър е 32 битова стойност, която се подава на глобалната функция, третия е кода на приоритета на нишката. Windows разделя времето между всички нишки според техния приоритет. Ако главната нишка чака съобщение, работната нишка за изчисление може да работи.Спирам глава 11 дотука после може и да я продължа ако ми се наложи да се разправям с нишки де.
Класът
CEditViewБазира се на
Edit – контролата. Размерът на текста е до 64 КВ, и не може да се смесват шрифтове. Този обект има цялата функционалност на CEdit и CView. CEditView импелентира и асоциира функции за изрязване, копиране и вмъкване чрез клипборда, така че елементите на менюто Edit са разрешени.Класът
CRichEditViewБазира се на
Rich Edit контролата. Поддържа формат за смесени шрифтове и работи с големи количества текст. Проектиран е за употреба на класовете CRichEditDoc и CRichEditCntrItem, за създаване и завършване на ACtive X контейнерни приложения.Класът
CRichEditCtrlОбвива контролата
Rich Edit и може да се използва за създаване на сравнително сносни текстови редактори. Това е и идеята на следващия пример. Използваните функции са:
Create |
Създава прозореца на rich edit контрол. Извивка се от функцията на родителя за обработка на WM_CREATE |
SetWindowPos |
Задава размера и позицията на прозореца |
GetWindowText |
Извлича обикновен текст от котролата |
SetWindowText |
Поставя обикновен текст в контролата |
GetModify |
Извлича флаг, който е TRUE, ако текста е бил променен |
SetModify |
Установява флага в TRUE или FALSE |
GetSel |
Извлича фкал, който показва дали потребителя е избрал текст |
SetDefaultCharFormat |
Задава подразбиращи се форматни характеристики на контрола |
SetSelectionCharFormat |
Задава форматните характеристики на избрания текст. |
Примерът ЕХ12А
Показва пренасочването на команди от менюто и комнти от клавиатурните акселератор към документа и към изгледи. Командите от менюто
Transfer, прехвърлят данни между изгледа и документа. Елементът Store Data In Document е сив (забранен), когато изгледа не е бил модифициран след последното прехвърляне на данни. Елементът Clear Document от менюто Edit е забранен, когато документа е празен.1. Генерирай проекта
;2.
Редактирай главното меню. Избери Work space -> Resource View и редактирай меню-ресурса IDR_MAINFRAME, като поставиш разделител и меню елемент Clear Document къмДобави и менюто
Transfer и дефинирай елементите му:
Меню |
Заглавие |
ID команда |
||
Edit |
Clear &Document |
ID_CLEAR_ALL |
||
Transfer |
&Get Data From Docuemt\tF2 |
ID_TANSFER_GETDATA |
||
Transfer |
&Store Data In Document\tF3 |
ID_TRANSFER_STOREDATA |
\t е символа за табулация.
3.
Използвай редактора на ресурси, за добавяне на клавиатурни акселератори. Отвори таблицата с акселератори на IDR_MAINFRAME и след това използвай клавиш Insert, за да добавиш следните елементи:
ID на акселератор |
Клавиш |
|
ID_TRANSFER_GETDATA |
VK_F2 |
|
ID_TRANSFER_STOREDATA |
VK_F3 |
Изключи модификаторите
Ctrl, Alt и Shift.4.
Добави функциите към изгледния клас CEx12aView
ID на обект |
Съобщение |
Член-функция |
||
ID_TRANSFER_GETDATA |
COMMAND |
OnTransferGetData |
||
ID_TRANSFER_STOREDATA |
COMMAND |
OnTransferStoreData |
||
ID_TRANSFER_STOREDATA |
UPDATA_COMMAND_UI |
OnUpdateTransferStoreDAta |
5.
Добави към документа. Избери класа CEx12aDoc и добави:
ID на обект |
Съобщение |
Член-функция |
||
ID_EDIT_CLEAR_ALL |
COMMAND |
OnEditClearDocument |
||
ID_EDIT_CLEAR_ALL |
UPDATE_COMMAND_UL |
OnUpdateEditClearDocument |
6. Добави към класа
CEx12aDocpublic:
CString m_strText;
7. Редактирай функциите
BOOL CEx12aDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_strText = "Hello (from CEx12aDoc::OnNewDocument)";
return TRUE;
}
Функцията се извиква при създаване на нов документ и когато се избере
File -> New. В примера се поставя текст в стринговата член променлива.void CEx12aDoc::OnEditClearDocument()
{
m_strText.Empty();
}
Тази функция изчиства променливата. Избира се от
Edit -> Clear Document.void CEx12aDoc::OnUpdateEditClearDocument(CCmdUI* pCmdUI)
{
pCmdUI->Enable (m_strText.IsEmpty())
}
Тази функция забранява менюто
Edit -> Clear Document ако стринга е празен.8. Прибави променливата в класа
CEx12aViewpublic:
CRichEditCtrl m_rich;
9. Асоциирай съобщенията
WM_CREATE и WM_SIZE в класа CEx12aView//Създаване на
rich edit контролатаint CEx12aView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CRect rect(0, 0, 0, 0);
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_rich.Create(ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN |
WS_CHILD | WS_VISIBLE | WS_VSCROLL, rect, this, 1);
// CHARFORMAT cf;
// Format(cf);
// m_rich.SetDefaultCharFormat(cf);
return 0;
}
Windows
изпраща съобщението WM_SIZE към изгледа веднага що първоначалния размер на изгледа бъде определен, и всекъ път когато се променя размера на рамката. Тази функция настройва размера на rich edit контролата.void CEx12aView::OnSize(UINT nType, int cx, int cy)
{
CRect rect;
CView::OnSize(nType, cx, cy);
GetClientRect(rect);
m_rich.SetWindowPos(&wndTop, 0, 0, rect.right - rect.left,
rect.bottom - rect.top, SWP_SHOWWINDOW);
}
10. Редактирай функциите за обработка на командни съобщения от менюта от класа
Ex12aView.Тази функция взема тексдта от
m_strText и го поставя в rich edit контролата.void CEx12aView::OnTransferGetData()
{
CEx12aDoc* pDoc = GetDocument();
m_rich.SetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE);
}
Тази функция копира текста от
rich edit контролата в m_strTExt и сваля фйлага за промянаvoid CEx12aView::OnTransferStoreData()
{
CEx12aDoc* pDoc = GetDocument();
m_rich.GetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE);
}
Диалози за характеристики
(Property Sheets)Позволява наблъскването на голямо количество информация в малък диалог. Страниците се избират с мишката.
Widows предлага контрол за страници (Tab control), който може да поставиш в даден диалог, но може да се поставят и в Tab контролата. Отделните диалози се наричат страници за характеристики.1. Създай диалогови шаблони с еднакви размери. Заглавията (captions) се появяват върху етикетите;
2. Създай клас за всеки шаблон;
3. Създай клас производен на
CProperySheet;4. Постави по една член-променлива за всеки клас;
5. В конструктора на
sheet-класа извикай член-функцията AddPage за всяка страница и задай адиреса на вградения page-обект;6. Създай обект в п;риложението на производния клас
CprotertySheet и извикай DoModal. Задай заглавие при извикване на конструктора. По-късно може да го промениш чрез CProperty::SetTitle;7.
Погрижи се за поведението на бутона Apply.Преправяне на примера
EX12AЩе добавиш един диалог за характеристики, който ще променя шрифта. Може да се използва стандартна
MFC функция CFontDialog но сега ще се прави ръчно.1. Редактирай главното меню на приложенито. Добави меню
Format с две подменюта;
Заглавие |
ID команда |
&Default |
ID_FORMAT_DEFAULT |
&Selection |
ID_FORMAT_SELECTION |
2.
Добави следните функции към класа CEx12aView:
ID_FORMAT_DEFAULT |
COMMAND |
OnFormatDefault |
||
ID_FORMAT_SELECTION |
COMMAND |
OnFormatSelection |
||
ID_FORMAT_SELECT |
UPDATE_COMMAND_UI |
OnUpdateFormatSelection |
3. Добави четири диалогови шаблона за страниците на диалога
За контролите и диалозите използвай идентификаторите от следващата таблица. Задай характеристиките
Auto Buddy и Set Buddy Integer за spin button и характеристиката Group за радиобутоните IDC_FONT и IDC_COLOR. Установи минимална стойност на IDC_FONTSIZE на 8 а максимална на 24.Създай класовете
CPage1, CPage2, CPage3 и CPage4. Избери за базов клас CPropertyPage. Натисни бутона Change от диалога New на Class Wizard, за да генерираш кода на тези класове във файловете Property.h и Property.cpp. Добави следващите член променливи:
Диалог |
ID |
Тип |
Променлива |
IDD_PAGE1 |
IDC_FONT |
int |
m_nFont |
IDD_PAGE2 |
IDC_BOLD |
BOOL |
m_bBold |
IDD_PAGE2 |
IDC_ITALIC |
BOOL |
m_bItalic |
IDD_PAGE2 |
IDC_UNDERLINE |
BOOL |
m_bUnderline |
IDD_PAGE3 |
IDC_COLOR |
int |
m_nColor |
IDD_PAGE4 |
IDC_FONTSIE |
int |
m_nFontSie |
IDD_PAGE4 |
IDC_SPIN1 |
В
CPage4 добави функцията OnInitDialog4. Съдай клас наследник на
CProperySheet. Ибери името CFontSheet. Генерирай кода в Property.cpp. Добави към Property.h:#if !defined(AFX_PROPERTY_H__CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_)
#define AFX_PROPERTY_H__CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
// Property.h : header file
//
#define WM_USERAPPLY WM_USER + 5
extern CView* g_pView;
///////////////////////////////////////////////////////////////////////
// CPage1 dialog
class CPage1 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage1)
// Construction
public:
CPage1();
~CPage1();
// Dialog Data
//{{AFX_DATA(CPage1)
enum { IDD = IDD_PAGE1 };
int m_nFont;
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPage1)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
virtual BOOL OnApply();
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CPage1)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////
// CPage2 dialog
class CPage2 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage2)
// Construction
public:
CPage2();
~CPage2();
// Dialog Data
//{{AFX_DATA(CPage2)
enum { IDD = IDD_PAGE2 };
BOOL m_bBold;
BOOL m_bItalic;
BOOL m_bUnderline;
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPage2)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CPage2)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////
// CPage3 dialog
class CPage3 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage3)
// Construction
public:
CPage3();
~CPage3();
// Dialog Data
//{{AFX_DATA(CPage3)
enum { IDD = IDD_PAGE3 };
int m_nColor;
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPage3)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CPage3)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////
// CPage4 dialog
class CPage4 : public CPropertyPage
{
DECLARE_DYNCREATE(CPage4)
// Construction
public:
CPage4();
~CPage4();
// Dialog Data
//{{AFX_DATA(CPage4)
enum { IDD = IDD_PAGE4 };
int m_nFontSize;
//}}AFX_DATA
// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CPage4)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CPage4)
virtual BOOL OnInitDialog();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////
// CFontSheet
class CFontSheet : public CPropertySheet
{
DECLARE_DYNAMIC(CFontSheet)
public:
CPage1 m_page1;
CPage2 m_page2;
CPage3 m_page3;
CPage4 m_page4;
// Construction
public:
CFontSheet(UINT nIDCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL,
UINT iSelectPage = 0);
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFontSheet)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CFontSheet();
// Generated message map functions
protected:
//{{AFX_MSG(CFontSheet)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_PROPERTY_H__CD702F99_7495_11D0_8FDC_00C04FC2A0C2__INCLUDED_)
// Property.cpp : implementation file
//
#include "stdafx.h"
#include "ex12a.h"
#include "Property.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CView* g_pView;
///////////////////////////////////////////////////////////////////////
// CPage1 property page
IMPLEMENT_DYNCREATE(CPage1, CPropertyPage)
CPage1::CPage1() : CPropertyPage(CPage1::IDD)
{
//{{AFX_DATA_INIT(CPage1)
m_nFont = -1;
//}}AFX_DATA_INIT
}
CPage1::~CPage1()
{
}
BOOL CPage1::OnApply()
{
TRACE("CPage1::OnApply\n");
g_pView->SendMessage(WM_USERAPPLY);
return TRUE;
}
BOOL CPage1::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}
void CPage1::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage1::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPage1)
DDX_Radio(pDX, IDC_FONT, m_nFont);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CPage1, CPropertyPage)
//{{AFX_MSG_MAP(CPage1)
// NOTE: the ClassWizard will add message map macros here
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CPage1 message handlers
///////////////////////////////////////////////////////////////////////
// CPage2 property page
IMPLEMENT_DYNCREATE(CPage2, CPropertyPage)
CPage2::CPage2() : CPropertyPage(CPage2::IDD)
{
//{{AFX_DATA_INIT(CPage2)
m_bBold = FALSE;
m_bItalic = FALSE;
m_bUnderline = FALSE;
//}}AFX_DATA_INIT
}
CPage2::~CPage2()
{
}
BOOL CPage2::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}
void CPage2::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage2::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPage2)
DDX_Check(pDX, IDC_BOLD, m_bBold);
DDX_Check(pDX, IDC_ITALIC, m_bItalic);
DDX_Check(pDX, IDC_UNDERLINE, m_bUnderline);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CPage2, CPropertyPage)
//{{AFX_MSG_MAP(CPage2)
// NOTE: the ClassWizard will add message map macros here
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CPage2 message handlers
///////////////////////////////////////////////////////////////////////
// CPage3 property page
IMPLEMENT_DYNCREATE(CPage3, CPropertyPage)
CPage3::CPage3() : CPropertyPage(CPage3::IDD)
{
//{{AFX_DATA_INIT(CPage3)
m_nColor = -1;
//}}AFX_DATA_INIT
}
CPage3::~CPage3()
{
}
BOOL CPage3::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}
void CPage3::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage3::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPage3)
DDX_Radio(pDX, IDC_COLOR, m_nColor);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CPage3, CPropertyPage)
//{{AFX_MSG_MAP(CPage3)
// NOTE: the ClassWizard will add message map macros here
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CPage3 message handlers
///////////////////////////////////////////////////////////////////////
// CPage4 property page
IMPLEMENT_DYNCREATE(CPage4, CPropertyPage)
CPage4::CPage4() : CPropertyPage(CPage4::IDD)
{
//{{AFX_DATA_INIT(CPage4)
m_nFontSize = 0;
//}}AFX_DATA_INIT
}
CPage4::~CPage4()
{
}
BOOL CPage4::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE);
return CPropertyPage::OnCommand(wParam, lParam);
}
void CPage4::DoDataExchange(CDataExchange* pDX)
{
TRACE("Entering CPage4::DoDataExchange -- %d\n",
pDX->m_bSaveAndValidate);
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CPage4)
DDX_Text(pDX, IDC_FONTSIZE, m_nFontSize);
DDV_MinMaxInt(pDX, m_nFontSize, 8, 24);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CPage4, CPropertyPage)
//{{AFX_MSG_MAP(CPage4)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CPage4 message handlers
BOOL CPage4::OnInitDialog()
{
CPropertyPage::OnInitDialog();
((CSpinButtonCtrl*) GetDlgItem(IDC_SPIN1))->SetRange(8, 24);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
///////////////////////////////////////////////////////////////////////
// CFontSheet
IMPLEMENT_DYNAMIC(CFontSheet, CPropertySheet)
CFontSheet::CFontSheet(UINT nIDCaption, CWnd* pParentWnd,
UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
}
CFontSheet::CFontSheet(LPCTSTR pszCaption, CWnd* pParentWnd,
UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
AddPage(&m_page4);
}
CFontSheet::~CFontSheet()
{
}
BEGIN_MESSAGE_MAP(CFontSheet, CPropertySheet)
//{{AFX_MSG_MAP(CFontSheet)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CFontSheet message handlers
5. Добави към
CEx12aprivate:
BOOL m_bDefault; // TRUE default format, FALSE selection
CFontSheet m_sh;
void Format(CHARFORMAT& cf);
6.
Добави към CEx12aView.cpp съобщението://}}AFX_MSG_MAP
ON_MESSAGE(WM_USERAPPLY, OnUserApply)
Добави към
OnCreate:CHARFORMAT cf;
Format(cf);
m_rich.SetDefaultCharFormat(cf);
Редактирай конструктора на изгледа да задава стойности по подразбиране за член-променливите на диалога за характеристиките:
CEx12aView::CEx12aView() : m_sh("")
{
m_sh.m_page1.m_nFont = 0;
m_sh.m_page2.m_bBold = FALSE;
m_sh.m_page2.m_bItalic = FALSE;
m_sh.m_page2.m_bUnderline = FALSE;
m_sh.m_page3.m_nColor = 0;
m_sh.m_page4.m_nFontSize = 12;
g_pView = this;
m_bDefault = TRUE;
}
void CEx12aView::OnFormatDefault()
{
m_sh.SetTitle("Default Format");
m_bDefault = TRUE;
m_sh.DoModal();
}
void CEx12aView::OnFormatSelection()
{
m_sh.SetTitle("Selection Format");
m_bDefault = FALSE;
m_sh.DoModal();
}
void CEx12aView::OnUpdateFormatSelection(CCmdUI* pCmdUI)
{
long nStart, nEnd;
m_rich.GetSel(nStart, nEnd);
pCmdUI->Enable(nStart != nEnd);
}
LRESULT CEx12aView::OnUserApply(WPARAM wParam, LPARAM lParam)
{
TRACE("CEx12aView::OnUserApply -- wParam = %x\n", wParam);
CHARFORMAT cf;
Format(cf);
if (m_bDefault) {
m_rich.SetDefaultCharFormat(cf);
}
else {
m_rich.SetSelectionCharFormat(cf);
}
return 0;
}
void CEx12aView::Format(CHARFORMAT& cf)
{
cf.cbSize = sizeof(CHARFORMAT);
cf.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE |
CFM_ITALIC | CFM_SIZE | CFM_UNDERLINE;
cf.dwEffects = (m_sh.m_page2.m_bBold ? CFE_BOLD : 0) |
(m_sh.m_page2.m_bItalic ? CFE_ITALIC : 0) |
(m_sh.m_page2.m_bUnderline ? CFE_UNDERLINE : 0);
cf.yHeight = m_sh.m_page4.m_nFontSize * 20;
switch(m_sh.m_page3.m_nColor) {
case -1:
case 0:
cf.crTextColor = RGB(0, 0, 0);
break;
case 1:
cf.crTextColor = RGB(255, 0, 0);
break;
case 2:
cf.crTextColor = RGB(0, 255, 0);
break;
}
switch(m_sh.m_page1.m_nFont) {
case -1:
case 0:
strcpy(cf.szFaceName, "Times New Roman");
break;
case 1:
strcpy(cf.szFaceName, "Arial");
break;
case 2:
strcpy(cf.szFaceName, "Courier New");
break;
}
cf.bCharSet = 0;
cf.bPitchAndFamily = 0;
}
Класът
CMenuРесурсът на менюто се прикачва към прозорец в рамка, когато се извикват функциите
Create или LoadFrame на прозореца. Член функцията GetMenu на CWnd връща временен указател от тип CMenu.IRD_MAINFRAME
винаги идентифицира първоначалнот меню в описанието на ресурсите. Ако искаш второ меню използвай редактора на менюта. След това програмата създава обект от клас CMenu, използвай функцията CMenu::LoadMenu за да заредиш менюто от ресурса, и извикай CWnd””SetMenu, за да прикачиш менюто към прозорец на рамката. После извикат функцията Detach, за да отделиш манипулатора HMENU на обекта, така че менюто не се разрушава, когато CMenu обекта вече е видим (out of scope).Може да се използва ресурс за дефиниране на меню, и след това по време на изпълнение на програмата да модифицираш елементите на менюто. Ако е необходимо може да се изгради цяло меню по време на изпълнение на програмата. И в двата случая може да се използват функциите на
CMenu: MoifyMenu, Insertmenu и DeleteMenu. Всяка функция действа на елемент от менюто чрез неговото ID иил чрез индекс за относителна позиция.Може да се използва функцията
GetSubMenu, за да получиш указател към някое подменю, съдържащо се в главния CMenu обект.Примера
EX13A – модифициране на панела с инструментиЩе се модифицират трите бутона
Cut, Copy и Paste. Ще конструираме и меню Draw със съответните три меню-елемента.Опциите на менюто и панела с инструменти принуждават потребителя да чертае кръг и квадрат. Ако се начерта кръг менюто
Circle и бутона на панела се забраняват и обратно. .В менюто
Draw, елемента Pattern “получава” знак за отметка, когато е активиран моделът на запълване. Съответния бутон от панела с инструментите е бутон за обозначаване (check box button), който е в състояние “долу”, когато моделът на запълване е включен и “горе” – когато е изключен.
Меню елемент |
Функция |
Circle |
Чертае кръг |
Square |
Черта квадрат |
Pattern |
Превключва модела на запълване |
1. Генерирай проекта;
2. Редактирай главното меню.
Създай менюто Draw и подменюта:
Circle |
ID_DRAW_CIRCLE |
Square |
ID_DRAW_SQUARE |
Pattern |
ID_DRAW_PATTERN |
3. С редактора на ресурси промени панела с инструменти
. Редактирай ресурса IDR_MANFRAME. Създай битмап. Замени бутоните Cut, Copy и Paste. За трите бутона използва идентификаторите: ID_DRAW_CIRCLE, ID_DRAW_SQUARE и ID_DRAW_PATTERN4. Добави във
CEx13aView функциите за обработка на съобщенията:
ID на обект |
Съобщение |
Член-функция |
ID_DRAW_CIRCLE |
COMMAND |
OnDrawCircle |
ID_DRAW_CIRCLE |
UPDATE_COMMAND_UI |
OnUpdateDrawCircle |
ID_DRAW_PATTERN |
COMMAND |
OnDrawPattern |
ID_DRAW_PATTERN |
UPDATE_COMMAND_UI |
OnUpdateDrawPattern |
ID_DRAW_SQUARE1 |
COMMAND |
OnDrawSquare |
ID_DRAW_SQUARE1 |
UPDATE_COMMAND_UI |
OnUpdateDrawPattern |
4. Добави в
CEX13aView и три член променливи.private:
BOOL m_bCircle;
CRect m_rect;
BOOL m_bPattern;
6. Редактирай и:
CEx13aView::CEx13aView() : m_rect(0, 0, 100, 100)
{
m_bCircle = TRUE;
m_bPattern = FALSE;
}
Функцията
OnDraw чертае елипса или правоъгълник, в зависимост от стойността на флага m_bCicle. Четака е бяла или на с наклонение линии, в зависимост от стойността на m_bPattern.void CEx13aView::OnDraw(CDC* pDC)
{
CBrush brush(HS_BDIAGONAL, 0L); // brush with diagonal pattern
if (m_bPattern) {
pDC->SelectObject(&brush);
}
else {
pDC->SelectStockObject(WHITE_BRUSH);
}
if (m_bCircle) {
pDC->Ellipse(m_rect);
}
else {
pDC->Rectangle(m_rect);
}
pDC->SelectStockObject(WHITE_BRUSH); // Deselects brush
// if selected
}
Функциите
OnDrawSquare и OnDrawCircle преместват правоъгълника надолу и надясно и го обявяват за невалиден. По този начин принуждават OnDraw да го чертае. Чертаят се диагонално и каскадно кръгове и елипси.void CEx13aView::OnDraw(CDC* pDC)
{
CBrush brush(HS_BDIAGONAL, 0L); // brush with diagonal pattern
if (m_bPattern) {
pDC->SelectObject(&brush);
}
else {
pDC->SelectStockObject(WHITE_BRUSH);
}
if (m_bCircle) {
pDC->Ellipse(m_rect);
}
else {
pDC->Rectangle(m_rect);
}
pDC->SelectStockObject(WHITE_BRUSH); // Deselects brush
// if selected
}
Следващите функции забраняват и разрешават бутоните
Circle и Square. Двата елемента не могат да бъдат едновременно разрешени.void CEx13aView::OnUpdateDrawCircle(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_bCircle);
}
Превключване състоянието на флага
m_bPatter.void CEx13aView::OnDrawPattern()
{
m_bPattern ^= 1;
}
Следващата функция обновява бутона
Pattern и съответния меню елемент според състоянието на флага m_bPattern.void CEx13aView::OnUpdateDrawPattern(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_bPattern);
}
7
. Стартирай приложениетоДефиниция на лентата за статус
Статични масив indicators, във файла MainFrm.cpp дефинира поелтата за текстове в стату-лентата на приложението. Константата ID_SEPARATOR идентифицира поле за съобщение. Другите константи са идентификатори на стрингови ресурси, които идентифицират полетата за индикация.
Static = {
ID_SEPARATOR, //Message panel (subscript (0)
ID_INDICATOR_CAPS, //CAPS
ID_INDICATOR_NUM, //NUM
ID_INDICATOR_SCRL //SCRL
}
Член-функцията
CStatusBar::SetIndicator, извиквана в производния клас за рамката в приложението, конфигурира лентата според съдържанието на масива indicators.За да зададеш стойност на
Message panel (първото поле от ляво на дясно) трябва да получиш достъп до лентата и след това да извикваш CStatusBar::SetPanelText с нулев базиран индекс. (Масив с номер 0).Следващия фрагмент е част от член-функция на изгледен клас. Трябва да стигнеш до обекта-приложение и след това да се върнеш "надолу" към прозореца на главната рамка.
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWind;
CStatusbar* pStatus = &pFrame->m_wndStatusBar;
pStatus->SetPaneText (0, "Message line for first pane");
Дължината на първото поле е четвърт от прозореца.
Индикатори за състояние
Свързани са с един ресурсен стринг. Идикаторът
Caps Lock се обработва в класа на рамката чрез макрос за асоцииране на съобщения и функцията за тяхната обработка. Функцията Enable включва индикатора, ако клавиатурата е в режим Caps LockON_UPDATE_COMMAND_UI (ID_INDICATORS_CAPS, OnUpdateKeyCapsLock)
void CMainFrame::OnUpdatecKeyCapsLock(CCmdUI *pCmdUI) { pCmdUI->Enable(); }
Пример ЕХ13В
Заместване стандартната приложна лента за статус с ноя, която има следните полета:
индекс |
ID |
Тип |
Описание |
0 |
ID_SEPARATOR(0) |
Съобщение |
Х - координата |
1 |
ID_SEPARATOR(1) |
Съобщение |
У - координата |
2 |
ID_INDICATOR_LEFT |
индикатор |
ляв бутон |
3 |
ID_INDICATOR_RIGHT |
индикатор |
десен бутон |
1. Създай проекта;
2. Редактирай стринговата таблица със стринговия редактор
. От Resource View избери String Table. Щракни два път върху последния ред за да добавиш:
ID на стринг |
Заглавие на стринга |
ID_INDICATOR_LEFT |
LEFT |
ID_INDICATOR_RIGHT |
RIGHT |
3. Добави от
View -> Recource Symbols. ID_MY_STATUS _BAR за новата статус лента и приеми стойността по подразбиране.4. Добави в клас
CMainFrame:
ID_VIEW_STATUS_BAR |
COMMAND |
OnViewStatusBar |
ID_VIEW_STATUS_BAR |
UPDATE_COMMAND_UL |
OnUpdateViewStatusBar |
5. Добави следните прототипи на функции към
MainFrm.h//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnUpdateLeft(CCmdUI* pCmdUI);
afx_msg void OnUpdateRight(CCmdUI* pCmdUI);
6.
Редактирай файла MainFrm. Замени оригиналния масив indicators със следния код:static UINT indicators[] =
{
ID_SEPARATOR, // first message line pane
ID_SEPARATOR, // second message line pane
ID_INDICATOR_LEFT,
ID_INDICATOR_RIGHT,
};
Редактирай и
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this,
WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, ID_MY_STATUS_BAR) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// 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;
}
Функцията
Create използва новия идентификатор за статус лентата ID_MY_STATUS_BAR.Добави в картата за асоцииране на съобщения на клас
CMainFrameBEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_UPDATE_COMMAND_UI(ID_INDICATOR_LEFT, OnUpdateLeft)
ON_UPDATE_COMMAND_UI(ID_INDICATOR_RIGHT, OnUpdateRight)
ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
ON_UPDATE_COMMAND_UI(ID_VIEW_STATUS_BAR, OnUpdateViewStatusBar)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CMainFrame::OnViewStatusBar()
{
m_wndStatusBar.ShowWindow((m_wndStatusBar.GetStyle() &
WS_VISIBLE) == 0);
RecalcLayout();
}
void CMainFrame::OnUpdateViewStatusBar(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck((m_wndStatusBar.GetStyle() & WS_VISIBLE) != 0);
}
7.
Напиши иvoid CEx13bView::OnDraw(CDC* pDC)
{
pDC->TextOut (0, 0, "Гледай статус бара и натискай мишката");
}
8. Добави функция за обработка на съобщението
WM_MOUSEMOVE.void CEx13bView::OnMouseMove(UINT nFlags, CPoint point)
{
CString str;
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
CStatusBar* pStatus = &pFrame->m_wndStatusBar;
if (pStatus) {
str.Format("x = %d", point.x);
pStatus->SetPaneText(0, str);
str.Format("y = %d", point.y);
pStatus->SetPaneText(1, str);
}
}
Добави в
Ex13bView.cpp#include "MainFrm.h"
Ако желаеш първото поле да има релефна рамка (
beveled border) като останалите, напиши в CMainFrame::OnCreate след извикването на функцията Create на статус-лентата:m_wndStatusBar.SetPaneInfo (0, 0, 0, 50);
m_wndStatusBar.SetPaneInfo (1, 0, SBPS_STRETCH, 50);
Тези изрази променят ширината на първите две полета (правят я различна от подразбиращата се 1/4 от ширината на екрана) и правят второто поле с (индекс 1) да бъде разтеглящо се.
Използване на класа
CStringНай-якото му качество е динамично заделяне на памет
CString strFirstName ("koko");
CString strLastName ("kiki");
CString strTruth = strFirstName + " " + strLastName;
strTruth += " mara ba";
ASSERT (strTruth == "koko kiki mara ba");
ASSERT (strTruth.Left(5) == strFirstName;
ASSERT (strtruth[2]) = 'o'; //subscrip operator
Оператора
const char*() конвертира един CString обект в указател към char. Такъв указател има глобалната функция AfxMessageBox.int AFXAPI AfxMessageBox (LPCTSTR ipcsText, UINT nType = MB_OK
Извикването и става с:
char szMessageText[] = "Mara ba";
AfxMessageBox (szMessageText);
Генериране на форматен стринг
int nError = 23;
CString strMessageText;
strMessageText.Format ("Error number %d, nError);
AfxMessageBox (strMessageText);
Функцията GetBuffer заключва буфера с оказаната дължина и връща char*. Функцията ReleaseBuffer прави променливата отново динамична.
CString strTest ("test);
strncpy (strTest.GetBuffer(5), "T", 1);
strTest.ReleaseBuffer();
ASSERT (strTest == "Test"):
Първия параметър на функцията
strncpy трябва да е дефиниран като const char*.pDC->TextOut (0, 0, "
Мра ба", 5); // броене на символитеПозицията на максимизиран прозорец
Функцията CWnd::GetWindowRect извлича последните координати на един прозорец. Ако прозореца е максимизиран, връща координатите на екрана. Немаксимизираните координати на екрана се пазят във функцията CWnd::GetWindowPlacement.
Функцията
SetWindowPlacement задава максимализирано или минимизирано състояние на прозореца. За да се определи горния ляв ъгъл, трябва да се вземе в предвид размера на рамката. Този размер се получава от GetSystemMetrics.Registry
и състоянието на панелите за контролиЗа записване и зареждане в
registry се използват две член функции на CWnd - SaveBarState и LoadBarState. Тези функции обработват размера и позицията на лентата за статус и на скачените панели с инструменти. Те обаче не обработват позицията на плаващите панели с инструменти.Статични член променливи
Класът
CPersistentFrame съхранява своите имена на ключове за Registry в свои членове, които са масиви от тип static const char. Какви са другите възможности за съхранение? Стринговете трябва да са дефинирани в самия клас така, че това отпада. Глобалните променливи не се препоръчват щото обезсмисляли инкапсулацията. Статичните CString обекти също щото пък те трябвало да се копират в хийпа при стартиране на програмата.Обикновените член-променливи могат да се използват, ако са само за четене
(read only data section). Статичните член-променливи са видими само в техния клас.Подразбиращ се правоъгълник на прозорец
Един
CRect обект е специален ако се конструира така:CRect rect(CW_USEDEFAULT, CW_USEDAFAULT, 0, 0);
Когато
Windows създава нов прозорец с този специален правоъгълник, той позиционира прозореца в каскаден модел, като горния ляв ъгъл е по-надолу и по-вдясно от този на предходния създаден прозорец.Статичната член-променлива
rectDefault на клас CFrameWnd се създава чрез използване на CW_USEDEFAULT по този начин, така че тя съдържа специалния правоъгълник. Класът CPresistentFrame декларира свой собствен правоъгълник на прозорец - rectDefault - с фиксирани размери и позиция, като статична член променлива, като по този начин скрива променливата от базовия клас.Примерът
EX14AДемонстрира употребата на клас за прозорец на постоянна рамка
- CPresistentFrame. Създай двата файла Persist.h и Persist.cpp. Трябва да вмъкнеш новия клас за рамка в кода на SDI приложението, генериран от App Wizard.// Persist.h
#ifndef _INSIDE_VISUAL_CPP_PERSISTENT_FRAME
#define _INSIDE_VISUAL_CPP_PERSISTENT_FRAME
class CPersistentFrame : public CFrameWnd
{ // remembers where it was on the desktop
DECLARE_DYNAMIC(CPersistentFrame)
private:
static const CRect s_rectDefault;
static const char s_profileHeading[];
static const char s_profileRect[];
static const char s_profileIcon[];
static const char s_profileMax[];
static const char s_profileTool[];
static const char s_profileStatus[];
BOOL m_bFirstTime;
protected: // Create from serialization only
CPersistentFrame();
~CPersistentFrame();
//{{AFX_VIRTUAL(CPersistentFrame)
public:
virtual void ActivateFrame(int nCmdShow = -1);
protected:
//}}AFX_VIRTUAL
//{{AFX_MSG(CPersistentFrame)
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#endif // _INSIDE_VISUAL_CPP_PERSISTENT_FRAME
// Persist.cpp Persistent frame class for SDI apps
#include "stdafx.h"
#include "persist.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////////////////////////////////
// CPersistentFrame
const CRect CPersistentFrame::s_rectDefault(10, 10,
500, 400); // static
const char CPersistentFrame::s_profileHeading[] = "Window size";
const char CPersistentFrame::s_profileRect[] = "Rect";
const char CPersistentFrame::s_profileIcon[] = "icon";
const char CPersistentFrame::s_profileMax[] = "max";
const char CPersistentFrame::s_profileTool[] = "tool";
const char CPersistentFrame::s_profileStatus[] = "status";
IMPLEMENT_DYNAMIC(CPersistentFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CPersistentFrame, CFrameWnd)
//{{AFX_MSG_MAP(CPersistentFrame)
ON_WM_DESTROY()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////
CPersistentFrame::CPersistentFrame()
{
m_bFirstTime = TRUE;
}
///////////////////////////////////////////////////////////////
CPersistentFrame::~CPersistentFrame()
{
}
///////////////////////////////////////////////////////////////
void CPersistentFrame::OnDestroy()
{
CString strText;
BOOL bIconic, bMaximized;
WINDOWPLACEMENT wndpl;
wndpl.length = sizeof(WINDOWPLACEMENT);
// gets current window position and
// iconized/maximized status
BOOL bRet = GetWindowPlacement(&wndpl);
if (wndpl.showCmd == SW_SHOWNORMAL) {
bIconic = FALSE;
bMaximized = FALSE;
}
else if (wndpl.showCmd == SW_SHOWMAXIMIZED) {
bIconic = FALSE;
bMaximized = TRUE;
}
else if (wndpl.showCmd == SW_SHOWMINIMIZED) {
bIconic = TRUE;
if (wndpl.flags) {
bMaximized = TRUE;
}
else {
bMaximized = FALSE;
}
}
strText.Format("%04d %04d %04d %04d",
wndpl.rcNormalPosition.left,
wndpl.rcNormalPosition.top,
wndpl.rcNormalPosition.right,
wndpl.rcNormalPosition.bottom);
AfxGetApp()->WriteProfileString(s_profileHeading,
s_profileRect, strText);
AfxGetApp()->WriteProfileInt(s_profileHeading,
s_profileIcon, bIconic);
AfxGetApp()->WriteProfileInt(s_profileHeading,
s_profileMax, bMaximized);
SaveBarState(AfxGetApp()->m_pszProfileName);
CFrameWnd::OnDestroy();
}
///////////////////////////////////////////////////////////////
void CPersistentFrame::ActivateFrame(int nCmdShow)
{
CString strText;
BOOL bIconic, bMaximized;
UINT flags;
WINDOWPLACEMENT wndpl;
CRect rect;
if (m_bFirstTime) {
m_bFirstTime = FALSE;
strText = AfxGetApp()->GetProfileString(s_profileHeading,
s_profileRect);
if (!strText.IsEmpty()) {
rect.left = atoi((const char*) strText);
rect.top = atoi((const char*) strText + 5);
rect.right = atoi((const char*) strText + 10);
rect.bottom = atoi((const char*) strText + 15);
}
else {
rect = s_rectDefault;
}
bIconic = AfxGetApp()->GetProfileInt(s_profileHeading,
s_profileIcon, 0);
bMaximized = AfxGetApp()->GetProfileInt(s_profileHeading,
s_profileMax, 0);
if (bIconic) {
nCmdShow = SW_SHOWMINNOACTIVE;
if (bMaximized) {
flags = WPF_RESTORETOMAXIMIZED;
}
else {
flags = WPF_SETMINPOSITION;
}
}
else {
if (bMaximized) {
nCmdShow = SW_SHOWMAXIMIZED;
flags = WPF_RESTORETOMAXIMIZED;
}
else {
nCmdShow = SW_NORMAL;
flags = WPF_SETMINPOSITION;
}
}
wndpl.length = sizeof(WINDOWPLACEMENT);
wndpl.showCmd = nCmdShow;
wndpl.flags = flags;
wndpl.ptMinPosition = CPoint(0, 0);
wndpl.ptMaxPosition =
CPoint(-::GetSystemMetrics(SM_CXBORDER),
-::GetSystemMetrics(SM_CYBORDER));
wndpl.rcNormalPosition = rect;
LoadBarState(AfxGetApp()->m_pszProfileName);
// sets window's position and minimized/maximized status
BOOL bRet = SetWindowPlacement(&wndpl);
}
CFrameWnd::ActivateFrame(nCmdShow);
}
1. Създай проекта
2. Модифицирай
MainFrm.h. Трябва да смениш базовия клас за CMainFrame. За целта промени реда:class CMainFrame public CFrameWnd
#include "persist.h"
class CMainFrame : public CPersistentFrame
3. MOdificiraj MainFrm.cpp
. Направи глобално заместгване на CFrameWnd с CPersistenFrame;4. Модифицираи
Ex14a.cppНамери реда:
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
и го замести с
SetRegistryKey("Inside Visual C++");
5.
Добави файловете persist към проекта. След като са създадени във директорията тези файлове трябва да се добавят с Project -> Add To Project -> Files.6.
Включи класа CPersistentFrame. Изтрий файла на Class Wizard - Ех14а.clw. Извикай Class Wizard след това. Ако те попита компютъра дали искаш да изградиш пак файла кажи да. // е да ама мене не ме пита //.7. Стартирай проекта. Промени размера и местоположението на прозореца и пак стартирай проекта.
8. Запази класа
CPersistentFrame като компонент на галерията (Callery). В прозореца Class Viewм щракни с десния бутон вхрху CPresistentFrame и избери Add to Gallery. От меню Project -> Add to Project ->Combonents And Controls. Ще видиш папка ех14а. Смени и името на Persistent Frame. В тази папка се намира файла Persistent Frame.ogx.9. Изследвай
Windows Registry. Стартирай regedit.exe от \windows. Върви на ключа HKEY_CURRENT_USER\Software_inside Visual C++\ex14a и погледни стойностите.Примерът ЕХ15А
1. Създай проекта като промениш:
3. Добави диалога
IDD_E15A_FORM. С App Wizard прибави следните контроли:
Контрол |
Идентификатор |
Name edit control |
IDC_NAME |
Grade edit control |
IDC_GRADE |
Enter pushbutton |
IDC_ENTER |
4. Добави в
CEx15aView функция за обработка на съобщенията (message handlers). Добави функции за обработка на следните съобщения
IDC_ENTER |
BN_CLICKED |
OnEnter |
ID_EDIT_CLEAR_ALL |
COMMAND |
OnEditClearAll |
ID_EDIT_CLEAR_ALL |
UPDATE_COMMAND_UL |
OnUpdateEditClearAll |
5.
Добави в Class CEx15aView променливите:
ID_GRADE |
m_nGrade |
Value |
int |
|||
ID_NAME |
m_strName |
Value |
CString |
За
m_nGrage въведи минимална стойност 0 и максимална 100.6. Предифинирай виртуалната функция
OnInitialUpdate в класа на изгледа7. Добави прототип за спомагателната функция UpdateControlsFromDoc
в класа CEx15View.private:
void UpdateControlsFromDoc();
8.
Коригирай функциитеvoid CEx15aView::OnInitialUpdate()
{
//called in start up
UpdateControlsFromDoc();
}
Функцията
OnEnter прехвърля данни от Edit-контролите към член-променливите на класа и след това към документа:void CEx15aView::OnEnter()
{
CEx15aDoc* pDoc = GetDocument();
UpdateData(TRUE);
pDoc->m_student.m_nGrade = m_nGrade;
pDoc->m_student.m_strName = m_strName;
}
Функцията за обновяване на команди забранява меню-елемента, ако обекта от клас
CStudent на документа е празен.:void CEx15aView::OnEditClearAll()
{
GetDocument()->m_student = CStudent(); // "blank" student object
UpdateControlsFromDoc();
}
9.
Добави файловете student.cpp и student.h от Project -> Add Project -> Files (препиши ги от края на примера ако ги нямаш).10. Добави член-променливите от клас
CStudent в клас CEx15aDoc. Направи го с Class Wizard. Директиравата include ще се дефинира автоматично.11.
Редактирай и:CEx15aDoc::CEx15aDoc() : m_student("default value", 0)
{
TRACE("Document object constructed\n");
}
CEx15aDoc::~CEx15aDoc()
{
#ifdef _DEBUG
Dump(afxDump);
#endif // _DEBUG
}
Ето ги и двата файла
student://studen.cpp
#include "stdafx.h"
#include "student.h"
IMPLEMENT_DYNAMIC(CStudent, CObject)
#ifdef _DEBUG
void CStudent::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << "m_strName = " << m_strName << "\nm_nGrade = " << m_nGrade;
}
#endif // _DEBUG
// student.h
#ifndef _INSIDE_VISUAL_CPP_STUDENT
#define _INSIDE_VISUAL_CPP_STUDENT
class CStudent : public CObject
{
DECLARE_DYNAMIC(CStudent)
public:
CString m_strName;
int m_nGrade;
CStudent()
{
m_nGrade = 0;
}
CStudent(const char* szName, int nGrade) : m_strName(szName)
{
m_nGrade = nGrade;
}
CStudent(const CStudent& s) : m_strName(s.m_strName)
{
// copy constructor
m_nGrade = s.m_nGrade;
}
const CStudent& operator =(const CStudent& s)
{
m_strName = s.m_strName;
m_nGrade = s.m_nGrade;
return *this;
}
BOOL operator ==(const CStudent& s) const
{
if ((m_strName == s.m_strName) && (m_nGrade == s.m_nGrade)) {
return TRUE;
}
else {
return FALSE;
}
}
BOOL operator !=(const CStudent& s) const
{
// Let’s make use of the operator we just defined!
return !(*this == s);
}
#ifdef _DEBUG
void Dump(CDumpContext& dc) const;
#endif // _DEBUG
};
#endif // _INSIDE_VISUAL_CPP_STUDENT
Класът-колекция
CObListПоддържа подредени списъци от указатели към обекти от класове, производни на
CObject. Най-лесно се използва за добавяне на елементи в опашката на списък. Следващата програма слага 5 елемента в един списък и след това ги извлича в същата последователност:#include <afx.h>
#include <afxcoll.h>
class CAction: public CObject
{
private:
int m_nTime;
public:
//Constructor stores integer time value
CAction (int nTime) {m_nTime = nTime;}
void PrintTime() {printf ("time = %d\n", m_nTime);} //{trace ("time = %d\n", m_nTime);}
}
main()
{
CAction* pAction;
CObList actionLIst; //action list constructed on stack
int i;
// inserts action objects in sequence {0, 1, 2, 3, 4}
for (i = 0; i < 5; i++) {
pAction = new CAction(i);
actionLIst.AddTail (pAction); // no cast necessary for pAction
}
//retrieves and removes action objects in sequence {0, 1, 2, 3, 4}
while (!actionLIst.IsEmpty()) {
pAction = (CAction*) actionLIst.RemoveHead(); //return value
pAction->PrintTime();
delete pAction;
}
return 0;
}
Първо се създава обекта
actionList от клас CObList. Функцията CObList::AddTail вмъква указатели към новосъздадените обекти от клас CAction. Не трябва да се преобразуват типовете за Pаction защото AddTail приема за параметър указател към CObject, a pAction е указател към клас, производен на CObject.В последствие указателите към
CAction обектите се премахват от списъка и обектите се изтриват. Тук се налага преобразуване на типовете за върнатата стойност на RemoveHead, защото RemoveHead връща указател към CObject, който е по-високо в йерархията на класовете, отколкото CAction.Функцията
GetNext връща указател към следващия елемент от списъка. Приема параметър POSITION, който е 32 битова променлива.Ето как се употребява
GetNext ако се използва списъка от предния пример:CAction* pAction;
POSITION pos = actionLIst.GetHeadPosition();
while (Pos != NULL) {
pAction = (CAction*) actionLIst.GetNext (pos);
PAction->PrintTime();
}
За шаблони с различни типове указатели се използва шаблонни класове. Един такъв е
CTypePtrList. За декларация на обект от клас CAction за указатели, се пише:CTypedPtrList<CObList.CAction*>m_actionLIst;
Първият параметър е базовия клас за колекцията, а втория е типът на параметрите и връщаните стойности.
Примерът ЕХ15В
Прави се диалогов шаблон както в предния пример. Бутона
Enter се заменя с бутон Clear: IDC_NAME, IDC_GRADE, IDC_CLEAR.Панела с инструменти е създаден чрез изтриване на бутоните
Cut, Copy и Paste. Слагат се шест нови картини. Командата Fip Vertical (от меню Image) се използва за дублиране на някой от картините.Менюто
Student може да съответства на бутоните но не е задължително.Менюто
Edit има един елемент Clear All
Заглавен файл |
Сорс файл |
Класове |
Описание |
|||
Ex15b.h |
Ex15b.cpp |
CEx15bApp |
Класът на приложението |
|||
MainFrm.h |
MainFrm.cpp |
CMainFrame |
SDI главна рамка |
|||
CAboutDlg |
Диалог About |
|||||
StuDoc.h |
CtuDoc.cpp |
CStudentDoc |
Studen документ |
|||
StuView.h |
StuView.cpp |
CStudentView |
Student формен изглед |
|||
StdAfx.h |
StdAfx.cpp |
списък на предварително компилирани заглавни файлове |
CStudent
- Това е кода от пример Ех15а, с изключение на следния ред добавен в Studen.h:typedef CtypedPtrListCObList.Student* >CStudenList;
Това се добавя в
StdAfx.h:#include (asftempl.h
Надолу обясненията са страшно тъпи и затова спирам до тука.
Четене и запис на документи
SDI (Single Document Interface)
MDI (Multiple Document Interface)
Процеса на записване и възстановяване на документа се нарича сериализация (
serializacion). Определени класове имат член-функция, наречена Serialize. Когато се извика Serialize за даден обект данните се записват на диска, или се прочитат от там.Сериализацията не е заместител на системата за управление на бази от данни (СУБД). Всички обекти, асоциирани с даден документ, последователно се записват или четат от един файл на диска. Няма възможност за получаване на достъп до отделен обект. За база с данни трябва да се използва
Microsoft Open Database Connectivity (ODBC) или Data Access Object (DAO).Как трябва Serialize да чете, записва или свързва към дисков файл?
В MFC библиотеката дисковите файлове са представени от обекти от клас CFile. Един обект от този клас инкапсулира манипулатора на двоичен файл, който може да се получи чрез Win32 функцията CreateFile. Това не е буферираният указател от тип FILE. Този манипулатор се използва от приложната среда за извикване на Win32 функциите ReadFile, WriteFile и SetFilePointer. Ако приложението не извършва директен вход-изход с диска, но вместо това разчита на процеса на сериализация, може да избегнеш директната употреба на CFile обект. Между функцията Serialize и обекта от клас CFile стои обект-архив от клас CArchive. Той буферира данните и поддържа флаг, който показва дали архивът записва или чете от диска. Само един активен архив е асоцииран с даден файл в определен момент. Твоята функция Serialize трябва да чете или записва данни от обекта архив. Приложната среда извиква функцията Serialize на документа по време на процеса File Open и File Save.
Как се прави класа сериализируем?
Трябва да е директен или индиректен наследник на класа CObject. Декларацията на класа трябва да съдържа позоваване на макроса DECLARE_SERIAL, а файла с имплементацията на класа позоваване на макроса IMPLEMENT_SERIAL.
Написване на функцията
SerializeПонеже
Serialize е виртуална функция, член на класа CObject, трябва да си сигурен, че типовете на връщаната стойност и на параметрите съвпадат с тези на декларацията на CObject:void CStudent::Serialize(CArchive& ar)
{
TRACE("Entering CStudent::Serialize\n");
if (ar.IsStoring()) {
ar << m_strName << m_nGrade;
}
else {
ar >> m_strName >> m_nGrade;
}
}
Параметъра
ar е псевдоним на CArchive, който идентифицира обекта-архив на приложението. Член-функцията CArchive::IsStoring ни казва дали архивът в момента се използва за съхраняване или за зареждане. Класът CArchive има предефинирани оператори за вход (<<) и изход (>>) за много от вградените типове на С++
BYTE |
8 бита без знак |
WORD |
16 бита без знак |
LONG |
32 бита със знак |
DWORD |
32 бита без знак |
float |
32 бита |
double |
64 бита, FREE стандарт |
int .. |
32 бита със знак |
char |
8 бита без знак |
unsigned |
32 бита без знак |
Операторите за вход за предефинирани за работа със стойности, а операторите за изход за предефинирани за работа с псевдоними. Понякога се налага преобразуване на типовете:
ar << (int) m_nType;
ar >> (nt&) m_nType;
Свързване на
File Open с кода за сериализацияПри генериране на едно приложение меню елемента File -> Open се асоциира с функцията CWinApp::OnFileOpen. При извикването и се изпълняват следните стъпки:
Ако желаеш документните класове да работят в
SDI приложение, изчиствай съдържанието на документа в член-функцията DeleteContents, отколкото в деструктора. В деструктора се изтриват елементите, чието съществуване трае докато трае "живота" на обектаПри генериране на приложение меню-елемента
File -> Save се асоциира с функцията OnFileSave на класа CDocument. В последствие се извиква функцията Serialize на твоя документ, с архивен обект, настроен за запис. File -> Save As се обработва по същия начин от функцията OnFileSaveAs на CDocument.Флагът променян на документа
Това се поддържа от променливата m_bModified на CDocument. Тази булева променлива има стойност TRUE, ако документа е променян. Достъпът до m_bModified става с функциите SetModifiedFlag и IsMOdified на CDocument. Програмиста трябва да използва функцията SetMOdifiedFlag за да устави флага в TRUE, когато документа се променя.
Примерът ЕХ16А -
SDI със сериализацияCEx16aApp
Генерирай проекта. В стъпка 4 щракни бутона
Advanced Options. Въведи разширението на името на файловете.Това гарантира, че рекурсния стринг на докуметния шаблон съдържа правилното разширение по подразбиране и че коректния код, касаещ
Explorer, е вмъкнват в функцията InitInstaance на твоя клас за приложение. Може да промениш някоя от другите рекурсивни събстрингове, ако желаеш.Тази функция забранява бутона
File -> Save когато документа е в немодифицирано състояние:BOOL CStudentDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
TRACE("Entering CStudentDoc::OnOpenDocument\n");
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
return TRUE;
}
Разрешаване на Drab and Drop
Трябва да извикваш функцията DragAcceptFiles за прозореца на главната рамка на приложението. Public променливата m_pMainWnd на обекта приложение сочи към обекта от клас CFrameWnd. Когато потребителя пусне мипката някъде в прозореца на рамката, последния получава съобщение WM_DROPFILES, което предизвиква позоваване на FrameWnd::OnDropFiles. Следващия ред в InitInstance, разрешава тегленето и пускането:
m_pMainWnd->DrabAcceptFiles();
В примера това става и с двукратно натискане на мишката.
Печатане
Функцията
CView::OnDrawКогато програмата печата, тя използва оект-контекст на устройство, което е от клас
CDC. Обекта се създава от приложната среда и се подава като параметър на функцията OnDraw. Ако принтера се използва за дублиране на екрана OnDraw може да изпълнява двойно предназначение. Ако показваш, OnPaint извиква OnDraw и контекстът на устройството е контекста на екрана. При печат OnDraw се извикват от виртуалната функция на CView - OnPrint, с устройствен контекст за принтера подаван като параметър.В режим на предпечатен преглед, параметъра на
OnDraw е указател към обект от клас CPriviewDC. Функциите OnPrint и OnDraw работят по един и същи начин независимо дали се печата или се гледа.Функцията
CView::OnPrintРежима на съпоставяне трябва да бъде зададен преди извикването на
OnPrint. Може да се предефинира OnPrint за разпечатване на елементи, които не се показват на екрана. Параметрите са:- Указател на контекст на устройство;
- Указател към обект за пеечатна инфирмация (
CPrintInfo), който включва размери на страница, номер на текущата страница и максималния номер на страница.OnPrint
може въобще да не извиква OnDraw, за да поддържаш логиката за печат, напълно различна от логиката за показване на екрана. OnPrint се извиква веднъж за отпечатване на цялата страница.Режима на съпоставяне се задава в
CView::OnPrepareDC. Тя се извиква от OnPaint преди позоваването на OnDraw. Втория параметър на OnPrepareDC е указател към структура от тип CPrintInfo. IsPrint е маса удобна ако използваш OnPrepareDC за задаване на различни режими на съпоставяне за екрана и принтера.Ако не знаеш колко страници трябва да се печатат,
OnPrepareDC може да намери края на документа и да нулира флага m_bContinuePrinting в структурата CPrintInfo. Когато този флаг е FALSE OnPrint няма да се извика отново и управлението ще бъде предадено в края на цикъла за печат.Начало и край на печата
Първо се извикват функциите
OnPreparePrinting и OnBeginPringing. Те се генерират ако е избрана опцията Printing And Print Preview при създаване на проекта. OnPreparePrinting се извиква преди показването на диалога Print. Ако знаеш номера на първата и последната страница извикай CPrintInfo::SetMinPage и CPrintInfo::SetMaxPage в OnPreparePrinting. Номерата на страниците които се подават на двете функции ще се появят в диалога Print.OnBeginPrinting
се извиква след излизане от диалога Print. Добре е тази функция да се предефинира за създаване на GDI обектиOnEndPringing
се извиква след като печата свърши. Предефинирай функцията да изтрие GDI обектите.Пример Ех18А -
wysiwyng печатПримера печата като външния вид на печатаното съвпада с външния вид на екрана. За това се използва режим на съпоставяне
MM_TWIPS.1.
Създай проекта - MDI приложение;2. Добави към класа
CPoemDoc:public:
CStringArray m_stringArray;
Данните на документа се съхраняват в масив от стрингове. Класът
CStringArray съдържа масив от обекти на клас CString. Не е нужно да се задава размер на масива.3. Добави към класа
CStringViewprivate:
CRect m_rectPrint;
4.
Редактирай трите функции на PoemDoc.cppBOOL CPoemDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_stringArray.SetSize(10);
m_stringArray[0] = "The pennycandystore beyond the El";
m_stringArray[1] = "is where I first";
m_stringArray[2] = " fell in love";
m_stringArray[3] = " with unreality";
m_stringArray[4] = "Jellybeans glowed in the semi-gloom";
m_stringArray[5] = "of that september afternoon";
m_stringArray[6] = "A cat upon the counter moved among";
m_stringArray[7] = " the licorice sticks";
m_stringArray[8] = " and tootsie rolls";
m_stringArray[9] = " and Oh Boy Gum";
return TRUE;
}
Приложната среда извиква виртуалната функция
DelteContents на документа когато го затваря.void CPoemDoc::DeleteContents()
{
// called before OnNewDocument and when document is closed
m_stringArray.RemoveAll();
}
Сериализацията не е важна в случая. Следващия пример демонстрира един лесен начин за това:
void CPoemDoc::Serialize(CArchive& ar)
{
m_stringArray.Serialize(ar);
}
5. Редактирай и:
void CStringView::OnInitialUpdate()
{
CLogScrollView::OnInitialUpdate();
CSize sizeTotal(m_rectPrint.Width(), -m_rectPrint.Height());
SetLogScrollSizes(sizeTotal);
}
Функцията
OnDraw чертае като на екрана, така и на принтера. Освен, чер показва редовете от текста на поемата с шрифт от тип roman с 10 точки, тя чертае рамка около областта за печат и груба размерна лийийка по горното и по лявто гранични полета. Функцията допуска режима на съпоставяне MM_TWIPS, в който 1 инч = 1440 единици.void CStringView::OnDraw(CDC* pDC)
{
int i, j, nHeight;
CString str;
CFont font;
TEXTMETRIC tm;
CPoemDoc* pDoc = GetDocument();
// Draw a border, slightly smaller to avoid truncation
pDC->Rectangle(m_rectPrint + CRect(0, 0, -20, 20));
// Draw horizontal and vertical rulers
j = m_rectPrint.Width() / 1440;
for (i = 0; i <= j; i++) {
str.Format("%02d", i);
pDC->TextOut(i * 1440, 0, str);
}
j = - m_rectPrint.Height() / 1440;
for (i = 0; i <= j; i++) {
str.Format("%02d", i);
pDC->TextOut(0, -i * 1440, str);
}
// Print the poem 0.5 inch down and over;
// use 10-point roman font
font.CreateFont(-200, 0, 0, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,
"Times New Roman");
CFont* pOldFont = (CFont*) pDC->SelectObject(&font);
pDC->GetTextMetrics(&tm);
nHeight = tm.tmHeight + tm.tmExternalLeading;
TRACE("font height = %d, internal leading = %d\n",
nHeight, tm.tmInternalLeading);
j = pDoc->m_stringArray.GetSize();
for (i = 0; i < j; i++) {
pDC->TextOut(720, -i * nHeight - 720, pDoc->m_stringArray[i]);
}
pDC->SelectObject(pOldFont);
TRACE("LOGPIXELSX = %d, LOGPIXELSY = %d\n",
pDC->GetDeviceCaps(LOGPIXELSX),
pDC->GetDeviceCaps(LOGPIXELSY));
TRACE("HORZSIZE = %d, VERTSIZE = %d\n",
pDC->GetDeviceCaps(HORZSIZE),
pDC->GetDeviceCaps(VERTSIZE));
}
OnPreparePrint
задава максималния брой печатни страници в заданието за печат (print job). Задължително трябва да се извика DoPreprarePrinting:BOOL CStringView::OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo->SetMaxPage(1);
return DoPreparePrinting(pInfo);
}
Редактирай конструктора в
StringView.cpp. Първоначалната стойност на правоъгълника за печат трябва да бъде 8 на 10.5 инча, изразена в twip-единици (1inch = 1440 twips)CStringView::CStringView() : m_rectPrint(0, 0, 11520, -15120)
{
}
Тествай приложението
Прочитане на правоъгълника на принтера
Указателя от тип
CPrintInfo има поле m_recDraw, което съдържа правоъгълника в логически координати. Функцията OnPrint вкарва в правоъгълника някоя член-променлива на изгледа, а OnDraw го показва. Не може да се получи размера на правоъгълника преди да започне печата.Ако искаш примера Ех18а да чете правоъгълника на принтера и в съответствие с това да настрйва размера на изгледа за скролиране, предифинирай:
void CStringView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
m_rectPrint = pInfo->m_rectDraw;
SetLogScrollSizes(CSize(m_rectPrint.Width(), -m_rectPrint.Height()));
CLogScrollView::OnPrint(pDC, pInfo);
}
Пример ЕХ18В - печатане на маса страници
1. Генерирай проекта.
Избери Single Document. Останалото е по подразбиране.2.
Прибави в StdAfx.h#include <afxtempl.h>
3.
Прибави в Ex18bDoc.htypedef CArray<CRect, CRect&> CRectArray;
public:
enum { nLinesPerPage = 12 };
enum { nMaxEllipses = 50 };
CRectArray m_ellipseArray;
4.
Коригирай функцията:BOOL CEx18bDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
int n1, n2, n3;
// Make 50 random circles
srand((unsigned) time(NULL));
m_ellipseArray.SetSize(nMaxEllipses);
for (int i=0; i< nMaxEllipses; i++) {
n1 = rand() * 600 / RAND_MAX;
n2 = rand() * 600 / RAND_MAX;
n3 = rand() * 50 / RAND_MAX;
m_ellipseArray[i] = CRect(n1, -n2, n1+n3, -(n2 + n3));
}
return TRUE;
}
void CEx18bDoc::Serialize(CArchive& ar)
{
m_ellipseArray.Serialize(ar);
}
5.
Прибави следните променливи в Ех18bpublic:
int m_nPage;
private:
void PrintPageHeader(CDC* pDC);
void PrintPageFooter(CDC* pDC);
Променливата
m_nPage съдържа номера на страницата за отпечатване. Private функците се използват за за работа с горен и долен колонтитул.Функцията
OnDraw, чертае балончетаvoid CEx18bView::OnDraw(CDC* pDC)
{
int i, j;
CEx18bDoc* pDoc = GetDocument();
j = pDoc->m_ellipseArray.GetUpperBound();
for (i = 0; i < j; i++) {
pDC->Ellipse(pDoc->m_ellipseArray[i]);
}
}
Създай функциите:
BOOL CEx18bView::OnPreparePrinting(CPrintInfo* pInfo)
{
CEx18bDoc* pDoc = GetDocument();
pInfo->SetMaxPage(pDoc->m_ellipseArray.GetUpperBound() /
CEx18bDoc::nLinesPerPage + 1);
return DoPreparePrinting(pInfo);
}
Отпечатаното, ще бъде различно от онова, което се вижда на екрана
void CEx18bView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
int i, nStart, nEnd, nHeight;
CString str;
CPoint point(720, -1440);
CFont font;
TEXTMETRIC tm;
pDC->SetMapMode(MM_TWIPS);
CEx18bDoc* pDoc = GetDocument();
m_nPage = pInfo->m_nCurPage; // for PrintPageFooter's benefit
nStart = (m_nPage - 1) * CEx18bDoc::nLinesPerPage;
nEnd = nStart + CEx18bDoc::nLinesPerPage;
// 14-point fixed-pitch font
font.CreateFont(-280, 0, 0, 0, 400, FALSE, FALSE,
0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_MODERN, "Courier New");
// Courier New is a TrueType font
CFont* pOldFont = (CFont*) (pDC->SelectObject(&font));
PrintPageHeader(pDC);
pDC->GetTextMetrics(&tm);
nHeight = tm.tmHeight + tm.tmExternalLeading;
for (i = nStart; i < nEnd; i++) {
if (i > pDoc->m_ellipseArray.GetUpperBound()) {
break;
}
str.Format("%6d %6d %6d %6d %6d", i + 1,
pDoc->m_ellipseArray[i].left,
pDoc->m_ellipseArray[i].top,
pDoc->m_ellipseArray[i].right,
pDoc->m_ellipseArray[i].bottom);
point.y -= nHeight;
pDC->TextOut(point.x, point.y, str);
}
PrintPageFooter(pDC);
pDC->SelectObject(pOldFont);
}
OnPreparePrinting изчислява броя на страниците в документа и след това го съобщава на
SetMaxPageBOOL CEx18bView::OnPreparePrinting(CPrintInfo* pInfo)
{
CEx18bDoc* pDoc = GetDocument();
pInfo->SetMaxPage(pDoc->m_ellipseArray.GetUpperBound() /
CEx18bDoc::nLinesPerPage + 1);
return DoPreparePrinting(pInfo);
}
Отпечатване на колонтитул (
header и footer) на страницаvoid CEx18bView::PrintPageHeader(CDC * pDC)
{
CString str;
CPoint point(0, 0);
pDC->TextOut(point.x, point.y, "Bubble Report");
point += CSize(720, -720);
str.Format("%6.6s %6.6s %6.6s %6.6s %6.6s",
"Index", "Left", "Top", "Right", "Bottom");
pDC->TextOut(point.x, point.y, str);
}
void CEx18bView::PrintPageFooter(CDC * pDC)
{
CString str;
CPoint point(0, -14400); // Move 10 inches down
CEx18bDoc* pDoc = GetDocument();
str.Format("Document %s", (LPCSTR) pDoc->GetTitle());
pDC->TextOut(point.x, point.y, str);
str.Format("Page %d", m_nPage);
CSize size = pDC->GetTextExtent(str);
point.x += 11520 - size.cx;
pDC->TextOut(point.x, point.y, str); // right-justified
}
Тествай скапаното приложение.
Прозорец за разделяне (
Splitter Window)Този прозорец има няколко изгледа в т.нар. "пана" (
panes). Може да се раздели при създаването си а може и в последствие с избирането на команда, изтеглянето на разделителна кутия (splitter box) от лентата за скролиране. Разделителите се изтеглят с мишката на желания размер. Могат да се използват SDI и MDI приложения.Пример ЕХ19А
Използва се кода за документа от ЕХ18А. Създай
SDI проект. В стъпка 4 избери Advance щракни върху етикета на страницата Windows Styles и избери Use Split Window.Другите примери ще ги пропусна ми щото са си тъпичко обяснени.
Пример Ех20А. Създаване на прост помощен файл
1. Създай директорията;
2. Напиши с
Word или друг редактор поддържащ RTF формат (я по-добре гепи готовия файл)#$
Simple Help Table of ContentsHelp topics
Topic 1HID_TOPIC1HID_TOPIC1
Topic 2HID_TOPIC3HID_TOPIC2
Topic 3HID_TOPIC3HID_TOPIC3
Всички HID_TOPICх в жълто са скрити. В зависимост от желанието ти за показване на информацията цялата връзка трябва да бъде подчертана, двойно подчертана или зачеркната. При подчертаване помощта се показва в прозорец под връзката.
Вмъкни нова страница и напиши това:
#$K
Help Topic -1-This is the text for help topic number 1.
3. Добави бележки (
footnotes) за екрана със съдържанието Table Of Contents. Това е първия екран;
знак |
текст |
описание |
# |
HIR_CONTENTS |
Контекстен идентификатор |
$ |
SIMPLE Help Contents |
Заглавие на темата |
4. Добави бележки за екрана
Help Topic1
знак |
текст |
описание |
# |
HIR_TOPIC1 |
Контекстен идентификатор |
$ |
SIMPLE Help Topic 1 |
Заглавие на темата. Това е заглавието което се показва при търсене с ключова дума когато има повторения на ключови думи. |
K |
SIMPLE Topic |
Тест за ключова дума |
5. Копирай екрана
Help Topic1, като включиш знака за нова страница и в следващите страници смениш номера.6. Запиши документа като
Simple.rtf7.
Напиши проектен файл. Използвай Developer Studio или друг текстов редактор. Създай Simple.hpj.[OPTIONS]
CONTENTS=HID_CONTENTS
TITLE=SIMPLE Application Help
COMPRESS=true
WARNING=2
[FILES]
Simple.rtf
8.
Изгради Help файла. Стартирай Microsoft Help Workshop. Щракни два пъти с мишката върху Simple.hpj. След това го компилирай.9. Стартирай файла
Simple.hlpПомощта може да се покаже и като дървовидна структура. Трябва да се създаде текстов файл с разширение
CNT.:Base Simple.hlp
1 Help topics
2 Topic 1=HID_TOPIC1
2 Topic 2=HID_TOPIC2
2 Topic 3=HID_TOPIC3
Компилирай файла и стартирай
Simpe.hlpИзвикване на
WinHelpФункцията
WinHelp на CWinApp активира WinHelp от твоето приложение. Тази функция има два параметъра. Втория не е задължителен. Целочисления параметър dwData от тип unsigned long съответства на темата на помощта. Ако се изпълни:AfxGetApp()->WindHelp(HID_TOPIC1);
ще се появи екранът
Help Topic 1. Името на Help файла съвпада с името на приложението. Може да се използва и друго име ако се зададе стойност на променливата m-pszHelpFilePath на CWinApp. Файлът на help-проекта трябва да съдържа секция МАР, която описва връзката между контекстните идентификатори и числа. Ако файлът resource.h от приложението дефинира HID_TOPIC1 като 101, секцията МАР в Simple.hpj трябва да изглежда по този начин:[MAP]
HID_TOPC1 101
Когато се използва за търсене дума използвай опциите
HELP_KEY или HELP_PARTIALKEY . Например:CString STRING ("FIRST STRING");
AfxGetApp->WinHelp((DWORD) (LPCSTR) STRING, HELP_KEY);
Двойното преобразуване по тип за
string е необходимо, защото първият параметър на WinHelp е многоцелеви. Неговото значение се определя в зависимост от стойността на втория параметър.App Wizard
генерира опцията Help Topics в менюто Help и асоциира тази опция с CWind::OnHelpFinder от прозореца в главната рамка, която пък извиква:AfxGetApp()->WinHelp (0L , HELP_FINDER);
Ако искаш да има елемент "помощ за помощта", използвай:
AFxGetApp()->WinHelp(0L, HELP_HELPONHELP);
Секцията
ALIAS от HPJ- файла позволява да приравниш един контекстен идентификатор на друг:[ALIAS]
HID_TOPC1 = HID_GETTING_STARTED
[MAP]
HID_TOPC1 101
RTF
файловете могат да използват HID_TOPIC1 и HID_GETTING_STARTED взаимозаменяемо. И двата ще бъдат асоциирани с help-контекста 101, както е генерират от твоето приложение.Определяне на
help-контекстаДефинирай
F1 като клавиатурен акселератор и след това напиши функцията за обработка на командата, която асоциира help-контекста от програмата с параметър на WinHelp. Може да използваш и свой код освен вече дефинирания от системата.Приложната среда определя
help-контекста въз основа на идентификатора на активния програмен елемент. Такива програмни елементи с идентификатори са меню-командите, прозорците за рамки, диалоговите прозорци, прозорците за съобщения и панелите за контроли. Един меню-елемент може да бъде идентифициран като ID_EDIT_CLEAR_ALL например, а прозореца на главната рамка обикновено има идентификатор IDR_MAINFRAME. Може да очакваш, че тези идентификатори се асоциират директно с help-контекстни идентификатори - например IDR_MAINFRAME би трябвало да съответства на контекстен идентификатор със същото име. Идентификатора на прозореца и командата трябва не трябва да имат еднакви стойности.Приложната среда решава кахъра с припокриването, като дефинира ново множество
#define-константи, производни на идентификаторите на програмните елементи. Тези константи представлява сумата на идентификатора на елемента и една от следните базови стойности:
Програмен елемент |
ID на елемент |
ID на контекст |
Hex |
Елемент на меню или бутон на лентата с инструменти |
ID_, IDM_ |
HID_, HIDM_ |
10000 |
Рамка или диалог |
IDR_, IDD_ |
HIDR_, HIDD |
20000 |
Диалог със съобщение за грешка |
IDR_ |
HIDP_ |
30000 |
Не клиентска област |
H_ |
40000 |
|
Управляващ панел |
IDW_ |
HIDW_ |
50000 |
Съобщения за грешки при изпращане (dispatch) |
60000 |
HID_EDIT_CLEAR_ALL (0x1E121)
съответствува на ID_EDIT_CLEAR_ALL (0xE121), HIDR_MAINFRAME (0x20080) съответства на IDR_MAINFRAME (0x80).При натискане на
F1 програмата може да направи най-доброто предположение за контекста на помощта и след това извиква WinHelp. В този режими е възможно да се определи текущо избраната команда от менюто или избрания прозорец.По-мощен режим за извикване на помощ е
Shift-F1. Програмата може да идентифицира следните help-контексти:- Меню-елемент, избран с мишката;
- Бутон от панела с инструменти;
- Прозорец на рамка;
- Прозорец на изглед;
- Специфични графични елементи от прозорец;
- Лента за статус;
- Различни не-клиентски елементи, като контрола на системно меню.
Shif-F1
не работи с модални диалози или прозорци за съобщения.Помощ в прозорци за съобщения
Глобалната функция
AfxMessageBox показва съобщения за грешки. Тази функция е подобна на MessageBox, с тази разлика, че има параметър за контекстен идентификатор. Приложната среда асоциира този идентификатор с контекстен идентификатор и извиква помощ при натискането на F1. Идентификатора трябва да започва с IDP_. В RTF файловете използвайте идентификатор за help-контекст започващи с HIDP.Има две версии на
AfxMessageBox. В първата версия подсказващия низ се указва посредством параметър, който е указател към масив от символи. Във втората параметъра указва един стрингов ресурс. И двете версии приемат параметър за стил, който настройва прозореца за съобщения да показва удивителна, въпросителна или друг графичен символ.Когато контекстно чувствителната помощ (Context Sensitive Help) е включена,
AppWizard подготвя серия от подразбиращи се теми, които се асоциират със стандартните елементи на MFC. Тези теми се съдържат във файловете AfxCore.rtf и AfxPrint.rtf. Тези файлове се създават в директорията hlp на приложението.Пример
EX19D - Помощ1. Стартирай проекта. Натисни
F1. Ако не излезе помощ със заглавие "Modify the Document" значи bat файла MAKEHELP се е смрангясал. Генерирай помощта по познатия начин и стартирай bat файла.Посочи с мишката
File -> New и натисни F1. Натисни с мишката бутона Help и след това избери Save. Ефекта е същия5. Добави тема за меню-елемента
New String Window#
$KNew String command (Window Menu)Use the command to open a new String window with the same contents as the active window. You can open multiple document windows to display different parts of views of a document at the same time. What you open a new window, it new window, it becomes the active window and is displayed is top of all other open windows.
Добави и
footnote#
HID_WINDOW_NEW_STRING$
New String WindowK New String
В програмата идентификаторът е
ID_WINDOW_NEW_STRINGПроцесът MAKEHELP се извършва от програмата
makehm.exe. Тя прочита файла resource.h и генерира файл с карта за помощта (help map file), който дефинира стойностите на контекстните идентификатори на помощта.Обработка на
F1Клавишът
F1 се обработва от клавиатурен акселератор, което App Wizard вмъква в RC файла. Акселератора се асоциира с клавиша F1 с командата ID_HELP, която се подава на функцията OnHelp на класа CFrameWnd. Функцията CFrameWnd::OnHelp изпраща съобщение WM_COMMANDHELP на най-вътрешния прозорец, който обикновено е изгледът. Ако вашият изгледен клас не прихваща това съобщение или ако функцията за обработка върне FALSE, приложната среда препраща съобщението към следващия външен прозорец. Ако не си асоциирал съобщението WM_COMMANDHELP в производни класове за прозорци на рамки, съобщението се обработва от класа CFrameWnd.Ако асоциираш
WM_COMMANDHELP в наследен клас, твоята функция трябва да извиква CWinApp::WinHelp с подходящ контекстен идентификатор като параметър.За всяко приложение
App Wizard добавя символа IDR_MAINFRAME към проекта, а НМ файла дефинира контекстния идентификатор HIDR_MAINFRAME, който е синоним на main_index от HPJ файла. Стандартния файл AfxCore.rtf асоциира главния индекс с този контекстен идентификатор.Например за едно
MDI-приложение, наречено SAMPLE, App Wizard добавя също и символа AIDR_SAMPLETYPE, който пък е синоним на HIDR_DOC1TYPE от HPJ файла. Стандартния файл AfxCore.rtf асоциира темата "Modify the Document" с този контекстен идентификатор.Обработка на Shift-
F1При натискане на
Shift-F1 или щракане върху бутона Context Help, се изпраща командно съобщение към CFrameWnd::OnContextHelp. При повторно натискане на бутона след позициониране курсора на мишката съобщението WM_HELPITTEST се изпраща към най-вътрешния прозорец, в който е засечено щракването. Следващото пренасочване на съобщението е идентично със съобщението WM_COMMANDHELP.Параметъра
iParam на OnHelpHitTest съдържа координатите на мишката и устройствени единици, спрямо горния ъгъл на клиентската област на прозореца. Стойността на У се намира в старшата половина, а стойността на Х в младшата. Може да се използват тези координати, за определяне контекстни идентификатори специално за някой елемент от изгледа функцията OnHelpHitTest трябва да връща правилния контекстен идентификатор, а приложната среда ще извика WinHelp.Пример за обработка на
help-команда - EX20BИ двата изгледни класа имат функция
OnHelpTest за обработка на Shift-F1.Компилатора разпознава специфичните за помощта идентификатори само ако присъства следната #
include клауза#include <afxpriv.h>
В примера тази клауза се намира във файла
StdAfx.hStringView.h
се нуждае от прототипи на функции за асоцииране на съобщенията като за F1 помощ, така и за Shift-F1.afx_msg LRESULT OnCommandHelp(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
Ето и асоцииране на съобщенията в
StringView.cpp:ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)
OnCommandHelp
от CStringView обработва заявките F1 за помощ:LRESULT CStringView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
if (lParam == 0) { // context not already determined
lParam = HID_BASE_RESOURCE + IDR_STRINGVIEW;
}
AfxGetApp()->WinHelp(lParam);
return TRUE;
}
Ето и функцията
OnHelpHItTest, която обработва Shift-F1LRESULT CStringView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
return HID_BASE_RESOURCE + IDR_STRINGVIEW;
}
Класът
CHexVeiw обработва заявките за помощ по същия начин както и CStringView. Ето и необходимия код в HexVeiw.h:afx_msg LRESULT OnCommandHelp(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnHelpHitTest(WPARAM wParam, LPARAM lParam);
Това са вписванията за пренасочване на съобщенията в
HexView.cppON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)
Ето и кода за имплементация от
HexView.cppLRESULT CHexView::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
if (lParam == 0) { // context not already determined
lParam = HID_BASE_RESOURCE + IDR_HEXVIEW;
}
AfxGetApp()->WinHelp(lParam);
return TRUE;
}
LRESULT CHexView::OnHelpHitTest(WPARAM wParam, LPARAM lParam)
{
return HID_BASE_RESOURCE + IDR_HEXVIEW;
}
Ресурсни извиквания
Два нови символа са добавени към файла Resource.h. Ето техните стойности и контекстни идентификатори:
Символ |
Стойност |
Контекстен идентификатор |
Стойност |
IDR_STRINGVIEW |
101 |
HIDR_STRINGVIEW |
0x20065 |
IDR_HEXVIEW |
102 |
HIDR_HEXVIEW |
0x20066 |
Двете теми са добавени към файла
AfxCore.rtf с контекстните идентификатори HIDR_STIRNGVIEW и HIDR_HEXVIEW:Генерираният файл
ex20b.hm в директорията hlp трябва да изглежда така:
// MAKEHELP.BAT generated Help Map file. Used by EX20B.HPJ. // Commands (ID_* and IDM_*) |
|
HID_WINDOW_NEW_STRING |
0x18003 |
HID_WINDOW_NEW_HEX |
0x18004 |
// Prompts (IDP_*) // Resources (IDR_*) |
|
HIDR_STRINGVIEW |
0x20065 |
HIDR_HEXVIEW |
0x20066 |
HIDR_MAINFRAME |
0x20080 |
HIDR_EX20BTYPE |
0x20081 |
// Dialogs (IDD_*) |
|
HIDD_ABOUTBOX |
0x20064 |
// Frame Controls (IDW_*) |
Библиотеки за динамично свързване -
DLLDynamic Link Libraries
Могат да се използват за писане на модифицируем софтуер по време на изпълнението.
DLL модулите са малки. Предпроцесора е работеща инстанция на една програма, а програмата се стартира като ЕХЕ файл от диска. DLL съдържа глобални данни и компилирани функции които са част от процес. Те се компилират за зареждане на избран базов адрес и ако няма конфликти с останалите DLL-и файла се асоциира със същия виртуален адрес от твоя процес.При
Win32 всеки процес взема собствено копие за четене/запис на глобалните променливи на DLL. Ако искаш да имаш общодостъпна или споделена памет между процесите, трябва да използваш файл за асоцииране с паметта (memory-mapped file), или да декларираш споделена секция за данни (shared data section)Всяка динамична библиотека съдържа таблица с експортираните функции. Тези функции се идентифицират към външния свят посредством техните символни имена или с числа наречени предни номера. Процеса на динамично свързване свързва адресите на функциите с клиентските извиквания.
В кода на библиотеката трябва изрично да декларираш експортните функции по следния начин:
_declspec(dllexport) int MyFunction (in n);
Съществува възможност да изброиш експортираните функции в дефиниращ модула
[DEF] файл. но това създава повече кахъри. Съответстващите импорти трябва да се декларираш по следния начин:_declspec(dllimport) int MyFunction (int n);
Компилатора генерира декорирано име за функцията
MyFunction, което другите езици не могат да използват. Ако искаш да използваш името MyFunction, трябва да напишеш:extern "C" _declspec(dllexport) int MyFunction (int n);
extern "C" _declspec(dllimport) int MyFunction (int n);
За да се направи клиентска връзка с
DLL трябва да се укаже импортна библиотека (LIB) на линкера и освен това клиентската програма трябва да има поне едно извикване на някоя от импортираните функции на динамичната библиотека, като при това извикващия израз трябва да бъде на такова място в програмата, където със сигурност ще бъде изпълнена.Ако свързването е имплицитно (
implicit link) при изграждане на DLL линкерът създава придружаващ импортен LIB файл, който съдържа всички експортирани символи и евентуално поредни номера. LIB файла е заместител на DLL, който се добавя към проекта на клиентската програма.Ако свързването е експлицитно (
explicit linking) не се използва импортен файл. Извиква се Win32 функцията LoadLibrary, като се указва пътя до съответния DLL като параметър. LoadLIbrary връща HINSTANCE параметър, който може да използваш при извикването на GetProcAddres, която пък конвертира един символ или номер в адрес от динамичната библиотека. Ако имаш DLL който експортира функция като тази:extern "C"_declspec(dllexport) double SquareRot (double d);
ето примерно експлицитно свързване на клиент към тази функция:
typedef double (SQRTPROC) (double d);
HINSTANCE histance;
SQRTPROC* pFunction;
VERIFY (hinstance = :LoadLibrary ("c:\\winnt\\system32\\mydll.dll"));
VERYFY (pFunction = (SQRTPROC*)::GetProcAddress (hinstance, "SquareRoot"));
double d = (*pFunction)81.0;; //Call the DLL function
Една динамична библиотека експортира цели класове. Клиентската програма и библиотеката разширение трябва да бъдат синхронизирани с една и съща версия на
MFC библиотеката (mfc42.dll, mfcd42dll и др.) Библиотеките разширения са малки.При изграждане на обикновен
DLL може да се избере статично или динамично свързване с MFC библиотеката. При статично свързване твоята библиотека ще съдържа целия MFC код от който се нуждае. Една статично свързана библиотека (изградена с опцията Release build) е с размер около 144 КВ. При динамично размера е око 17 КВ.Когато се укаже какъв тип ще е програмата
DLL или ЕХЕ, App Wizard настройва #define-константите по следния начин:
Динамично свързан към обща MFC- |
Динамично свързан към MFC библиотека |
|
Обикновен DLL |
_AFXDLL, _USRDLL |
_USERDLL |
DLL- разширение |
_AFXEXT, AFXDLL |
не се поддържа |
ЕХЕ-клиент |
_AFXDLL |
не се дефинира константи |
При изграждане на споделени
(shared) MFC DLL, твоята програма се свърза динамично с една или повече от следните библиотеки:
mfc42d.dll |
Core MFC classes |
mfco42d.dll |
ActiveX (OLE) classes |
mfcd42d.dll |
Database classes (ODBC and DAO) |
mfcn42d.dll |
Winsock, WinInet classes |
При изграждане на
Release версия твоята програма се свърза само към mfc.dll.Ако твоите
extensions DLLs съдържат експортирани С++ класове, може лесно да ги създадеш Стъпките в пример ЕХ21А показват как да кажеш на App Wizard че изграждаш скелет за DLL - разширение. Този скелет съдържа само функцията DllMain. Добави си класовете. Към декларацията на обаче трябва да се добави макросът AFX_EXT_CLASS:class AFX_EXT_CLASS CStudent: public CObject
Тази модификация се поставя в Х файла, който е част от
DLL проекта.Понякога може да се наложи промяна в последователността на търсенето в ресурсите на
DLL. Ето пример за претърсване първо на библиотеката разширение:HINSTANCE hinstResoriceClient = AfxGetResourceHande("Mydllname.dll"));
//Use Dll's instance handle
AfxSetResourceHandle (::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString (IDS_MYSTRING);
//Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);
Пример ЕХ2
1АТози пример създава DLL разширение от класа
CPresistentFrame, който видя в глава 14. Първо ще се изгради ex12a.dll, а в последствие ще го използваш в една текстова клиентска програма ЕХ21В:1. Стартирай
App Wizard. Създай проекта само че избери MFC App Wizard (dll). От следващия екран избери MFC Extension DLL2. Разгледай Ех21а.срр;
3. Вмъкни класа
CPersistenFrame в проекта.4. Редактирай
persist.hclass AFX_EXT_CLASS CPersistentFrame : public CFrameWnd
5. Компилирай
. Копирай файла Ех21а.DLL в системната директорията \windows\system или там която е.Пример ЕХ21В
Тестване на
DLL-чето1. Създай проекта. Обикновено ЕХЕ;
2. Копирай само persist
.h;3.
Смени името CMainFrame c CPersistenFrame. Замени всички срещания на CFrameWnd c SersistenFrame. Добави в MainFrame.h и MainFrame.cpp#include "persist.h"
4.
Добави импортната библиотека ех21а към списъка с водни библиотеки на линкера. Избери Project -> Setting. От списъка избери All Configurations. Попълни полето Object library. Трябва да укажеш пълния път към файла ex21a.lib ако не е в текущата директория.5. Тествай проекта. Ако стартираш програмата от дебъгера и
Windows не може да намери ех21а.dll, той извежда прозорец със съобщение, при стартирането. Ако всичко е точно трябва да получиш приложение с постоянна рамка, което работи като ех14а. Разликата е само, че CPresistentFrame се е една DLL разширение.Пример ЕХ21С
Обикновена динамична библиотека, която експортира една функция за корен квадратен. Първо ще изградиш файла
ex21c.dllслед това ще модифицираш тестващата програма-клиент ex21b, за да тестващ новата библиотека1. Създай проекта. Избери
Regular DLL Using Shared MFC2. Разгледай кода на
ex21c.cpp;3.
Добави кода за експортиране на функцията Ex12cSequareRoot.extern "C" __declspec(dllexport) double Ex21cSquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
TRACE("Entering Ex21cSquareRoot\n");
if (d >= 0.0) {
return sqrt(d);
}
AfxMessageBox("Can't take square root of a negative number.");
return 0.0;
}
#include "math.h"
4.
Компилирай проекта. Копирай DLL файла.До сега примера се свързваше динамично с
MFС динамичната библиотека-разширение ех211а. Сега ще се промени проекта за свързване имплицитно обикновената MFC DLL-EX21C и извикаш функция за корен квадратен1. Добави диалогов ресурс. Създай шаблона
IDD_EX21CГенерирай класа
CTestDialog, наследник на CDialog. Контролите, променливите и функциите са показани в таблицата.
Контрол |
Тип |
Член-променлива |
Функция |
|||
IDC_INPUT |
edit |
m_dInput (double) |
||||
IDC_OUTPUT |
edit |
m_dOutput (double) |
||||
IDC_COMPUTE |
button |
OnCompute |
2. Напиши кода на функцията
OnCompute. Извикай експортиращата функция на динамичната библиотека. Редактирай функциятаvoid CTest21cDialog::OnCompute()
{
UpdateData(TRUE);
m_dOutput = Ex21cSquareRoot(m_dInput);
UpdateData(FALSE);
}
Трябва да декларираш функцията
EX21cSquareRoot. Като импортирана функция. Добави в Test21cDialog.h:extern "C" __declspec(dllimport) double Ex21cSquareRoot(double d);
3.
Интегрирай класа Ctest21cDialog в приложението ex21b. Добави менюто Test и командата Ех21с DLL с идентификатор ID_TEST_EX21CDLL. Асоциирай тази меню-команда към член-функция от класа CEx21bView и добави тази функция:void CEx21bView::OnTestEx21cdll()
{
CTest21cDialog dlg;
dlg.DoModal();
}
Прибави към
Ex21bView.cpp:#include "Test21cDialog.h"
4.
Добави импортната библиотека Ех21С към списъка с входните библиотеки на линкера. Избери Project -> Setting и добави ..\vcpp32\ex21c\Debug\ex21c.lib в полето Object/Library Modules от страница Link. Интервала служи за разделител.5. Програмата не ще да тръгне.
Динамични библиотеки с потребителски контрол
Потребителските контроли имат същия принцип както и обикновените контроли. Може да се рисува всичко в техните прозорци. Може да се използва
Class Wizard за асоцииране на съобщенията.Шаблонът с ресурса на диалога указва своите потребителски контроли с техните имена на прозоречния клас.
Ако не искаш да пишеш остарели разклонения с
case оператори - може да асоциираш съобщенията към член-функции. В този случай трябва да имаш DLL със С++ клас, който да съответства на прозоречния клас на контролата.Аба ми според тъпите автори на книжката сега имало очевидно решение. Използвай
Class Wizard за да създадеш нов клас, наследника CWnd. Сега пък имало хитра част свързването на класа с функцията WndProc и с механизма за извличане на съобщения. Това бил кода на функция WinProc за един контрол. мале мале!LRESULT MyControlWndroc (HWND hWnd, UINTT message WEPARAM w Param. LPARAM lParam)
{
if (this is the first message for this window){
CWnd* pWnd = new CMYControlWindowClass();
attach pWnd to hWnd
}
return AfxCallWndProc (pWnd, message, WParam, lParam;
}
AfxCAllWndProc
функцията подава съобщенията към приложната среда която от своя страна ги разделя към асоциираните член-функции в CMyControlWindowClass.Контролът комуникира с родителския прозорец, като му изпраща съобщения
WM_COMMAND с параметри, които са показани в таблицата:
Параметър |
Употреба |
(HIWORD)wParam |
Код на нотификацията |
(LWORD)wParam |
Идентификатор на дъщерния прозорец |
lParam |
Манипулатор на дъщерния прозорец |
Значението на нотифицирания код зависи и се определя от контрола. Родителския прозорец трябва да интерпретира кода, базирайки се на познанията си за контрола. Например код 77 може да означава, че потребителят е въвел символ в контролата
Контрола може да изпраща нотифициращи съобщения по следния начин:
GetParent()->SendMessage (WM_COMMAND, GetDlgCtrllD() | ID_NOTIFICODE << 16, (LONG) GetSafeHwnd());
От клиентската страна, ти асоциираш съобщенията с
MFC макроса ON_CONTROL по този начин:ON_CONTROL (ID_NOTIFYCODEJ, IDC_MYCONTROL, OnClickedMyControl)
Следващите обяснения ги пропускам защото са ЛЕКО тъпанарски. Пише едно а програмата прави друго.
Пример ЕХ22А
Тука диалога функционира като калкулатор. Class Wizard се грижи за генерирането на извикванията на
DDX (Dialog Date Exchange) функцията.2. От
Properties -> System избери System Menu и Minimize Box. Задай имена на контролите:
Контрол |
Идентификатор |
edit - контрол за левия операнд |
IDC_LEFT |
edit - контрол за десния операнд |
IDC_RIGHT |
edit - контрол за резултата |
IDC_RESULT |
първи радио бутон |
IDC_OPERATION |
бутон за изчисление |
IDC_COMPUTE |
3.
Добави член променливите в CEx22aDlg;
Идентификатор |
Член-променлива |
Тип |
||
IDC_LEFT |
m_dLeft |
double |
||
IDC_RIGHT |
m_dRight |
double |
||
IDC_RESULT |
m_dResult |
double |
||
IDC_OPERATION |
m_nOperation |
int |
Добави и функцията
OnCompute за IDC_COMPUTE4. Напиши кода за
OnComute в ex22aDlg.cpp;void CEx22aDlg::OnCompute()
{
UpdateData(TRUE);
switch (m_nOperation) {
case 0: // add
m_dResult = m_dLeft + m_dRight;
break;
case 1: // subtract
m_dResult = m_dLeft - m_dRight;
break;
case 2: // multiply
m_dResult = m_dLeft * m_dRight;
break;
case 3: // divide
if (m_dRight != 0.0) {
m_dResult = m_dLeft / m_dRight;
}
else {
AfxMessageBox("Divide by zero");
m_dResult = 0.0;
}
break;
default:
TRACE("default; m_nOperation = %d\n", m_nOperation);
}
UpdateData(FALSE);
}
Функцията CeEx22aApp::Instance създава основния прозорец и връща
TRUE, разрешава цикъла за обработка на съобщения, конструира обект от немодален диалог, извиква DoModal и връща FALSE.Пример ЕХ22В
H
еllo world. Този надпис се показва на екрана. При затваряне на джама. Излиза надпис дали искаш да го затвориш. Та ето и кода.void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.TextOut(0, 50, "Hello, world!");
// Do not call CFrameWnd::OnPaint() for painting messages
}
void CMainFrame::OnClose()
{
if (AfxMessageBox("OK to close window?", MB_YESNO) == IDYES) {
CFrameWnd::OnClose();
}
}
BOOL CMainFrame::OnQueryEndSession()
{
if (AfxMessageBox("OK to close window?", MB_YESNO) == IDYES) {
return TRUE;
}
return FALSE;
}
Това е пример за вложени класове.
class CSpacehlp {
protected:
int m_nPosition;
int m_Accelerator;
int m_nColor;
public:
CSpacehlp()
{m_nPosition = m_nAccelerator = m_nColor = 0;}
class XMoton: public lMotion {
public:
XMotion{};
virtual void Fly();
virtual int& GetPosition;
}m)xMotion;
class XVisual: public lVisual {
public:
XVisula{};
virtual void Dislay();
}m_xVisual;
friend class XVisual;
friend class XMOtion;
};
Имплементациите
lMotion и lVisual са разположени в родителския класПример ех23а
Според документация трябва да е СОМ файл. Няма точни обяснения затова пропускам всичко.
Понеже по традиция в тази книга обясненията са МАСА ЯКИ пропускам цялото за СОМ.
Та значи, според авторите на това дето е обратното на острото и го преписвам понякога и в полу-трезво състояние, ако някой си е направил труда да го чете в пример ех23а били използвани вложени класове за имплементация на интерфейси.
MFC библиотеката има набор от макроси, които автоматизират този процес. За класа CSpaceship, били използвани макросите:BEGIN_INTERVACE_PART(Motion, IMotion)
STOMETHOD_(void,vly)();
STOMETHOD_(int&, GetPosition)();
END_INTERFACE_PART(Monitor)
BEGIN_INTERFACE_PART (Visual, IVisual)
STOMETHOD_(void, Display)();
END_INTERFACE_PART(Visual);
Макросите
INTERFACE_PART генерират вложените класове, добавяйки Х към първия параметър за форматиране името на класа, а името на вградения обект се формира чрез добавяне на m_x. Макросите генерират прототипи за указваните интерфейсни функции, и прототипи за WueryInterface, AddRef, и Release.Макросът
DECLARE_INTERFACE_MAP генерира декларации за таблица, която съдържа идентификаторите на всички интерфейси на класа. Някоя си функция CCmdTarget::ExternalQueryInterface използва таблицата, за да извлече интерфейсните указатели.Уф пак тъпотии. Хич и не се обяснява онова което се пише затова да го духат. Който не вярва да си го чете от книгата.......стр. 560 -> .... 583
Типът
VARIANT //скивай само как се дънятАба ми бил някакъв си многоцелеви тип който се използвал от
IDispatch::invoce за предаване на параметри и връщаните стойност. /*Е това не го вярвам и пак пропускам тъпотии ...*/Та значи този
VARIANT съдържал код от тип vt .....Пример ЕХ24А
EXE Automation -
компонент без потребителски интерфейс. С++ компонентът имплементира финансови транзакции. Може да се пише потребителски интерфейс на VBA1. Генерирай проекта.
В стъпка 1 избери Dialog-Based. В стъпка 2 изключи всички опции.2. Премахни диалоговия клас от проекта. Забърши Ex24Dlg.h и
Ex24Dlg.cpp. Премахни ги и от проекта от прозореца Workspace. Премахни от ex24a.cpp #include директивата на диалога. Изтрий шаблона на диалоговия ресурс IDD_EX24A_DIALOG.3.
Добави кода за разрешаване на Automation. Добави в StdAfx.h;#include <afxdisp.h>
Добави и
BOOL CEx24aApp::InitInstance()
{
AfxOleInit();
if (RunEmbedded() || RunAutomated())
{
// component started by COM
COleTemplateServer::RegisterAll();
return TRUE;
}
// Component is being run directly by the user
COleObjectFactory::UpdateRegistryAll();
AfxMessageBox("Bank component is registered"); // add this line
return FALSE;
}
4.
Добави клас CBank5. Добави два метода и характеристика с Class Wizard. Избери страницата Automation и добави метода Withdrawal. Параметърът dAmount е сумата, която трябва да се изтегли, а връщаната стойност е действително изтеглена сума. Ако се опиташ да изтеглиш $100 а сумата е $60, изтеглената сума ще бъде $60.
6. Добави
m_dBalance. Public променлива тип double към класа CBank във файла Bank.h. Инициализирай я 0.0 в конструктора CBank, който се намира във файла bank.cpp7.
Редактирай иdouble CBank::Withdrawal(double dAmount)
{
if (dAmount < 0.0) {
return 0.0;
}
if (dAmount <= m_dBalance) {
m_dBalance -= dAmount;
return dAmount;
}
double dTemp = m_dBalance;
m_dBalance = 0.0;
return dTemp;
}
void CBank::Deposit(double dAmount)
{
if (dAmount < 0.0) {
return;
}
m_dBalance += dAmount;
}
double CBank::GetBalance()
{
// TODO: Add your property handler here
return m_dBalance;
}
void CBank::SetBalance(double newValue)
{
// TODO: Add your property handler here
TRACE ("Sorry, Dave I can't do train'n");
}
8. Стартирай проекта
9. Замени 5 макроса на
Excel в една нова работна книга - ex24a.хls. Добави следния кодDim Bank As Object
Sub LoadBank()
Set Bank = CreateObject("Ex24a.Bank")
End Sub
Sub UnloadBank()
Set Bank = Nothing
End Sub
Sub DoDeposit()
Range("D4").Select
Bank.Deposit (ActiveCell.Value)
End Sub
Sub DoWithdrawal()
Range("E4").Select
Dim amt
amt = Bank.Withdrawal(ActiveCell.Value)
Range("E5").Select
ActiveCell.Value = amt
End Sub
Sub DoInquiry()
Dim amt
amt = Bank.Balance()
Range("G4").Select
ActiveCell.Value = amt
End Sub
11.
Тествай проекта. Натисни бутона Load Bank Program и въведи стойността за внасяне в клетка D4 и натисни бутона Deopsit. Натисни бутона Balance Inquiry и виж появилия се остатък в клетка G4. Въведи стойност за теглене в клетка Е4 и натисни Withdrawal за да видиш остатъка.Когато стартираш програмата тя показва съобщение и излиза. Функцията
COleObjectFactory::UpdateRegistryAll лови глобалните glass factory обекти, а извикването на макроса IMPLEMENT_OLECREATE се генерира, понеже избра опцията Createable By Type ID, когато добави класа CBank. Идентификатора на класа и идентификатора на програмата, EX24A.BANK, се добавят в Registry.Когато
Excel извика CreateObject, COM зарежда програмата ex24a, която съдържа глобалната "фабрика"(factory) за CBank обекти;Обясненията на другите примери започващи с ех24 ги пропускам щото са тъпички.
TCP/IP, WINSOCK WinInet
Всички мрежи използват разделянето на нива за своите протоколи за предаване. Колекцията от нива се нарича
stack. Приложните програми общуват с най-горното нива, а най-долното комуникира с мрежата. Всяко ниво е свързано логически със съответното ниво от другата страна на комуникационни канал. Програмата сървър постоянно подслушва от единия край на канала, а програмата клиент периодично се свързва.Internet Protocol (IP)
дефинира пакети наречени диаграми, които са фундаментални единици в Интернет -комуникацията. Тези пакети са по-малки от 1000 байта, обхождат целия свят, когато отваряш Web-страница. IP дейтаграмата съдържа 32 битови адреси за компютрите на източника и получателя. Тези IP адреси уникално идентифицират компютрите в Интернет и се използват от рутерите (маршрутиризатор) за пренасочване на индивидуалните дейтаграми по техните направления. Рутерите четат само адреса на диаграмите. Нивото IP не съобщава на изпращаната програма дали дейтаграмата е достигнала успешно. Това е задача за следващото ниво в stack-a. Получаващата програма може да гледа само контролната сума.Протокола
TCP/IP включва и User Datagram Protocol (UDP). Всички транспортни протоколи, базирани на IP съхраняват собствени заглавни блокове в частта за данниTCP
протокола осъществява пълна двупосочна връзка между два компютъра.Winsock
и WinInet скриват голяма част от детайлите на TCP протокола което опростява работата на програмиста.Domain Name System (DNS)
При сърфиране не се пишат
IP адреси а имена. Тези имена се транслират в IP-адреси от DNS обработвайки заявки. Имената започват с един главен домеин. Под главния домеин има серия от домейни на високо ниво, като com, edu, gov и org. (Скивай http://ds.internic.net)HTTP
протокола е изграден на основа на TCP. Една програма прослушва порт 80. В последствие някоя клиентска програма (браузър) се свързва със сървъра (www.slowsoft.com) след като получи координатите му от именния сървър. Заявката към сървъра може да изглежда така:GET /customer/hewproducts/html HTTP/1.0
FTP
протокола обслужва славлянето и качването на файлове от и към сървъра. Дава възможност за работа с директории.За работа в локална мрежа под
Windows 9x инсталирай Microsoft Networks и File And Printer Sharing For Microsoft Networks. Ако имаш инсталирани други протоколи това не пречи. За Windows NT инсталирай Client And Server Services и ги свържи към TCP/IP. Ако искаш компютъра да предоставя ресурси трябва да го споделиш (sharing). Това става с Windows Explorer или нещо от сорта като си избереш цяло устройство или директория с десен бутон на мишката върху името.Най-лесния начин за преобразуване на имена в
IP адреси в локална мрежа е използване на HOST файл. При Windows NT този текстов файл се намира в \Winnt\System32\DRIVERS\ETC, a за Windows 95 e \WINDOWS\HOST.SAM.Тестването на локалната мрежа (интранет) става с командата
Ping. Извиква се от DOS prompt. След Ping се пише IP адреса или името на компютъра, който тестваме.Първия ред на
hosts файла би трябвало да бъде:127.0.0.1 localhost
Ако стартираш сървърна програма, проверяваща този адрес, клиентската програма работещи на същата машина, могат да се свързват към локалния хост и да получават
TCP/IP връзки към сървърната програма. Това е валидно, независимо дали имаш инсталирана мрежова карта.Winsock
е най-ниското ниво на Windows API за TCP/IP програмиране. Част от кода е разположен в winsock32.dll.Accept
- Тази TCP/IP функция извиква Winsock - функцията accept след извикването на Listen. Връща TRUE при успешно свързване.
s |
Псевдоним на съществуващ обект за който не е извиквана Create |
psa |
Обект или указател към променлива от тип sockaddr |
Conect
- Извивка се след Create. Има параметър psa.Send -
Извиква се след select за активиране на time-out. Броя на изпратените байтове зависи от скоростта на връзката. Връща стойността на изпратените байтове.
pch |
Указател към буфер, съдържащ байтовете за изпращане |
nSize |
Размер на блока за изпращане в байтове. |
nSecs |
time-out стойността в секунди |
Write
- Многократно извиква Send докато се изпратят всички байтове или получателя затвори socket-a. Параметрите са същите.Receive -
връща получените байтове.Параметрите са същите.SendDatagram
- UDP функция. Предварително трябва да е извикване Create с параметър SOCK_DGRAM. Параметрите са същите плюс psa - адреса на получателя, променлива от тип sockaddr.ReceiveDatagram
- обратното на горното.GetPeerAddr
- Връща порта и IP адреса на компютъра към който си се вързал. Ако си се вързал към proxy сървър връща адреса на сървъра. Параметъра е psa CSockAddr - обект или указател към променлива тип sockaddr.GetSockAddr -
Връща адреса присвоен на компютъра на който се дава от интернет доставчика. Winsock присвоява и номер на порт. Параметъра е psa.GetNostByName -
Прави запитване към сървъра за имена и връща socket адреса
pshName |
Указател към масив от символи, съдържащ хост-името |
ushPort |
Номер на порт (по подразбиране 0), който ще стане част от върнатия socket адрес |
GetHostByAddr
- Статична функция която прави запитване към сървъра за имена и връща името, съответстващо на адреса. Параметъра е psa.Започва описването на примера ex30a
CHttpBlockingSocket
- Връща байтовете, които са се натрупали на твоя компютър до този момент на връзката.RealHttpHeaderLine -
Тази функция връща един ред от заглавния блок, завършващ с двойката <cr><if>. Връща нула в края на реда. Ако буферът е пълен, терминиращата нула се записва в последната позиция. Връща броя на получените байтове бе нулата. Параметрите са:
pch |
Указател към съществуващ буфер, в който ще се получава реда (завършващ с нула) |
nSize |
Размер на буфера pch |
nSecs |
Time out стойност в скунди |
ReadHttpResponse
- Връща остатъка от отговора на сървъра, получен при затваряне на socket-а или когато буфера е пълен. Не разчитай че буфера има терминираща нула. Параметрите са същите.Опростена
HTTP сървърна програмаИнициализирането на
Winsock става с добавянето в InitInstance тези редове:WSADATA wsd;
WSAStartup (0x101,&wsd);
Сървъра се стартира в отговор на някой действия на потребителя
CBlockingSocket g_sListen;
void CEx30aView::OnInternetStartServer()
{
try {
CSockAddr saServer;
if(g_strIPServer.IsEmpty()) { // first or only IP
saServer = CSockAddr(INADDR_ANY, (USHORT) g_nPortServer);
}
else { // if our computer has multiple IP addresses...
saServer = CSockAddr(g_strIPServer, (USHORT) g_nPortServer);
}
g_sListen.Create();
g_sListen.Bind(saServer);
g_sListen.Listen();// start listening
g_bListening = TRUE;
g_nConnection = 0;
AfxBeginThread(ServerThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);
}
catch(CBlockingSocketException* e) {
g_sListen.Cleanup();
LogBlockingSocketException(GetSafeHwnd(), "VIEW:", e);
e->Delete();
}
}
Функцията създава
socket и започва да го подслушва а след това стартира работна нишка която чака някой клиент да се свържа към порт 80. Ако нещо се издъни се генерира изключение. Глобалния обект g_sListen може да приема множество връзки едновременно, като всяка от тях се обслужва от отделна нишка.UINT ServerThreadProc(LPVOID pParam)
{
CSockAddr saClient;
CHttpBlockingSocket sConnect;
char* buffer = new char[SERVERMAXBUF];
char message[100], headers[500], request1[MAXLINELENGTH], request2[MAXLINELENGTH];
char hdrErr[] = "HTTP/1.0 404 Object Not Found\r\n"
"Server: Inside Visual C++ SOCK01\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 66\r\n\r\n" // WinInet wants correct length
"<html><h1><body>HTTP/1.0 404 Object Not Found</h1></body></html>\r\n";
char hdrFmt[] = "HTTP/1.0 200 OK\r\n"
"Server: Inside Visual C++ EX30A\r\n"
"Date: %s\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %d\r\n";
char html1[] = "<html><head><title>Inside Visual C++ \
Server</title></head>\r\n"
"<body><body background=\"/samples/images/usa1.jpg\">\r\n"
"<h1><center>This is a custom home page</center></h1><p>\r\n"
"<a href=\"/samples/iisdocs.htm\">Click here for iisdocs.htm.</a><p>\r\n"
"<a href=\"/samples/disclaim.htm\">Click here for disclaim.htm.</a><p>\r\n";
// custom message goes here
char html2[] = "</body></html>\r\n\r\n";
CString strGmtNow = CTime::GetCurrentTime().FormatGmt("%a, %d %b %Y %H:%M:%S GMT");
int nBytesSent = 0;
CFile* pFile = NULL;
try {
if(!g_sListen.Accept(sConnect, saClient)) {
// view or application closed the listing socket
g_bListening = FALSE;
delete [] buffer;
return 0;
}
g_nConnection++;
AfxBeginThread(ServerThreadProc, pParam, THREAD_PRIORITY_NORMAL);
// read request from client
sConnect.ReadHttpHeaderLine(request1, MAXLINELENGTH, 10);
LogRequest(pParam, request1, saClient);
char* pToken1; char* pToken2;
if(Parse(request1, &pToken1, &pToken2)) {
if(!stricmp(pToken1, "GET")) {
do { // eat the remaining headers
sConnect.ReadHttpHeaderLine(request2, MAXLINELENGTH, 10);
TRACE("SERVER: %s", request2);
}
while(strcmp(request2, "\r\n"));
if(!stricmp(pToken2, "/custom")) { // special request
// send a "custom" HTML page
wsprintf(message, "Hi! you are connection #%d on IP %s, port %d<p>%s",
g_nConnection, saClient.DottedDecimal(), saClient.Port(), strGmtNow);
wsprintf(headers, hdrFmt, (const char*) strGmtNow, strlen(html1)
+ strlen(message) + strlen(html2));
// no If-Modified
strcat(headers, "\r\n"); // blank line
sConnect.Write(headers, strlen(headers), 10);
sConnect.Write(html1, strlen(html1), 10);
sConnect.Write(message, strlen(message), 10);
sConnect.Write(html2, strlen(html2), 10);
}
else if(strchr(pToken2, '?')) { // CGI request
// Netscape doesn't pass function name in a GET
TRACE("SERVER: CGI request detected %s\n", pToken2);
// could load and run the ISAPI DLL here
}
else { // must be a file
// assumme this program has already set the default WWW directory
if((pFile = OpenFile(pToken2)) != NULL) {
CFileStatus fileStatus;
pFile->GetStatus(fileStatus);
CString strGmtMod = fileStatus.m_mtime.FormatGmt("%a, %d %b %Y %H:%M:%S GMT");
char hdrModified[50];
wsprintf(hdrModified, "Last-Modified: %s\r\n\r\n", (const char*) strGmtMod);
DWORD dwLength = pFile->GetLength();
// Date: , Content-Length:
wsprintf(headers, hdrFmt, (const char*) strGmtNow, dwLength);
strcat(headers, hdrModified);
nBytesSent = sConnect.Write(headers, strlen(headers), 10);
TRACE("SERVER: header characters sent = %d\n", nBytesSent);
// would be a good idea to send the file only if the If-Modified-Since date
// were less than the file's m_mtime
nBytesSent = 0;
#ifdef USE_TRANSMITFILE
if(::TransmitFile(sConnect, (HANDLE) pFile->m_hFile, dwLength,
0, NULL, NULL, TF_DISCONNECT)) {
nBytesSent = (int) dwLength;
}
#else
DWORD dwBytesRead = 0;
UINT uBytesToRead;
// send file in small chunks (5K) to avoid big memory alloc overhead
while(dwBytesRead < dwLength) {
uBytesToRead = min(SERVERMAXBUF, dwLength - dwBytesRead);
VERIFY(pFile->Read(buffer, uBytesToRead) == uBytesToRead);
nBytesSent += sConnect.Write(buffer, uBytesToRead, 10);
dwBytesRead += uBytesToRead;
}
#endif
TRACE("SERVER: full file sent successfully\n");
}
else {
nBytesSent = sConnect.Write(hdrErr, strlen(hdrErr), 10); // 404 Object Not Found
}
}
}
else if(!stricmp(pToken1, "POST")) {
do { // eat the remaining headers thru blank line
sConnect.ReadHttpHeaderLine(request2, MAXLINELENGTH, 10);
TRACE("SERVER: POST %s", request2);
}
while(strcmp(request2, "\r\n"));
// read the data line sent by the client
sConnect.ReadHttpHeaderLine(request2, MAXLINELENGTH, 10);
TRACE("SERVER: POST PARAMETERS = %s\n", request2);
LogRequest(pParam, request2, saClient);
// launch ISAPI DLL here?
nBytesSent = sConnect.Write(hdrErr, strlen(hdrErr), 10); // 404 error for now
}
else {
TRACE("SERVER: %s (not a GET or POST)\n", pToken1);
// don't know how to eat the headers
}
}
else {
TRACE("SERVER: bad request\n");
}
sConnect.Close(); // destructor can't close it
}
catch(CBlockingSocketException* pe) {
LogBlockingSocketException(pParam, "SERVER:", pe);
pe->Delete();
}
TRACE("SERVER: file characters sent = %d\n", nBytesSent);
delete [] buffer;
if(pFile) delete pFile;
return 0;
}
След извикването на
Accept нишката се блокира, докато клиентът не се свърже с порт 80, тогава Accept връща новия socket - sConnect. Текущата нишка веднага стартира друга нишка и обработва клиентската заявка дошла от sConnect. Първо прочита всички заглавни блокове (request headers), като извиква ReadHttpHeaderLine, докато не засече празен ред. Тогава извиква Write за да изпрати заглавните блокове на отговора (responceheaders) и HTML- изразите. Накрая текущата нишка извиква Close, за да затвори socket. Това е края на връзката. Следващата нишка стои блокирана от извикването на Accept, очаквайки следващата връзка.За да се избегне изтичане на памет при излизане, трябва да се подсигури приключването на всички работни нишки. Най-лесно става като се затвори подслушвания
socket. Тогава Accept на всяка нишка връща FALSE, предизвиквайки приключването на нишката.try {
p_sListen.Close();
Sleep (300);
WSACleanup();
}
catch (SUserException* e){
e->Delete();
}
Опростена
HTTP клиентска програмаКогато към един сървър получи GET заявка с наклонена черта, той би трябвало да подаде своя подразбиращ се HTML файл.
GET /HTTP/1.0
Ако напишеш
http://www.slowsoft.com в браузър, той изпраща сляпа GET заявка. Тази клиентска програма може да използва същия клас CHttpBlockingSocket и трябва да инициализира Winsock по същия начин както и сървъра. Една функция за обработка на команди стартира клиентска нишка с извикване като следното:AfxBeginThread (ClientStocketThreadProc, GetSafeHwnd();
Това е кода за клиентската нишка:
CString g_strServerName = "localhost"; // used by both winsock and wininet
UINT ClientSocketThreadProc(LPVOID pParam)
{
// sends a blind request, followed by a request for a specific URL
CHttpBlockingSocket sClient;
char* buffer = new char[MAXBUF];
int nBytesReceived = 0;
// We're doing a blind GET, but we must provide server name if we're using a proxy.
// A blind GET is supposed to retrieve the server's default HTML document.
// Some servers don't have a default document but return a document name in the Location header.
char request[] = "GET %s%s%s HTTP/1.0\r\n";
char headers[] =
"User-Agent: Mozilla/1.22 (Windows; U; 32bit)\r\n"
"Accept: */*\r\n"
"Accept: image/gif\r\n"
"Accept: image/x-xbitmap\r\n"
"Accept: image/jpeg\r\n"
// following line tests server's ability to not send the URL
// "If-Modified-Since: Wed, 11 Sep 1996 20:23:04 GMT\r\n"
"\r\n"; // need this
CSockAddr saServer, saPeer, saTest, saClient;
try {
sClient.Create();
if(!g_strIPClient.IsEmpty()) {
// won't work if network is assigning us our IP address
// good only for intranets where client computer has several IP addresses
saClient = CSockAddr(g_strIPClient);
sClient.Bind(saClient);
}
if(g_bUseProxy) {
saServer = CBlockingSocket::GetHostByName(g_strProxy, 80);
}
else {
if(g_strServerIP.IsEmpty()) {
saServer = CBlockingSocket::GetHostByName(g_strServerName, g_nPort);
}
else {
saServer = CSockAddr(g_strServerIP, g_nPort);
}
}
sClient.Connect(saServer);
sClient.GetSockAddr(saTest);
TRACE("SOCK CLIENT: GetSockAddr = %s, %d\n", saTest.DottedDecimal(), saTest.Port());
if(g_bUseProxy) {
wsprintf(buffer, request, "http://" , (const char*) g_strServerName, g_strFile);
}
else {
wsprintf(buffer, request, "", "", g_strFile);
}
sClient.Write(buffer, strlen(buffer), 10);
sClient.Write(headers, strlen(headers), 10);
// read all the server's response headers
do {
nBytesReceived = sClient.ReadHttpHeaderLine(buffer, MAXBUF, 10);
TRACE("SOCK CLIENT: %s", buffer);
} while(strcmp(buffer, "\r\n"));
// read the server's file
nBytesReceived = sClient.ReadHttpResponse(buffer, MAXBUF, 10);
TRACE("SOCK CLIENT: bytes received = %d\n", nBytesReceived);
if(nBytesReceived == 0) {
AfxMessageBox("No response recevied. Bad URL?");
}
else {
buffer[nBytesReceived] = '\0';
::MessageBox(::GetTopWindow(::GetDesktopWindow()), buffer, "WINSOCK CLIENT", MB_OK);
}
// could do another request on sClient by calling Close, then Create, etc.
}
catch(CBlockingSocketException* e) {
LogBlockingSocketException(pParam, "CLIENT:", e);
e->Delete();
}
sClient.Close();
delete [] buffer;
return 0;
}
Тази нишка първо извиква
CBlockingSecket::GetHostByName, за да получи IP адреса на сървъра. След това създава socket и извиква Connect за този socket. Сега има двупосочен комуникационен канал към сървъра. Нишката изпраща своята GET- заявка, следвана от заглавни блокове на заявката, прочита заглавните блокове на отговора на сървъра и след това прочита самия файл на отговора, който се предполага, че е текстов файл. След извеждането на текста в диалог за съобщение, нишката излиза.Архитектура на сървъра
EX30A.Примера комбинира
HTTP сървър, Winsock HTTP клиент и два WinInet HTTP клиента. И трите клиента мога да общуват с вградения сървър, как и с всеки друг сървър в интернет. Всяка клиентска програма Telnet Internet Explorer могат да комуникират с EX30A. Това е стандартно MFC SDI приложение използващо архитектурата документ-изглед, като класът на изгледа е производен на CEditViewФункцията на командата
Start Server и Stop Server стартира подслушване на глобалния socket и след това пуска една нишка, както при опростения HTTP- сървър. Основната задача на сървъра е да доставя файлове. Той първо отваря файла, съхранявайки CFile указател в pFile и след това прочита блоковете от 5K (SERVERMAXBUF) и ги записва в sConnect.char* buffer = new char[SERVERMAXBUF];
DWORD dwLength = pFile->GetLength();
DWORD dwBytesRead = 0;
UINT uBytesToRead;
// send file in small chunks (5K) to avoid big memory alloc overhead
while(dwBytesRead < dwLength) {
uBytesToRead = min(SERVERMAXBUF, dwLength - dwBytesRead);
VERIFY(pFile->Read(buffer, uBytesToRead) == uBytesToRead);
nBytesSent += sConnect.Write(buffer, uBytesToRead, 10);
dwBytesRead += uBytesToRead;
}
Тестване на
EX30AДиректорията
Website се настройва като home директория на сървъра EX30AСтартирай програмата от дебъгера и след това избери
Start Server от менюто Internet. Сега върви Web-баузъра и въведи locallhost. Би трябвало да видиш подразбиращата се страница на IIS. Прозореца на EX03A трябва да показва съобщения.Инструмента
Telnet.EXE е полезен за тестване на сървърни програми. При първото му стартиране избери Terminal - Preferences и включи Local Echo. При всяко стартиране избирай Connect -> Remote System. Въвеждаш номера на порта и името на сървъра. Напиши GET C:\NAME.HTM HTTP/1.0Winsock
клиента ЕХ30АПримера имплементира
Winsock клиент във файла ClientsockThread.cpp. Клиентската нишка използва глобални променливи, зададени от диалога Configuration включващи има на файл на сървъра, хост име на сървъра, IP адреса и порт на сървъра и клиента. При стартиране на клиента, той се свързва към указания сървър и издава GET заявка за указания файл. Winsock клиента регистрира грешките за съобщения в главния прозорец на ex30a.Ако компютъра ти е свързан към локална мрежа връзката му с интернет става чрез
proxy сървър или сървър пълномощник (firewall). Има два типа proxy сървъри, Web и Winsock. Web сървърите поддържат протоколите HTTP, Gropher и FTP. Winsock протоколите позволяват поддържането и на RealAudio протокол.Пример ЕХ30А може да комуникира през
Web proxy, ако включиш опцията Use Proxy обаче трябва да въведеш името на proxy сървъра. Всички GET и POST заявки трябва да указват пълния Uniform Resource Location (URL). Ако си свързан към сървъра SlowSoft, заявката може да изглежда така:GET /sutomer/newproducts.http HTTP/1.0
Обаче ако си свързан през
Web proxy:GET http://slowsoft.com/customer/newproducts.http HTTP/1.0
Най-лесния начин за тестван на
Winsock-клиент е използването на вградения Winsock-сървър. Стартиран програмата и избери Request (Winsock) от менюто Internet.WinInet
е API от по-високо ниво но работи само за клиентски програми.MFC WinInet-
класовеCinternetSesion
- Осъществява достъп до интернет по HTTP, FTP или gopher - връзки, или може да отварящ отдалечени файлове директно с функцията OpenURL.CHttpConnection - Поддържа постоянна връзка HTTP към определен хост. Когато имаш CinternetSesion обект може да извикваш GetHttpConnection която връща указател към CHttpConnmection - обект. След приключване на работа освободи обекта!
CFtpConnection, CGopherConnection
- Подобни са на горния клас. Член функциите GetFile и PutFile правят трансфер на файлове от и към твоя диск.CInternetFile
- При HTTP, FTP или gopher клиентските програми четат и записват потоци от байтове. MFC WinInet - класовете правят тези потоци байтове да изглеждат като обикновени файлове. Ако имаш обикновен CFile обект, той има 32 битова член променлива от тип HADNLE, която представлява файла от диска. CInternetFile използва същата член променлива m_hFile, но тя съдържа 32 битов манипулатор на Интернет-файл от типа HINTERNET, който не е взаимозаменяем с HADNLE. Предифинираните член-функции на CInternetFile използват този манипулатор за извикването на WinInet-функции, като InternetReadFile и InternetWriteFile.CHttpFile -
В този клас функциите AddRequestHeaders, SendRequest и SetFileURL, са уникални за HTTP файлове. Не трябва да създаваш CHttpFile обект, а да извикаш CHttpConnection::OpenRewquest, коят извикват HttpOpenRequest и връща CHttpFile - указател. Може да укажеш GET или POST заявка за това извикване.CFtpFileFind, CGopherFileFind
- позволяват на клиентската програма да разглежда директории.Опростена
WinInet клиентска програмаТази функция се стартира от функцията за обработка на команди от главната нишка:й:
AfxBeginThread(ClientUrlThreadProc, GetSafeHwnd());
Ето и кода на клиентската нишка:
CString g_strServerName = "localhost"; // used by both winsock and wininet
UINT ClientWinInetThreadProc(LPVOID pParam)
{
CCallbackInternetSession session;
CHttpConnection* pConnection = NULL;
CHttpFile* pFile1 = NULL;
char* buffer = new char[MAXBUF];
UINT nBytesRead = 0;
DWORD dwStatus;
try {
// username/password doesn't work yet
if(!g_strServerName.IsEmpty()) {
pConnection = session.GetHttpConnection(g_strServerName,
(INTERNET_PORT) g_nPort);
}
else {
pConnection = session.GetHttpConnection(g_strServerIP,
(INTERNET_PORT) g_nPort);
}
pFile1 = pConnection->OpenRequest(1, g_strFile, NULL, 1, NULL, NULL, // GET request
INTERNET_FLAG_KEEP_CONNECTION); // needed for NT Challenge/Response authentication
// INTERNET_FLAG_RELOAD forces reload from the server (bypasses client's cache)
pFile1->SendRequest();
pFile1->QueryInfoStatusCode(dwStatus);
TRACE("QueryInfoStatusCode = %d\n", dwStatus);
nBytesRead = pFile1->Read(buffer, MAXBUF - 1);
buffer[nBytesRead] = '\0'; // necessary for message box
char temp[100];
if(pFile1->Read(temp, 100) != 0) { // makes caching work if read complete
AfxMessageBox("File overran buffer -- not cached");
}
::MessageBox(::GetTopWindow(::GetDesktopWindow()), buffer, "WININET CLIENT", MB_OK);
// could use existing pFile1 to SendRequest again if we wanted to
}
catch(CInternetException* e) {
LogInternetException(pParam, e);
e->Delete();
}
// could call OpenRequest again on same connection if we wanted to
if(pFile1) delete pFile1; // does the close -- prints a warning
if(pConnection) delete pConnection; // why does it print a warning?
delete [] buffer;
g_csStatus.Lock();
strcpy(g_pchStatus, ""); // problem with empty string. bug #9897
g_csStatus.Unlock();
return 0;
}
ISAPI
сървърни разширенияТова са програми които се стартират в отговора на
GET или POST заявки от клиентска програма (браузър). Стандартът Common Gateway Interface (CGI), който е част от HTTP, възниква като начин за браузърните програми да взаимодействат със скриптове или отделни изпълними програми, работещи на сървъра.CGI прехвърля програмната тежест върху сървъра. Използвайки
CGI параметри браузъдът изпраща малки количества информация към сървърния компютър, а сървърът може да прави абсолютно всичко с тази информация, включително да осъществи достъп до БД, да генерира изображения и да управлява периферни устройства. Сървърът изпраща обратно файл на браузъра.Представи си, че
HTML файла съдържа:<a href="scripts/maps.dll?State=idaho">idaho Weather Map</a><p>
Когато потребителя щракне върху връзката браузъра изпраща към сървъра една
CGI GET заявка като тази:GET scripts/maps.dll?State=idaho HTTP/1.0
Тогава
IIS зарежда maps.dll от своята виртуална директория за скриптове, извиква подразбираща се функция (често именувана Default), и й подава Idaho чрез параметъра State. Динамичната библиотека след това започва работа, генерирайки JPG-файла, съдържащ най-новата сателитна метеорологична карта за времето в щата Ajdako, и изпраща на клиента.Ако
maps.dll има повече от една функция, етикетът може да указва името на функцията по следния начин:<a href="scripts/maps.dll?GetMap?State=Idaho&Res=5">Idaho Weather Map </a><p>
В този случай функцията
GetMap се извиква с два параметъра - State и Res.Ето и един
HTML код:<html>
<head><title>Weathermap HTML</title>
</head>
<body>
<h1><center>Welcome to the Satellite Weathermap Service<center></h1>
<form action="scripts/maps.dll?GetMap " method=GET>
<p>Select your state:
<select name="State">
<option>Alabama
<option>Alaska
<option>Idaho
<option>Washington
</select>
<p><input type="submit"><input type="reset">
</form>
При натискане на бутона
submit към сървъра се изпраща следната заявка:GET scripts/maps.dll?GetMap?State=Idaho
(
различни заглавни блокове на заявката)(празен ред)
Ако напишеш method = POST тогава заявката е:
POST scripts/maps.dll?GetMap
(различни заглавни блокове)
(празен ред)
State = Idaho
Писане на
ISAPI сървърно DLL разширениеИзбери
ISAPI Extension Wizard от списъка Projects. Избери опцията Generate A Server Extension Object.Напиши
ISAPI-функции като членове на производния на CHttpServer клас и трябва да напишеш parse map макроси. Може да използваш функцията Default, но се нуждаеш от функцията GetMap: Първо вмъкни (не е казано къде)ON_PARSE_COMMAND(Getmap.CweatherExtension, ITS_STR)
ON_PARSE_COMMAND_PARAMS ("State")
След това напиши
void CWeatherExtension::GetMap(CHttpServerContext *pCtxt, LPCTSTR pstrState)
{
StartContent(pCtxt);
WriteTitle (pCtxt);
*pCtxt << "Visualiza a weather map for the state of";
*pCtxt << pstrState;
EndContent (pCtxt);
} Extension::GetMap(CHttpServerContext *pCtxt, LPCTSTR pstrState)
{
StartContent(pCtxt);
WriteTitle (pCtxt);
*pCtxt << "Visualiza a weather map for the state of";
*pCtxt << pstrState;
EndContent (pCtxt);
}
Погледни примера
ex31a.htm.<body>
<h1><center>Welcome to CyberPizza<center></h1>
<p>Enter your order:</p>
<form action="debug/ex31a.dll?ProcessPizzaForm" method=POST>
<table>
<tr><td><p align = right>
Your name: <input type = "text" name = "name" value = ""><br>
Your Address: <input type = "text" name = "address" value = ""><br>
Number of Pies: <input type = "text" name = "quantity" value = 1>
</td></tr>
</table>
<p>Pizza Size: </P>
<menu>
<li><input type="radio" name = "size" value = 8> 8 - inch
<li><input type="radio" name = "size" value = 10> 10 - inch
<li><input type="radio" name = "size" value = 12 checked> 12 - inch
<li><input type="radio" name = "size" value = 14> 14 - inch
</menu>
<p> Toppings: </p>
<input type = "checkbox" name = "top1" value = "Pepperoni" checked> Pepperoni
<input type = "checkbox" name = "top2" value = "Onions"> Onions
<input type = "checkbox" name = "top3" value = "Pepperoni"> Mushrooms
<input type = "checkbox" name = "top4" value = "Pepperoni"> Sausage
<p><input type = "submit" value = "Submit Order Now"> <input type = "reset">
При натискане на бутона
Submit Order Now ето какво получава сървъра:POST debug/ex31a.dll?ProcessPizzaForm HTTP/1.0
(заглавни блокове на заявката)
(празен ред)
name = Waiter+Sulivan&addres=Readmond%2C+WA&quantity=2&size=12&top=1Pepperoni&top3=Mushrooms
Някой си Уолтър Съливан си е поръчал две 12 инчови пици пеперони и с гъби. Браузърът вмъква + вместо интервали, %2С заметва запетая, а & е разделител на параметри. Вписванията в
parse картата от ex31a.cpp са:ON_PARSE_COMMAND (pProcessPizzaForm, CEx31aExtension, ITC_PSTR ITS_PSTR ITS_I4 ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS ("name address quantity size top1=~top2=~top3=~top4=~)
DLL
- функцията ProcessPizzaForm прочита стойността на параметрите и изготвя един HTML формуляр за потвърждение, който се изпраща на потребителя. Ето кода.void CEx31aExtension::ProcessPizzaForm(CHttpServerContext* pCtxt, LPCTSTR pstrName,
LPCTSTR pstrAddr, int nQuantity, LPCTSTR pstrSize,
LPCTSTR pstrTop1, LPCTSTR pstrTop2, LPCTSTR pstrTop3, LPCTSTR pstrTop4)
{
StartContent(pCtxt);
WriteTitle(pCtxt);
if((strlen(pstrName) > 0) && (strlen(pstrAddr) > 0)) {
*pCtxt << " Your pizza order is as follows:";
*pCtxt << "<p>Name: " << pstrName;
*pCtxt << "<p>Address: " << pstrAddr;
*pCtxt << "<p>Number of pies: " << (long int) nQuantity;
*pCtxt << "<p>Size: " << pstrSize;
*pCtxt << "<p>Toppings: " << pstrTop1 << " " << pstrTop2 << " " << pstrTop3 << " " << pstrTop4;
*pCtxt << "<p>The total cost is $23.49, including delivery.";
*pCtxt << "<form action=\"ex31a.dll?ConfirmOrder\" method=POST>";
*pCtxt << "<p><input type=\"hidden\" name=\"name\" value=\"" << pstrName << "\">"; // xref to original order
*pCtxt << "<p><input type=\"submit\" value=\"Confirm and charge my credit card\">";
*pCtxt << "</form>";
// store this order in a disk file or database, referenced by name
}
else {
*pCtxt << " You forgot to enter your name or address. Back up and try again.";
}
EndContent(pCtxt);
}
Има и втора стъпка ама при положение, че първата не действа...
void CEx31aExtension::ConfirmOrder(CHttpServerContext* pCtxt, LPCTSTR pstrName)
{
StartContent(pCtxt);
WriteTitle(pCtxt);
*pCtxt << "<p>Our courteous delivery person will arrive within 30 minutes.";
*pCtxt << "<p>Thank you, " << pstrName << ", for using CyberPizza.";
// now retrieve the order from disk by name, then make the pizza
// be prepared to delete the order after a while if the customer doesn't confirm
m_cs.Lock(); // gotta be threadsafe
long int nTotal = ++m_nTotalPizzaOrders;
m_cs.Unlock();
*pCtxt << "<p>Total pizza orders = " << nTotal;
EndContent(pCtxt);
}
Името на купувача се връща в параметъра
pstrName, който трябва да използваш, за да извлечеш оригиналната поръчка от диска.Изграждане и тестване на
ex31a.dllЕ да ама е дадено само за
Windows NT.Най-простия начин за ограничаване достъпа до сървъра е включването на елементарно удостоверяване (
basic authentication). Тогава ако някой клиент направи анонимна заявка, сървърът изпраща обратно отговора.HTTP/1.0.401Unauthorized
Заедно със заглавен отговор като следния:
WWW-Authertificate: Basic realm = "xxxx"
Писане на
DLL за ISAPI-филтърЕдно ISAPI сървърно DLL разширение се зарежда при първата поява на GET или POST заявка. Един ISAPI DLL филтър се зарежда (на базата на информацията в Registriy), когато се стартира WWW услуга. След това филтъра е в цикъла на всички HTTP заявки. Може да се четат и променят всички данни постъпващи или излизащи от сървъра.
Започни създаването на нов проект като избереш ISAPI Extension Wizard. Избери Generate A Filter Object. Диалога Step 2 го остави по подразбиране. Списъкът с опции под "Wich Notifications Will Your Filter Process?" се отнася до седем места, където твоя филтър може да получи контрол по време на обработване на HTTP заявката.
Има два класа за ISAPI филтри - CHttpFilter и CHttpFilterContext.
Филтърът не може да извежда транзакциите директно. Трябва отделна програма, която да извежда текста в прозорец
. Следващата помощна функция изпраща съобщението WM_SENDTEXT към извеждащата програма.void CEx31bFilter::SendTextToWindow(char* pchData)
{
if(m_hProcessDest != NULL) {
int nSize = strlen(pchData) + 1;
HANDLE hMMFReceiver;
HANDLE hMMF = ::CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL,
PAGE_READWRITE, 0, nSize, NULL);
ASSERT(hMMF != NULL);
LPVOID lpvFile = ::MapViewOfFile(hMMF, FILE_MAP_WRITE, 0, 0, nSize);
ASSERT(lpvFile != NULL);
memcpy((char*) lpvFile, pchData, nSize);
::DuplicateHandle(::GetCurrentProcess(), hMMF, m_hProcessDest,
&hMMFReceiver, 0, FALSE, DUPLICATE_SAME_ACCESS |
DUPLICATE_CLOSE_SOURCE);
::PostMessage(m_hWndDest, WM_SENDTEXT, (WPARAM) 0,
(LPARAM) hMMFReceiver);
::UnmapViewOfFile(lpvFile);
}
}
Функцията
DuplicateHandle прави копие на манипулатора на EX31B, което тя изпраща на програмата E31C чрез параметъра на съобщението. Идентификатора на прозореца определян в GetFilterVersion, е необходим за извикването на DuplicateHandle. Следващата функциоя извиква SendTextToWindow.DWORD CEx31bFilter::OnReadRawData(CHttpFilterContext* pCtxt,
PHTTP_FILTER_RAW_DATA pRawData)
{
TRACE("CEx31bFilter::OnReadRawData\n");
// sends time/date, from IP, to IP, request data to a window
char pchVar[50] = "";
char pchOut[2000];
DWORD dwSize = 50;
BOOL bRet;
CString strGmt = CTime::GetCurrentTime().FormatGmt("%m/%d/%y %H:%M:%S \
GMT");
strcpy(pchOut, strGmt);
bRet = pCtxt->GetServerVariable("REMOTE_ADDR", pchVar, &dwSize);
if(bRet && dwSize > 1) {
strcat(pchOut, ", From ");
strcat(pchOut, pchVar);
}
bRet = pCtxt->GetServerVariable("SERVER_NAME", pchVar, &dwSize);
if(bRet && dwSize > 1) {
strcat(pchOut, ", To ");
strcat(pchOut, pchVar);
}
strcat(pchOut, "\r\n");
int nLength = strlen(pchOut);
// Raw data is not zero-terminated
strncat(pchOut, (const char*) pRawData->pvInData, pRawData->cbInData);
nLength += pRawData->cbInData;
pchOut[nLength] = '\0';
SendTextToWindow(pchOut);
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
Извеждащата програма
EX31C.EXE е стандартна CRichEditView програма с функцията за обработка на WM_SENDTEXT в главната рамка:LONG CMainFrame::OnSendText(UINT wParam, LONG lParam)
{
TRACE("CMainFrame::OnSendText\n");
LPVOID lpvFile = ::MapViewOfFile((HANDLE) lParam, FILE_MAP_READ, 0, 0,
0);
GetActiveView()->SendMessage(EM_SETSEL, (WPARAM) 999999, 1000000);
GetActiveView()->SendMessage(EM_REPLACESEL, (WPARAM) 0,
(LPARAM) lpvFile);
::UnmapViewOfFile(lpvFile);
::CloseHandle((HANDLE) lParam);
return 0;
}
Тази функция предава текста към изгледа. Класът
CMainFrame предифинира OnUdateFrameTitle, за да елимиинира името на документа от заглавието на прозорец. Това гарантира, че DLL-ът ще може да намери прозореца на EX31X по име.Класът на изгледа асоциира съобщението
WM_RBUTTONDOWN, за да имплементира контекстно меню за изтриване на текста от изгледа. Прозорците от тип "rich edit view" не поддържат съобщението WM_CONTEXTMENU.Тестване на
EX31BИзгради проекта
EX31B и стартирай EX31C.EXE. За да укажеш зареждането на нов филтърен DLL, трябва ръчно да промениш Registry. Стартирай Regedit и щракни два пъти върху Filter DLLs от \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3VC\Parameters. Добави пълния път до DLL-файла отделяки го от другите имена със запетая.Трябва да промениш режима на
IIS, за да позволиш услугата да взаимодейства с извеждащата програма EX31C. Това се прати от иконата Services от Control Panel, щракни двукратно върху World Wide Web Publishing Service и маркирай Allow Service To Interact With Desktop. Накрая използвай Internet Service Manager, за да спреш и рестартираш WWW - услугат, за да се зареди филтърния DLL. Сега ако използваш браузъра за гледане на страници в прозореца на EX31C ще се появят командите.Active X
документни сървъри и интернетПредставлява специален файл, който може да свалиш от
Web сървър. Когато браузъра попадне на Active X доимумент, той автоматично зарежда съответната програма, която е Active X документ от твоя твърд диск. Microsoft Office Binder също стартира сървърни програми на Active X документи.Примерът
EX32AВключва два броя диалогови панели. Един за прозорец на главната рамка и друг за активирания на местото прозорец-рамка. И двата са прикрепени към един и съш ресурсен шаблон
IDD_DIALOGBAR, който съдържа един edit-контрол за въвеждане на URL адрес на текстов файл, плюс бутони start и stop, който показвали червен и зелен bit map. Зеления се обработва от функцията OnStart на клас CE32aView ще стартираш нишка, която прочита текстовия файл ред по ред. Кодът на нишката от файла UrlThread.cpp е показан по-долуCString g_strURL = "http://";
volatile BOOL g_bThreadStarted = FALSE;
CEvent g_eKill;
UINT UrlThreadProc(LPVOID pParam)
{
g_bThreadStarted = TRUE;
CString strLine;
CInternetSession session;
CStdioFile* pFile1 = NULL;
try {
pFile1 = session.OpenURL(g_strURL, 0, INTERNET_FLAG_TRANSFER_BINARY |
INTERNET_FLAG_KEEP_CONNECTION); // needed for Windows NT c/r authentication
// Keep displaying text from the URL until the Kill event is received
while(::WaitForSingleObject(g_eKill.m_hObject, 0) != WAIT_OBJECT_0) {
// one line at a time
if(pFile1->ReadString(strLine) == FALSE) break;
strLine += '\n';
::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 999999, 1000000);
::SendMessage((HWND) pParam, EM_REPLACESEL, (WPARAM) 0,
(LPARAM) (const char*) strLine);
Sleep(250); // Deliberately slow down the transfer
}
}
catch(CInternetException* e) {
LogInternetException(pParam, e);
e->Delete();
}
if(pFile1 != NULL) delete pFile1; // closes the file -- prints a warning
g_bThreadStarted = FALSE;
// Post any message to update the toolbar buttons
::PostMessage((HWND) pParam, EM_SETSEL, (WPARAM) 999999, 1000000);
TRACE("Post thread exiting normally\n");
return 0;
}
Този код използва
CStdioFile- указател към PFile1, връщан от OpenURL.. Функцията ReadString чете ред по ред, като всеки ред се изпраща към "rich edit view" - прозореца. Когато главната нишка вдине "kill" събитието (червения бутон), URL нишката излиза.Зареждането на
bit map става с:m_bitmapGreen.LoadBitmap(IDB_GREEN);
HBITMAP hBitmap = (HBITMAP) m_bitmapGreen.GetSafeHandle();
((CButton*) m_wndDialogBar.GetDlgItem(IDC_START))->SetBitmap(hBitmap);
m_bitmapRed.LoadBitmap(IDB_RED);
hBitmap = (HBITMAP) m_bitmapRed.GetSafeHandle();
((CButton*) m_wndDialogBar.GetDlgItem(IDC_STOP))->SetBitmap(hBitmap);
Преди да тестваш ЕХ32А се убеди, че сървъра (ЕХ30А) работи и че имаш текстов файл в
home директорията. Тествай ЕХ32А първо в самостоятелен режим, като въведеш URL на текстов файл. След това стартирай програмата от IE в с ървърен режим. Въведи. Въведи test32a в полето Address на IE.Пример
EX32B документен сървърТози пример може да проверява стойността на въведеното в полето. Появява се формуляр за въвеждане на работното време на служителите. Изглежда като обикновен
HTML формуляр.Изгради проекта и го стартирай в самостоятелен режим, за да го регистрираш и запишеш като документен файл с името
test32. Въведи пътя към библиотеката EX31A. Стартирай някой сървър. ЕХ32В трябва да тръгне в прозореца Browse и трябва да може да се въвеждат транзакции за работното време.