Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.7. Настройка параметров блока

§2.7.3. Расширенные возможности функции обратного вызова

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.7. Настройка параметров блока

#### §2.7.3. Расширенные возможности функции обратного вызова

Рассматривается рисование произвольных изображений в модальном окне, формируемом вспомогательным объектом RDS. Для этого в окно настроек блока из предыдущего примера добавляется изображение внешнего вида формируемой генератором функции.

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

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

Прежде всего необходимо изменить функцию TTestGenData::Setup – нужно добавить в окно специальную панель для рисования и заменить вызов rdsFORMShowModalEx на rdsFORMShowModalServ. Функция приобретет следующий вид (изменения выделены цветом):

      //====== Функция редактирования параметров ======
      int TTestGenData::Setup(void)
      { RDS_HOBJECT window;	// Идентификатор вспомогательного объекта
      // …
        // Занесение исходных значений в поля ввода
        rdsSetObjectInt(window,1,RDS_FORMVAL_VALUE,Type);
        rdsSetObjectDouble(window,2,RDS_FORMVAL_VALUE,Period);
        rdsSetObjectDouble(window,3,RDS_FORMVAL_VALUE,Impulse);
        // Включение дополнительной панели слева от полей ввода
        rdsFORMEnableSidePanel(window,1,-1);
        // Добавление области для рисования графика
        rdsFORMAddEdit(window,1,4,RDS_FORMCTRL_PAINTBOX,NULL,100);
        // Автоматическое вычисление высоты этой области
        rdsSetObjectInt(window,4,RDS_FORMVAL_PBHEIGHT,-1);
        // Открытие окна с другой функцией обратного вызова
        ok=rdsFORMShowModalServ(window,TestGenDataCheckFunc2);
        if(ok)
      // …
        return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE;
      }

В этом примере график будет размещаться слева от полей ввода на дополнительной панели, поэтому сначала нужно разрешить отображение этой дополнительной панели при помощи функции rdsFORMEnableSidePanel. Второй параметр функции, равный 1, это идентификатор, который присваивается этой панели, при размещении на ней полей его нужно указывать при вызове rdsFORMAddEdit вместо идентификатора вкладки. Третий параметр функции (−1) указывает на необходимость автоматического вычисления ширины панели. Можно было бы разместить область рисования на основной панели над или под полями ввода, тогда в вызове функции rdsFORMEnableSidePanel не было бы необходимости. Здесь она используется только для иллюстрации возможности размещения полей на двух панелях.

После включения дополнительной панели на нее при помощи вызова rdsFORMAddEdit добавляется область рисования (RDS_FORMCTRL_PAINTBOX), которой присваивается идентификатор 4. В качестве идентификатора вкладки в функцию передается число 1, которое было использовано при вызове rdsFORMEnableSidePanel. Заголовка у области рисования нет (передается NULL), ширина устанавливается в 100 точек экрана. Для области рисования нужно задать не только ширину, но и высоту, поэтому за вызовом rdsFORMAddEdit следует вызов rdsSetObjectInt с параметрами RDS_FORMVAL_PBHEIGHT (установка высоты области рисования) и −1 (высота вычисляется автоматически). В результате на дополнительную панель будет добавлена область рисования с идентификатором 4, шириной в 100 точек и высотой во всю панель. Внутри этой области и будет рисоваться примерный вид графика функции.

Для открытия модального окна теперь используется функция rdsFORMShowModalServ, поэтому вместо старой функции обратного вызова TestGenDataCheckFunc теперь указана TestGenDataCheckFunc2 – эту функцию еще предстоит написать. Она будет принимать не один, а два параметра: идентификатор окна RDS_HOBJECT и указатель на структуру RDS_FORMSERVFUNCDATA, в которой содержатся дополнительные параметры вызова. Эта структура описана в файле «RdsDef.h» следующим образом:

      typedef struct
      { int Event;        // Событие (RDS_FORMSERVEVENT_*)
        int CtrlId;       // Идентификатор поля или -1
        // Для RDS_FORMSERVEVENT_DRAW
        HDC dc;           // Контекст устройства рисования Windows
        int Left,Top;     // Верхний левый угол зоны рисования
        int Width,Height; // Размеры зоны рисования
      } RDS_FORMSERVFUNCDATA;
      // Указатель на структуру
      typedef RDS_FORMSERVFUNCDATA *RDS_PFORMSERVFUNCDATA;

