Помощ за VC++ 5.0 / Help for VC++ 5.0/P>

AppWizard генерира кода за едно MFC приложение. Това работещо приложение показва един празен прозорец с прикачено меню. В последствие в него ще добавим код за чертане. За да се изгради приложението следвай тези стъпки:

1. Стартирай AppWizard за да генерира изходен код за SDI приложение.

Избери New от менюто File на Developer Studio и превключи на страницата Projects.

stroiartelka.ru

Избери опцията 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_ABOUTBOX

4. Прекомпилирай проекта

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 на CRect

CWnd::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. Създай EX04A

2. Добави променливите m_rectEllipse и m_nColor към CEX04A .

Избери страницата ClassView от прозореца Workspace и натисни левия бутон на мишката върху CEX04Aview. Избери AddMember Variable и въведи следните променливи

private
Crect m_rectEllipse:
int m_nColor;

Този код може да се напише в декларацията на класа във файла EX04AView.h

3. Използвай 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.cpp

CEX04AView::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 осите Х и У могат да се променят независимо една от друга. Ето пример за елипса която точно се побира в прозореца:

//Това се преписва във функцията OnDraww

CRect 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:

Други съобщения в Windows

WM_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 чрез указател.

Състояние на контекста на устройството

Контекстът е необходим за чертане. Вида на начертаното и печатаното зависи от състоянието на контекста на устройството. Това състояние включва следното:

- Прикачени GDI обекти за чертане, като писалки, четки и шрифтове;

- Режимът на съпоставяне, който определя мащаба на елементите при тяхното изчертаване;

- Различни детайлно, като параметри за подравняване на текста и режими за запълване на многоъгълници.

Класът 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.

Та създай проекта а в последствие и функцията OnPrepareDC

void 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

Натисни десния бутон на мишката върху новия диалог и избери Properties. Ако левия бутон е натиснат, Dialog Properties стои на преден план. Бутона Toggle Grid показва или скрива мрежата в диалоговия прозорец. Уголеми диалога на 10 – 15 сантиметра.

Задай стил на диалога: Stile – Popup, Border – Thin

Добави контролите в диалога посредством палитрата

Контрола за “статичен текст отпечатва символи на екрана.

Edit контрола - трябва да се промени идентификатора му. Избери properties с десен бутон на мишката и промени на IDC_EDIT1 на IDC_NAME.

Прибави още една Edit контрола като нейното ID направи ID_SSN. По-късно с Class Wizard ще го направиш числово поле.

Edit контрола Bio (биография) е многоредово текстово поле след това промени характеристиките му:

Group box контрола Category (категория) групира два радио бутона. Въведи Category за заглавие.

Сложи два радио бутона Hourly (почасово) и Salary (на постоянна заплата) в групата Category. Преименувай идентификатора на бутона Hourly в IDC_CAT и задай останалите му характеристики:

Характеристиките на Salary са:

Групата Insurance (застраховка). Този контрол съдържа три полета за отметки (check box). Въведи заглавието Insurance. Постави три check box контроли в групата. Характеристиките са по подразбиране. Смени идентификаторите на: IDC_LIFE, IDC_DIS и IDC_MED.

Постави Combo box контролът Skill (умения). Промени идентификатора на IDC_SKILL. Избери страницата Style и установи опцията Type в състояние Simple. Избери страницата Data и добави три реда: Programmer; Manager; Writer. Всеки нов ред се разделя с Ctrl + Enter.

Прибави 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 и левия бутон на мишката върху последния правилно избран контрол.

Ако се сложи пред статичния текст & той излиза подчертан. Това позволява при натискането на комбинация Alt + подчертаната буква да се избере текстовото поле непосредствено след статичния текст.

Сега извикай Class Wizard. Погледни дали е избран диалога IDD_DIALOG1. Добави класа CE06ADialog.

Натисни ОК.

Въведи името CE06Adialog

Добави променливите на CE06ADialog. Щракни върху етикета Member Variables:

Асоциирай член променливи на класа с всеки от контролите на диалога. За целта щракни върху идентификатора на даден контрол, след което натисни бутона Add Variable. Появява се диалога Add Member Variable:

Въведи името на променливата и избери нейния тип. Това се повтаря за всички контроли:

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. Промени идентификатора на ОК от IDOK на IDC_OK. Изключи характеристиката Default Button;

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.

Примерът EX06B

1. Избери Single Document и изключи опцията Print And Print Preview.

2. Създай диалога IDD_DIALOG1 и постави контролите;

Ред на обхождане

Тип контрол

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

20

Бутон

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_2

8. Напиши кода за 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. Напиши кода за управление на контрола за списъчен изглед. Постави атрибутите на list-контролата както е показано:

Задай 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. Напиши кода за управление на контрола за дървовиден изглед. Смени атрибутите на Tree-контролата.

След това добави следния код към 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.cpp

void 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.cpp

void 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

2. Направи диалога с размери 7 на 12 см. Използвай идентификатор IDD_FILESPACEIAL Задай характеристиката Style на диалога да бъде Child, а характеристиката Border – None и включи характеристиките Clip Siblings и Visible. Създай шаблона и постави бутон с идентификатор IDC_DELETE и група (group box) с идентификатор stc32=0x45f.

Провери какво си направил като избереш Resource Symbols от менюто View. Трябва да има нещо от сорта:

3. Създай класа CSpecialFileDialog

Натисни бутона Chance и промени имената на SpecFileDlg.h и SpecFileDlg.cpp

4. Промени реда на файла SpecFileDialog.h

class 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. Добави към OnDraw

pDC->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 контроли

В примера от глава 6 се извикваше функцията CWnd::UpdateData(FALSE) за четене на променливите на диалоговия клас и CWnd::UpdateData (TRUE) за записване на данни на всички контроли.

