Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
773
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 311

Когда вы будете определять точки на графике, вам потребуется два блока информации. Свойство NumberOfPoints хранит суммарное количество точек, выводимых для каждой линии, а свойство NumberOfLines представляет собой количество линий на графике. Другими словами, график будет являться двумерной сеткой линий (в количестве NumberOfLines), состоящих из точек в количестве NumberOfPoints. Если вы не хотите ограничивать пользователя в возможном количестве точек для линий, будет не так трудно предоставить ему возможность самостоятельно задавать эти значения, что мы увидим, когда начнем непосредственно воплощать компонент.

Предназначение большинства остальных свойств явно вытекает из их названий. Единственное, с чем может возникнуть небольшое затруднение со свойствами XGrid и YGrid. Если эти свойства имеют значение «истина», то график будет изображен с наложенной на него сеткой (состоящей из горизонтальных и/или вертикальных линий). Если эти свойства установлены в значение «ложь» (одно или оба), линии не будут рисоваться. По-моему, график с наложенной на него сеткой воспринимается куда лучше, и поэтому значения этих свойств по умолчанию есть true (истина).

Воплощение управляющего элемента

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

В случае свойств, относящихся к точкам и цвету, нам потребуется некие массивы для хранения данных. Это однозначно вытекает из определения этих свойств. Однако совершенно непонятным остается вопрос о том, насколько велик должен быть массив для того, чтобы хранить все данные. Откуда мы можем знать, насколько большие массивы точек захочет завести пользователь? Одни могут ограничиться десятком точек, другим же и тысячи может быть мало. Для того, чтобы у нас получился действительно полезный компонент, мы должны избегать конструктивных ограничений при проектировании и воплощении его до тех пор, пока такие ограничения не станут абсолютно необходимыми по логике. К счастью, ранее в книге мы уже говорили о библиотеке стандартных шаблонов (Standard Template Library, STL), библиотеке, которая предоставляет в наше распоряжение структуры данных, среди которых есть и массивы, у которых нет фиксированного размера и которые могут наращиваться динамически во время исполнения. Мы используем класс vector из STL для хранения данных о точках в нашем компоненте.

Давайте посмотрим на изменения, которые необходимо внести в заголовочный файл для описания переменных-членов класса, описывающих свойства:

typedef std::vector<double> DblArray; class LineGraph : public TCustomControl