Event(int)
  Событие, произошедшее в модальном окне. В данный момент может принимать два значения:

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

CtrlId(int)
  Идентификатор поля, с которым связано событие, или −1. Например, для события RDS_FORMSERVEVENT_DRAW в этом поле передается идентификатор области, которую необходимо перерисовать, а для события RDS_FORMSERVEVENT_CHANGE – идентификатор изменившегося поля или −1, если изменилось сразу несколько полей.
dc(HDC)
  Контекст устройства Windows, на котором нужно рисовать при реакции на RDS_FORMSERVEVENT_DRAW.
Left, Top, Width, Height(int)
  Положение и размеры области рисования.

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

      void RDSCALL TestGenDataCheckFunc2(RDS_HOBJECT win,
                                         RDS_PFORMSERVFUNCDATA data)
      { // Считать номер пункта выпадающего списка
        int type=rdsGetObjectInt(win,1,RDS_FORMVAL_VALUE);
        // Вспомогательные переменные для рисования графика
        int y0,y_ampl,x0,x1;
        double pix_period;

        switch(data->Event)
          { // Изменение поля ввода
            case RDS_FORMSERVEVENT_CHANGE:
              // Запретить ввод длительность импульса для sin и cos
              rdsSetObjectInt(win,3,RDS_FORMVAL_ENABLED,type==2);
              // Перерисовать график
              rdsCommandObject(win,RDS_FORM_INVALIDATE);
              break;

            // Рисование
            case RDS_FORMSERVEVENT_DRAW:
              // Заливка фона белым цветом
              rdsXGSetBrushStyle(0,RDS_GFS_SOLID,0xffffff);
              rdsXGFillRect(data->Left,
                            data->Top,
                            data->Left+data->Width,
                            data->Top+data->Height);
              // Координаты рисования
              x0=data->Left+10;             // Начало графика
              x1=data->Left+data->Width-10; // Конец графика
              y0=data->Top+data->Height/2;  // Центр по вертикали
              y_ampl=(data->Height-20)/2;   // Амплитуда
              pix_period=0.5*(x1-x0);       // Период на рисунке
              // Координатные оси
              rdsXGSetPenStyle(0,PS_SOLID,1,0,R2_COPYPEN);
              rdsXGMoveTo(data->Left+5,y0);
              rdsXGLineTo(data->Left+data->Width-5,y0);
              rdsXGMoveTo(x0,data->Top+5);
              rdsXGLineTo(x0,data->Top+data->Height-5);
              // График
              rdsXGSetPenStyle(RDS_GFWIDTH,0,3,0,0);
              if(type==2) // Прямоугольные импульсы
                { double period,impulse,pix_impulse;
                  // Чтение введенных пользователем значений
                  period=rdsGetObjectDouble(win,2,RDS_FORMVAL_VALUE);
                  impulse=rdsGetObjectDouble(win,3,RDS_FORMVAL_VALUE);
                  if(period==0.0) // Нельзя вычислить частоту
                    return;
                  // Длительность импульса на рисунке
                  pix_impulse=impulse*pix_period/period;
                  // Первый период
                  rdsXGMoveTo(x0,y0+y_ampl);
                  rdsXGLineTo(x0,y0-y_ampl);
                  rdsXGLineTo(x0+pix_impulse,y0-y_ampl);
                  rdsXGLineTo(x0+pix_impulse,y0+y_ampl);
                  rdsXGLineTo(x0+pix_period,y0+y_ampl);
                  // Второй период
                  rdsXGLineTo(x0+pix_period,y0-y_ampl);
                  rdsXGLineTo(x0+pix_period+pix_impulse,y0-y_ampl);
                  rdsXGLineTo(x0+pix_period+pix_impulse,y0+y_ampl);
                  rdsXGLineTo(x1,y0+y_ampl);
                }
              else // Синус или косинус
                { double t,y;
                  // Цикл по горизонтали с шагом в 3 точки
                  for(int x=x0;x<=x1;x+=3)
                    { t=2*M_PI*(x-x0)/pix_period;
                      y=y_ampl*((type==0)?sin(t):cos(t));
                      if(x==x0) // Первая точка – установить позицию
                        rdsXGMoveTo(x,y0-y);
                      else // Рисовать линию от предыдущей точки
                        rdsXGLineTo(x,y0-y);
                    } // for(int x=x0...)
                }
              break;
          } // switch
      }
      //=========================================