По ефективно е да се използва член-променлива. Ако трябва да се прочете характеристиката 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());

Примерът EX08A

1. Провери дали контролът 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. Бутоните ги остави по подразбиране. Добави другите контроли.

Включи характеристиката Default button на Select Date и задай идентификаторите и реда на обхождане.

Контрол

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. Добави към класа

8. Редактирай класа CActiveXDialog. Добави променливите m_varValue и m_BackColor, след което редактирай кода на петте функции:

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.h

private:

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.h

protected:

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.cpp

BEGIN_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);

}

}

В следващия пример ще се демонстрира плавно движение върху екрана. Първо се чертае битмапа а после се хвърля на екрана.

Пример EX10B

1. Създай проекта;

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.

Затвори джама на битмапа и от прозореца Resource View използвай клипборда или изтегляне с мишката, за да направиш копие на битмапа. Преименувай копието на “COPYD”, и редактирай битмапа. То менюто Image избери Invert Color.

Направи същото и за битмапите 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.cpp

void 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. Напиши кода и на OnTimer

void 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. Редактирай класа CEX11AView

void 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 към меню Edit;

Добави и менюто 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. Добави към класа CEx12aDoc

public:

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. Прибави променливата в класа CEx12aView

public:

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 добави функцията OnInitDialog

4. Съдай клас наследник на 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. Добави към CEx12a

private:

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_PATTERN

4. Добави във 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 Lock

ON_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.

Добави в картата за асоцииране на съобщения на клас CMainFrame

BEGIN_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 и погледни стойностите.

Класът CPersistenFrame няма да работи в MDI защото функцията ShowWindow се извиква от функцията InitInstance на класа на приложението.

Примерът ЕХ15А

1. Създай проекта като промениш:

2. Редактирай менюто. Добави View -> Clear All. Задай ID_VIEW_CLEAR_ALL

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. При извикването и се изпълняват следните стъпки:

    1. Подсказва на потребителя да избере файл;
    2. Извиква виртуалната функция CDocument::OnOpenDocument за вече съществуващия документ обект. Тази функция отваря файла, извиква CDocument:DeleteContents и създава обект от клас CArchive, настроена за зареждане. Извика функцията Serialize на документа, която зарежда данни от архива;
    3. Извиква функцията OnInitialUpdate

Ако желаеш документните класове да работят в 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. Добави към класа CStringView

private:

CRect m_rectPrint;

4. Редактирай трите функции на PoemDoc.cpp

BOOL 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.h

typedef 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. Прибави следните променливи в Ех18b

public:

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 изчислява броя на страниците в документа и след това го съобщава на SetMaxPage

BOOL 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.

Когато се генерира проекта с рамка за разделяне се включва опцията Split в менюто View.

Другите примери ще ги пропусна ми щото са си тъпичко обяснени.

Пример Ех20А. Създаване на прост помощен файл

1. Създай директорията;

2. Напиши с Word или друг редактор поддържащ RTF формат (я по-добре гепи готовия файл)

#$Simple Help Table of Contents

Help topics

Topic 1HID_TOPIC1HID_TOPIC1

Topic 2HID_TOPIC3HID_TOPIC2

Topic 3HID_TOPIC3HID_TOPIC3

Всички HID_TOPICх в жълто са скрити. В зависимост от желанието ти за показване на информацията цялата връзка трябва да бъде подчертана, двойно подчертана или зачеркната. При подчертаване помощта се показва в прозорец под връзката.

Вмъкни нова страница и напиши това:

#$KHelp 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.rtf

7. Напиши проектен файл. Използвай 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 Window

K 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.h

StringView.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-F1

LRESULT 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.cpp

ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)

ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest)

Ето и кода за имплементация от HexView.cpp

LRESULT 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_*)

 

Библиотеки за динамично свързване - DLL

Dynamic 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);

Пример ЕХ21А

Този пример създава DLL разширение от класа CPresistentFrame, който видя в глава 14. Първо ще се изгради ex12a.dll, а в последствие ще го използваш в една текстова клиентска програма ЕХ21В:

1. Стартирай App Wizard. Създай проекта само че избери MFC App Wizard (dll). От следващия екран избери MFC Extension DLL

2. Разгледай Ех21а.срр;

3. Вмъкни класа CPersistenFrame в проекта.

4. Редактирай persist.h

class 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 MFC

2. Разгледай кода на 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) функцията.

1. Създай проекта. Избери опцията Dialog Based в стъпка 1;

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_COMPUTE

4. Напиши кода за 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 - компонент без потребителски интерфейс. С++ компонентът имплементира финансови транзакции. Може да се пише потребителски интерфейс на VBA

1. Генерирай проекта. В стъпка 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. Добави клас CBank

5. Добави два метода и характеристика с Class Wizard. Избери страницата Automation и добави метода Withdrawal. Параметърът dAmount е сумата, която трябва да се изтегли, а връщаната стойност е действително изтеглена сума. Ако се опиташ да изтеглиш $100 а сумата е $60, изтеглената сума ще бъде $60.

Добави аналогичния метод Deposit, който връща void, и след това добави характеристиката Balance.

Може да се избере директен достъп до член-променливите на компонентата, но тогава нямаше да има read-only достъп. Избери Get/Set Methods, така че функцията SetBalance да не прави нищо.

6. Добави m_dBalance. Public променлива тип double към класа CBank във файла Bank.h. Инициализирай я 0.0 в конструктора CBank, който се намира във файла bank.cpp

7. Редактирай и

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.0

Winsock клиента ЕХ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> &nbsp;&nbsp;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 и трябва да може да се въвеждат транзакции за работното време.

 

Hosted by uCoz