{

private:

int FNumberOfLines; int FNumXTicks;

int FNumYTicks; double FXStart; double FYStart; double FXInc; double FYInc; int FNumPoints;

std::vector<DblArray> FXPoints; std::vector<DblArray> FYPoints; std::vector<Graphics::TColor> FLineColors; bool FXGrid;

Borland C++ Builder (+CD). Библиотека программиста 312

bool FYGrid;

int FXNumDecimals; int FYNumDecimals;

Описание типа в верхней части заголовочного файла используется для того, чтобы было проще работать с классом vector при множественных индексных массивах. Если бы мы так не сделали, нам пришлось бы применять конструкции типа:

vector < vector<double> > FXPoints;

Это вызвало бы проблемы с использованием большого количества скобок (<>). Вместо того, чтобы каждый раз писать эти скобки, мы описываем тип vector как новый тип DblArray, который является гибким массивом вещественных значений с двойной точностью.

Теперь, когда переменные класса описаны, надо их инициализировать в какие-нибудь удобоваримые значения. Это мы сделаем, естественно, в конструкторе класса. Давайте посмотри, как это будет выглядеть:

__fastcall LineGraph::LineGraph(TComponent* Owner) : TCustomControl(Owner)

{

// Инициализируем свойства компонента

FNumXTicks = 5;

FNumYTicks = 5; FXStart = 0.0; FYStart = 0.0; FXInc = 10.0; FYInc = 10.0;

FNumPoints = 0;

FNumberOfLines = 0;

//По умолчанию график отображается

//с наложенной на него координатной сеткой

FXGrid = true; FYGrid = true;

FXNumDecimals = 2; FYNumDecimals = 2;

}

Как вы видите, в этом куске кода мы просто присваиваем резонные значения всем переменным класса. А что же с массивами точек и цветов? Они не могут быть инициализированы в конструкторе класса, поскольку мы не знаем их размеров. Когда же мы это узнаем? Когда пользователь установит количество линий и количество точек. Если вы вернетесь назад и посмотрите описание свойств NumberOfLines и NumberOfPoints, то увидите, что они оба используют переменную для чтения и функцию для записи значений. Функции используются из- за того, что при изменении значений этих свойств возникают некие побочные эффекты. Здесь в очередной раз проявляется мощь свойств. Несмотря на то, что пользователь не подозревает (или, во всяком случае, его это не волнует) о том, что при изменении количества линий или точек где-то на заднем плане происходит выделение памяти, это тем не менее происходит. Вот как выглядят функции изменения значений этих свойств:

Borland C++ Builder (+CD). Библиотека программиста 313

void __fastcall LineGraph::SetNumberOfLines( int nLines )

{

//Устанавливаем количество точек

//по X и по Y

FXPoints.reserve( nLines );

FYPoints.reserve( nLines );

//Устанавливаем количество цветов

FLineColors.reserve( nLines );

//Цвет всех линий изначально

//устанавливаем в черный

for ( int i=0; i<nLines; ++i ) FLineColors[i] = clBlack;

// Сохраняем количество линий

FNumberOfLines = nLines;

}

void __fastcall LineGraph::SetNumberOfPoints( int nPoints )

{

//Устанавливаем количество точек

//по X и по Y для каждой линии

for ( int i=0; int<FNumberOfLines; ++i )

{

FXPoints[i].reserve( nPoints );

FYPoints[i].reserve( nPoints );

//На всякий случай устанавливаем

//все точки в значение 0.0

for ( int nPt=0; nPt<nPoints; ++nPt )

{

FXPoints[i][nPt] = 0.0;

FYPoints[i][nPt] = 0.0;

}

}

// Сохраняем количество точек

FNumPoints = nPoints;

}

Итак, теперь все свойства присвоены и инициализированы. Под массивы выделена память, и они готовы к приему данных. Что дальше? Перво-наперво, мы должны обеспечит возможность вводить данные в различные свойства. Методы, осуществляющие это, просты, поскольку под вектора уже была выделена память и они даже были инициализированы. Вот методы для ввода данных в массивы:

double __fastcall LineGraph::GetXPoints( int nLine, int nIndex )

{

return FXPoints[nLine][nIndex];

}

Borland C++ Builder (+CD). Библиотека программиста 314

void __fastcall LineGraph::SetXPoint( int nLine, int nIndex, double dPoint )

{

if ( nLine >= FXPoints.size() ) FXPoints.insert( FXPoints.end(), DblArray() );

FXPoints[ nLine ].inset(FXPoints[nLine].end(),dPoint);

}

double __fastcall LineGraph::GetYPoints( int nLine, int nIndex )

{

return FYPoints[nLine][nIndex];

}

void __fastcall LineGraph::SetYPoint( int nLine, int nIndex, double dPoint )

{

if ( nLine >= FYPoints.size() ) FYPoints.insert( FYPoints.end(), DblArray() );

FYPoints[ nLine ].inset(FYPoints[nLine].end(),dPoint);

}

void __fastcall LineGraph::SetLineColor( int nIndex, Graphics::TColor clrNewColor)

{

FLineColors[ nIndex ] = clrNewColor;

}

Graphics::TColor __fastcall LineGraph::GetLineColor ( int nIndex )

{

return FLineColors[ nIndex ];

}

Вполне понятно, как написан этот код, но гораздо интереснее, как он используется. Когда у вас есть свойство-массив (то есть определенное как свойство[индекс]) ваши функции Get... должны получать по параметру для каждого индекса массива. Если массив одномерный, как в случае со свойством цвета линии, функция Get... имеет один параметр индекс возвращаемого значения. Для двумерных массивов функции Get... имеют два параметра, и так далее.

Точно также и функции Set... для свойств-массивов имеют несколько параметров по одному для каждого индекса массива, и одно для значения, которое должно быть записано в массив по этим индексам. Для использования свойств-массивов вы обращаетесь к ним следующим образом:

pLineGraph->Xpoints[nLine][nPt] = x;

Предыдущая строка кода преобразуется в следующий вызов:

pLineGraph->SetXPoints(nLine, nPt, x);

Теперь вы, наверное, оценили важность возможности использования функций для чтения и записи

Borland C++ Builder (+CD). Библиотека программиста 315

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

Код для отображения результатов на экране является, по сути, последним большим куском компонента. Он вполне бесхитростен, но выглядит немного устрашающе из-за своих размеров, так что вы вполне можете разбить его на несколько составляющих при написании своего компонента. Мы воплощаем код для отображения на экран в своей собственной процедуре DoPaint, вместо того, чтобы просто обработать рисование, по весьма веской причине. Обрабатывая процедуру, использующую произвольный объект Canvas, код может быть использован для отображения не только на экран, но и на принтер, факс или другое устройство.

void __fastcall LineGraph::DoPaint(TCanvas *pCanvas)

{

int nYStart = Top + 20; int nXStart = 50;

int RightMargin = 40;

// Рисуем оси графика pCanvas->MoveTo( nXStart, nYStart); pCanvas->LineTo( nXStart, Height-30 );

pCanvas->LineTo( Width-RightMargin, Height-30);

//Наносим риски на оси

//Сначала горизонтальные

if (FNumXTicks > 0)

{

// Определяем промежутки

int nSpaceTicks = (Width-nXStart-RightMargin) / FNumXTicks;

double xVal = FXStart;

for ( int i=0; i<=FNumXTicks; ++i )

{

pCanvas->MoveTo( nXStart+(i*nSpaceTicks),Height-30); pCanvas->LineTo( nXStart+(i*nSpaceTicks),Height-25);

//Метим риски char szBuffer[ 20 ];

sprintf(szBuffer, "%5.*lf",FXNumDecimals,xVal);

//Получаем ширину строки ...

int nWidth = pCanvas->TextWidth( szBuffer );

//и помещаем ее в надлежащее место pCanvas->Brush->Color = Color; pCanvas->TextOut(nXStart+(i*nSpaceTicks)-nWidth/2,

Height-20, szBuffer );

//Увеличиваем значение

Borland C++ Builder (+CD). Библиотека программиста 316

xVal += FXInc;

// Если сетка требуется, отображаем ее if ( FXGrid )

{

pCanvas->MoveTo( nXStart+(i*nSpaceTicks), nYStart );

pCanvas->LineTo( nXStart+(i*nSpaceTicks), Height-30 );

}

}

}

// Теперь вертикальные if (FNumYTicks > 0)

{

double yVal = FYStart;

// Определяем промежутки

int nSpaceTicks = (Height-30-nYStart) / FNumYTicks;

for ( int i=0; i<=FNumYTicks; ++i )

{

int nYPos = Height-30-(i*nSpaceTicks); pCanvas->MoveTo( nXStart-5, nYPos ); pCanvas->LineTo( nXStart, nYPos );

//Метим риски char szBuffer[ 20 ];

sprintf(szBuffer, "%5.*lf",FYNumDecimals,yVal);

//Получаем ширину строки ...

int nWidth = pCanvas->TextWidth( szBuffer ); int nHeight = pCanvas->TextHeight( szBuffer );

//и помещаем ее в надлежащее место pCanvas->Brush->Color = Color; pCanvas->TextOut(nXStart-nWidth-7,

nYPos-nHeight/2, szBuffer );

//Увеличиваем значение

yVal += FXInc;

// Если требуется сетка, отображаем ее if ( FYGrid )

{

pCanvas->MoveTo( nXStart, nYPos ); pCanvas->LineTo( Width-RightMargin, nYPos );

}

}

}