Тело функции можно разместить перед функцией TTestGenData::Setup или, как в предыдущем примере, поместить там прототип, а тело функции разместить в любом другом месте.

В самом начале функции TestGenDataCheckFunc2, как и в TestGenDataCheckFunc, во вспомогательную переменную type считывается выбранный в выпадающем списке тип формируемой функции. Затем, в зависимости от события окна (data->Event), производятся различные действия.

При изменении какого-либо поля ввода (событие RDS_FORMSERVEVENT_CHANGE) разрешается или запрещается ввод данных в поле длительности импульса, так же, как и в предыдущем примере в функции TestGenDataCheckFunc. После этого вызов rdsCommandObject с параметром RDS_FORM_INVALIDATE информирует окно о необходимости перерисовки – если изменились тип функции, период или длительность импульса, внешний вид графика нужно изменить в соответствии с новыми параметрами.

Когда возникнет необходимость обновить изображение на панели рисования окна, функция TestGenDataCheckFunc2 будет вызвана с параметром data->Event, равным RDS_FORMSERVEVENT_DRAW. В функцию передается контекст рисования data->dc, поэтому для рисования можно использовать любые функции Windows API, работающие с контекстами устройств. Чтобы не загромождать пример, вместо функций API здесь будут использоваться графические функции-оболочки RDS – хотя они и не так богаты, как функции рисования API, ими гораздо проще пользоваться, и для этого примера их вполне достаточно.

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

      void RDSCALL rdsXGSetBrushStyle(
        int Mask,         // Набор устанавливаемых параметров
        int Style,        // Стиль заливки
        COLORREF Color);  // Цвет заливки

В данном случае параметр Mask равен нулю (устанавливаются оба параметра заливки), параметр Style – константе RDS_GFS_SOLID (сплошная заливка), а параметр Color – шестнадцатеричной константе 0xFFFFFF, соответствующей белому цвету (интенсивности всех трех компонентов цвета равны 255 (0xFF), то есть максимальны). После этого вызова все геометрические фигуры, которые будут рисоваться на данной панели, будут иметь сплошной белый цвет фона. Теперь можно закрасить всю панель установленным цветом, вызвав функцию заполнения прямоугольника rdsXGFillRect:

      void RDSCALL rdsXGFillRect(
        int Left,     // Левая граница
        int Top,      // Верхняя граница
        int Right,    // Правая граница
        int Bottom);  // Нижняя граница

Границы закрашиваемого прямоугольника определяются полями Left, Top, Width и Height структуры RDS_FORMSERVFUNCDATA, указатель на которую функция получает через параметр data. Left и Top – это левая и верхняя границы области рисования, Width и Height – ширина и высота. Таким образом, правая граница закрашиваемой области вычисляется как data->Left+data->Width, нижняя – data->Top+data->Height.

Для рисования графика необходимо вычислить несколько вспомогательных параметров. Будем считать, что горизонтальная ось графика проходит точно по центру панели, не доходя до левого и правого краев на 5 точек, а вертикальная ось отстоит от левого края панели на 10 точек, также не доходя до верхнего и нижнего краев на 5 точек (рис. 50). Для наглядности будем изображать на панели два периода графика, причем между правым краем графика и правым краем панели должен быть зазор в 10 точек. Максимум и минимум графика также должны отстоять от краев панели на 10 точек.

Рис. 50. Координаты и размеры графика в точках экрана

Таким образом получаем:

Начало графика отстоит на 10 точек от левого края панели:

      x0 = data->Left + 10;

Конец графика отстоит на 10 точек от правого края панели:

      x1 = data->Left + data->Width – 10;