Borland C++ Builder (+CD). Библиотека программиста 317

// Рисуем линии, соединяющие точки if ( FNumPoints > 0 )

{

for ( int nLine = 0; nLine < FXPoints.size(); ++nLine )

{

//Устанавливаем цвета для этой линии pCanvas->Pen->Color = FLineColors[ nLine ];

//Переводим в экранные единицы

int nXPos = XPointToScreen(FXPoints[nLine][0]); int nYPos = YPointToScreen(FYPoints[nLine][0]); for ( int i=1; i<NumberOfPoints; ++i )

{

pCanvas->MoveTo(nXPos, nYPos);

nXPos = XPointToScreen(FXPoints[nLine][i]); nYPos = YPointToScreen(FYPoints[nLine][i]); pCanvas->LineTo(nXPos, nYPos);

}

}

// Сбрасываем цвета pCanvas->Pen->Color = clBlack;

}

}

Имея эту функцию, мы без труда можем воплотить две функции для вывода графика на экран или на принтер, не изменяя для этого экран:

void __fastcall LineGraph::Paint(void)

{

DoPaint(Canvas);

}

void __fastcall LineGraph::Print(void)

{

TPrinter *pPrinter; pPrinter = new TPrinter(); pPrinter->BeginDoc(); DoPaint(pPrinter->Canvas); pPrinter->EndDoc(); delete pPrinter;

}

На самом деле у нас нет особых причин создавать новый объект TPrinter. Мы могли бы запросто использовать в приведенном выше методе Print функцию Printer() вместо объекта printer. Наконец, пришло время представить две последние функции в нашем компоненте для преобразования точек данных в точки дисплея. Вот они, во всей красе:

int __fastcall LineGraph::XPointToScreen(double pt)

{

int rightMargin = 40; int nXStart = 50;

// Рассчитываем ширину экрана

int nSpaceTicks = (Width-nXStart-RightMargin)

Borland C++ Builder (+CD). Библиотека программиста 318

/ FNumXTicks;

int nNumPixels = nSpaceTicks * FNumXTicks;

// Рассчитываем ширину данных

double dWidth = (FNumXTicks * FXInc) - FXStart;

//Рассчитываем, какую часть экрана занимают данные double dPercent = (pt-FXStart) / dWidth;

//Теперь переводим это в пикселы

int nX = dPercent * nNumPixels;

// Готово! Теперь откладываем это от начала nX = nXStart + nX;

return nX;

}

//-----------------------------------------------------

int __fastcall LineGraph::YPointToScreen(double pt)

{

int nYStart = Top + 20;

// Рассчитываем ширину экрана

int nSpaceTicks = (Height-30-nYStart) / FNumYTicks; int nNumPixels = nSpaceTicks * FNumYTicks;

// Рассчитываем ширину данных

double dWidth = (FNumYTicks * FYInc) - FYStart;

//Рассчитываем, какую часть экрана занимают данные double dPercent = (pt-FYStart) / dHeight;

//Теперь переводим это в пикселы

int nY = dPercent * nNumPixels;

// Готово! Теперь откладываем это от начала nY = nYStart + nY;

return nY; last>}

На этом наш графический управляющий элемент закончен, осталось только протестировать его. Вы найдете тестовую программу для этого компонента на прилагаемом к книге компакт-диске. На ее примере вы сможете посмотреть, как сначала динамически создается управляющий элемент, а потом в него загружаются данные. Если вы запустите эту программу, у вас на экране появится окно, показанное на рис. 14.5.

Borland C++ Builder (+CD). Библиотека программиста 319

Рис. 14.5. Компонент LineGraph в действии