Горизонтальная ось проходит по центру панели, то есть отстоит от верхнего края на половину высоты:

      y0 = data->Top + data->Height/2;

Амплитуда графика равна половине высоты панели с запасом в 20 точек:

      y_ampl = (data->Height – 20)/2;

Между x0 и x1 умещается два периода графика:

      pix_period = 0.5*(x1 – x0);

После того, как вспомогательные параметры вычислены, нужно нарисовать оси координат. Для этого сначала следует установить тип и цвет рисуемых линий при помощи сервисной функции rdsXGSetPenStyle:

      void RDSCALL rdsXGSetPenStyle(
        int Mask,        // Набор устанавливаемых параметров
        int Style,       // Стиль линии
        int Width,       // Толщина линии
        COLORREF Color,  // Цвет линии
        int Mode);       // Режим рисования

Нужно установить все параметры линии, поэтому параметр Mask в вызове равен нулю. В качестве стиля линии передается стандартная константа Windows API PS_SOLID (сплошная линия), толщина линии устанавливается равной одной точке, линия будет рисоваться черным (параметр Color равен нулю, что означает нулевую интенсивность всех трех компонентов цвета, то есть черный цвет). В качестве режима рисования линии передается стандартная константа API R2_COPYPEN – точки линии будут окрашены в установленный цвет независимо от цвета фона под линией. Эти параметры будут использоваться при рисовании всех линий и контуров геометрических фигур до следующего вызова функции rdsXGSetPenStyle, или до тех пор, пока стиль линии не будет изменен средствами Windows API.

Теперь, когда установлен тип линий, можно рисовать оси координат. Для рисования линий будут использоваться функции rdsXGMoveTo и rdsXGLineTo. Первая устанавливает координаты точки начала рисования, вторая рисует линию от последней точки до точки с указанными в параметрах координатами (аналоги таких функций есть в большинстве графических библиотек). Для того, чтобы нарисовать прямую линию, нужно сделать один вызов rdsXGMoveTo, указав координаты начала линии, и один вызов rdsXGLineTo, указав координаты конца.

После того, как оси нарисованы, нужно изобразить внешний вид графика формируемой функции в зависимости от типа, выбранного в выпадающем списке (номер варианта уже считан во вспомогательную переменную type). Чтобы график четко выделялся на фоне координатных осей, будем рисовать его линией толщиной в три точки. Для установки новой толщины линии снова используется функция rdsXGSetPenStyle, только теперь в ее параметре Mask передается константа RDS_GFWIDTH (описана в «RdsDef.h»), указывающая на то, что будет изменена только толщина линии. В параметре Width передается новая толщина (3), а остальные параметры будут проигнорированы функцией, поэтому их значения не важны – в данном случае для их заполнения используются нули.

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

При рисовании прямоугольных импульсов (значение type равно 2) необходимо вычислить длительность импульса на рисунке. Мы условились, что на рисунке должно изображаться два периода функции, и, исходя из этого, рассчитали период графика в точках экрана (вспомогательная переменная pix_period). Чтобы вычислить длительность импульса в точках экрана, нужно умножить pix_period на отношение введенной пользователем длительности к введенному им периоду. Введенные период и длительность считываются во вспомогательные переменные period и impulse соответственно, после чего, если значение периода не нулевое, вычисляется длительность импульса на рисунке pix_impulse. Теперь можно нарисовать два периода графика одной ломаной линией. Сначала нужно установить начальную точку рисования при помощи функции rdsXGMoveTo, а затем рисовать ломаную линию последовательными вызовами rdsXGLineTo (каждый следующий вызов будет рисовать линию от конца предыдущей линии до указанных в параметрах координат).

Рисование тригонометрических функций (значение type не равно 2) осуществляется в цикле от x0 до x1 с шагом в 3 точки. Внутри цикла вычисляется значение синуса или косинуса (в зависимости от type) и вызывается rdsXGMoveTo для самой первой точки или rdsXGLineTo для всех остальных.

Теперь при вызове функции настройки этого блока на панели слева от полей ввода будет изображаться внешний вид графика функции, соответствующий выбранному в выпадающем списке варианту (рис. 51).

Рис. 51. Окно настроек генератора с изображением
формируемой функции