Для того, чтобы понять все в воплощении компонента LineGraph, вам, возможно, потребуется затратить некоторые усилия. Этот компонент отнюдь не тривиален, но, как вы вскоре убедитесь, сможет серьезно помочь вам в ваших проектах. Не пожалейте времени на то, чтобы вернуть к коду компонента и разобраться, что как работает.

Что мы узнали в этой главе?

Эта глава была кратким, но насыщенным введением в чудесный мир разработки компонентов, с

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

Компоненты это строительные блоки системы CBuilder, которые позволяют вам расширять систему для решения ваших специфических задач. Разрабатывая компоненты, вы всемерно способствуете многократному использованию одного и того же кода (или его частей) в вашей среде его использования, так что написание действительно хорошего кода вознаграждается не единожды. Относитесь к компонентам как к отображению ваших программистских способностей.

Если вы собираетесь заниматься ими профессионально для распространения среди других программистов, я бы настоятельно порекомендовал вам приобрести книгу конкретно по разработке компонентов.

В этой главе были освещены следующие основные моменты:

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

Вы можете объединить компоненты в библиотеку для регистрации в CBuilder.

Компоненты могут создаваться на основе уже существующего кода, как показано в компоненте

AngleText, который содержит в себе код поворота текста, созданный нами в одной из предыдущих глав.

Свойства компонентов могут быть как простыми например целыми или вещественными, так и сложными такими, как массивы (причем любой мерности) и строки.

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

Вот, пожалуй, и все, что я хотел донести до вас в этой главе. Несмотря на то, что мы лишь едва-

Borland C++ Builder (+CD). Библиотека программиста 320

едва копнули тему разработки компонентов, я надеюсь, что вы узнали достаточно для того, чтобы быть в состоянии продолжить изучение ее самостоятельно. Компоненты самая восхитительная часть системы CBuilder, и их создание даст вам бесценный опыт.

Глава 15. Часто задаваемые вопросы (FAQ)

Самый быстрый способ решить проблему

Работа с STL

Где найти подробные ответы?

Работа с исключениями

Вопросы этой главы задавались на телеконференциях Borland, на форумах CompuServe или были получены мною по электронной почте от других пользователей системы CBuilder. Чтобы включить максимальное количество вопросов, на каждый вопрос дается по возможности краткий ответ. Если после прочтения вопросов и ответов у вас не будет достаточного понимания какой- либо проблемы или путей ее решения, обратитесь к соответствующим разделам книги. Эта глава разделена на 9 основных частей:

Общие вопросы.

Общие вопросы программирования.

Библиотека стандартных шаблонов (STL).

Библиотека визуальных компонентов (VCL).

Файлы помощи.

Вопросы по базам данных.

Обработка исключительных ситуаций (Exception Handling).

Разное.

Создание компонентов.

Общие вопросы

Что такое C++ Builder?

C++Builder это собственное имя нового инструмента для быстрого создания приложений фирмы Borland. В этой книге мы сокращаем это имя до CBuilder. В целом это Delphi, использующий С++ как язык разработки. CBuilder позволяет быстро создавать приложения на базе форм при использовании простого стиля конструирования drag-and-drop. Все компоненты CBuilder определяются свойствами, так что определение нового объекта или отображение формы сводится к добавлению нового компонента в форму (или новой формы в проект) и установке некоторых его свойств для определения его поведения.

CBuilder также характеризуется набором инструментов типа «туда и обратно». Изменения, сделанные в редакторе форм, немедленно отражаются в коде. Точно также, изменения, сделанные в коде, отражаются в представлении формы.

Ради чего мне стоит использовать CBuilder?

Если вы работаете с C++, создавая приложения для автоматизации различных рабочих мест (как большинство из нас), вы должны серьезно подумать об использовании CBuilder вместо тех программных средств, которые вы обычно используете. CBuilder отводит главную роль компилятору C++ промышленного стандарта, дополненному шаблонами, средствами обработки исключительных ситуаций, именованными областями видимости, библиотекой стандартных

Соседние файлы в предмете Программирование на C++