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

Описания RDS

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

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

§2.14. Программное управление расчетом

§2.14.3. Сохранение и загрузка состояния блоков

Полный исходный текст на языке C++ для библиотеки (DLL) с моделями блоков, иллюстрирующих сохранение и загрузку состояния части схемы. Библиотека содержит две модели:

- SaveLoadState – блок, сохраняющий и загружающий состояние своей родительской подсистемы по щелчку мыши;
- SimplePlot – ранее рассмотренная модель блока-графика, в которую добавлена поддержка сохранения и зарузки состояния (изменения в модели выделены цветом).

      // Сохранение и загрузка состояния блоков
      #include <windows.h>
      #include <stdio.h>
      #include <math.h>
      #include <RdsDef.h>
      // Подготовка описаний сервисных функций
      #define RDS_SERV_FUNC_BODY GetInterfaceFunctions
      #include <RdsFunc.h>

      //========== Главная функция DLL ==========
      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          { // Получение доступа к функциям RDS
            if(!GetInterfaceFunctions())
              RDS_SERV_ERROR_MSGW // Сообщение: старая версия RDS
          }
        return 1;
      }
      //========= Конец главной функции =========

      //=========================================
      // Запись и загрузка состояния
      //=========================================
      extern "C" __declspec(dllexport)
          int RDSCALL SaveLoadState(int CallMode,
                              RDS_PBLOCKDATA BlockData,
                              LPVOID ExtParam)
      { RDS_PMOUSEDATA mouse;
        // Указатель на личную обрасть данных блока (int)
        int *pSaveId=(int*)(BlockData->BlockData);

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создание личной области данных (одно число int)
              BlockData->BlockData=pSaveId=new int;
              // Исходное значение идентификатора: -1 (т.е. нет)
              *pSaveId=-1;
              break;

            // Очистка
            case RDS_BFM_CLEANUP:
              // Удаление сохраненного состояния (если есть)
              rdsDeleteSystemState(*pSaveId);
              // Уничтожение личной области
              delete pSaveId;
              break;

            // Нажатие кнопки мыши
            case RDS_BFM_MOUSEDOWN:
              mouse=(RDS_PMOUSEDATA)ExtParam;
              if(mouse->Button==RDS_MLEFTBUTTON) // Левая кнопка
                { RDS_BLOCKDESCRIPTION descr;
                  descr.servSize=sizeof(descr);
                  BOOL save=FALSE,load=FALSE;
                  // Есть ли у блока картинка?
                  rdsGetBlockDescription(BlockData->Block,&descr);
                  if(descr.Flags & RDS_BDF_HASPICTURE)
                    { // Картинка есть – смотрим идентификатор элемента
                      // под курсором
                      int pic_id=rdsGetMouseObjectId(mouse);
                      if(pic_id>0)
                        save=TRUE;
                      else if(pic_id<0)
                        load=TRUE;
                    }
                  else if(mouse->y<mouse->Top+mouse->Height/2)
                    save=TRUE; // Картинки нет, верх блока
                  else
                    load=TRUE; // Картинки нет, низ блока
                  if(save) // Сохраняем состояние
                    *pSaveId=rdsSaveSystemState(BlockData->Parent,
                                 *pSaveId,TRUE,NULL);
                  if(load) // Загружаем состояние
                    { rdsLoadSystemState(*pSaveId,NULL);
                      // Необходимо обновить окно подсистемы
                      rdsRefreshBlockWindows(BlockData->Parent,TRUE);
                    }
                }
              break;
          }
        return RDS_BFR_DONE;
      }
      //=========================================

      //==============================================================================
      // В график внесены изменения для поддержки сохранения и загрузки состояния
      //==============================================================================

      //=========================================
      // Простой график – личная область данных
      //=========================================
      class TSimplePlotData
      { private:
          // Запомненные координаты поля графика
          int Gr_x1,Gr_x2,Gr_y1,Gr_y2;
          // Масштаб окна на момент последнего рисования
          double OldZoom;

          // Индекс последнего нарисованного отсчета
          int LastDrawnIndex;

          // Настроечные параметры графика (цвета, шаг и т.п.)
          double TimeStep;          // Шаг записи отсчетов
          RDS_SERVFONTPARAMS Font;  // Шрифт чисел на осях
          COLORREF BorderColor;     // Цвет рамки вокруг блока
          COLORREF FillColor;       // Цвет фона блока
          COLORREF PlotBorderColor; // Цвет рамки поля графика
          COLORREF PlotFillColor;   // Цвет фона поля графика
          COLORREF LineColor;       // Цвет лини графика
          int LineWidth;            // Толщина линии графика

          // Ось X
          double Xmin,Xmax;  // Диапазон
          double XGridStep;  // Шаг чисел на осях
          int XNumDecimal;   // Дробная часть чисел на осях

          // Ось Y
          double Ymin,Ymax;  // Диапазон
          double YGridStep;  // Шаг чисел на осях
          int YNumDecimal;   // Дробная часть чисел на осях

          // Массивы для хранения отсчетов графика
          double *Times;     // Массив отсчетов времени
          double *Values;    // Массив значений
          int Count;         // Размер массивов
          int NextIndex;     // Индекс для записи следующего значения
          double NextTime;   // Время записи следующего значения

          RDS_PDYNVARLINK Time; // Связь с динамической переменной
                                // времени ("DynTime")
        public:
          // Функция сброса запомненного масштаба последнего рисования
          void ResetCoords(void){OldZoom=-1.0;};

          // Функция отведения массивов отсчетов
          void AllocateArrays(void);
          // Функция освобождения массивов отсчетов
          void ClearArrays(void);
          // Добавление очередной точки в массив отсчетов графика
          void AddPoint(double v);

          int Setup(void);           // Функция настройки параметров
          void SaveText(void);       // Функция сохранения параметров
          void LoadText(char *text); // Функция загрузки параметров
          void Draw(RDS_PDRAWDATA DrawData);     // Функция рисования
          void DrawFast(RDS_PDRAWDATA DrawData); // Функция быстрого рисования
          // Рисование иконки при отсутствии доступа к DynTime
          void DrawAdditional(RDS_PDRAWDATA DrawData);
          // Поиск индекса отсчета, соответствующего времени t
          int FindTimeIndex(double t);
          // Вывод всплывающей полсказки
          void PopupHint(RDS_PPOPUPHINTDATA hintdata);

          void SaveState(void); // Функция записи состояния
          void LoadState(void); // Функция загрузки состояния
          TSimplePlotData(void); // Конструктор класса
          ~TSimplePlotData();    // Деструктор класса
      };
      //=========================================

      // Модель блока
      extern "C" __declspec(dllexport) int RDSCALL SimplePlot(
            int CallMode,
            RDS_PBLOCKDATA BlockData,
            LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))
        // Указатель на личную область, приведенный к правильному типу
        TSimplePlotData *data=(TSimplePlotData*)(BlockData->BlockData);

        switch(CallMode)
          { // Инициализация блока
            case RDS_BFM_INIT:
              BlockData->BlockData=new TSimplePlotData();
              break;

            // Очистка данных блока
            case RDS_BFM_CLEANUP:
              delete data;
              break;

            // Проверка типов статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Функция настройки параметров
            case RDS_BFM_SETUP:
              data->ResetCoords(); // Сброс запомненных значений
              return data->Setup();

            // Загрузка параметров в текстовом формате
            case RDS_BFM_LOADTXT:
              data->LoadText((char*)ExtParam);
              data->ResetCoords(); // Сброс запомненных значений
              break;

            // Созранение параметров в текстовом формате
            case RDS_BFM_SAVETXT:
              data->SaveText();
              break;

            // Изменение размеров блока
            case RDS_BFM_RESIZE:
              data->ResetCoords(); // Сброс запомненных значений
              break;

            // Рисование внешнего вида блока
            case RDS_BFM_DRAW:
              //data->Draw((RDS_PDRAWDATA)ExtParam);
              data->DrawFast((RDS_PDRAWDATA)ExtParam);
              break;

            // Дополнительное рисование
            case RDS_BFM_DRAWADDITIONAL:
              data->DrawAdditional((RDS_PDRAWDATA)ExtParam);
              break;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart)
                data->AllocateArrays(); // Первый запуск
              break;

            // Сброс расчета
            case RDS_BFM_RESETCALC:
              data->ClearArrays();
              break;

            // Реакция на изменение динамической переменной
            case RDS_BFM_DYNVARCHANGE:
              data->AddPoint(x);
              break;

            // Всплывающая подсказка
            case RDS_BFM_POPUPHINT:
              data->PopupHint((RDS_PPOPUPHINTDATA)ExtParam);
              break;

            // Запись состояния блока
            case RDS_BFM_SAVESTATE:
              data->SaveState();
              break;
            // Загрузка состояния блока
            case RDS_BFM_LOADSTATE:
              data->LoadState();
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений для переменных
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      // Конструктор класса личной области данных графика
      TSimplePlotData::TSimplePlotData(void)
      { // Инициализация OldZoom: значение -1 приведет к принудительному
        // вычислению Gr_x1,Gr_x2,Gr_y1 и Gr_y2 в ближайшем вызове
        // функции Draw
        OldZoom=-1.0;
        // Инициализация LastDrawnIndex
        LastDrawnIndex=-1;

        // Присвоение начальных значений параметрам
        TimeStep=0.1;           // Шаг записи
        BorderColor=0;          // Цвет рамки вокруг блока
        FillColor=0xffffff;     // Цвет фона блока
        PlotBorderColor=0;      // Цвет рамки окна графика и сетки
        PlotFillColor=0xffffff; // Цвет окна графика
        LineColor=0;            // Цвет линии графика
        LineWidth=1;            // Толщина линии графика

        // Параметры шрифта
        Font.servSize=sizeof(Font);
        strcpy(Font.Name,"Arial");
        Font.SizePriority=FALSE;
        Font.Height=15;
        Font.Color=0;
        Font.Bold=Font.Italic=Font.Underline=Font.StrikeOut=FALSE;
        Font.CharSet=DEFAULT_CHARSET;

        // Диапазоны осей, шаг сетки, число десятичных знаков
        // в числах на осях
        Xmin=0.0; Xmax=10.0;
        XGridStep=5.0;
        XNumDecimal=0;
        Ymin=-1.0; Ymax=1.0;
        YGridStep=0.5;
        YNumDecimal=1;

        // Обнуление указателей на массивы и их размера
        // (массивы еще не отведены)
        Times=Values=NULL;
        Count=NextIndex=0;
        NextTime=Xmin;

        // Подписка на динамическую переменную времени
        Time=rdsSubscribeToDynamicVar(
                 RDS_DVPARENT,// В родительской подсистеме
                 "DynTime",   // Имя переменной
                 "D",         // Тип переменной (double)
                 TRUE);       // Искать по иерархии
      }
      //=========================================

      // Деструктор класса
      TSimplePlotData::~TSimplePlotData()
      { rdsUnsubscribeFromDynamicVar(Time); // Прекратить подписку
        ClearArrays();                      // Освободить массивы
      }
      //=========================================

      // Рисование внешнего вида блока
      void TSimplePlotData::Draw(RDS_PDRAWDATA DrawData)
      { // Вспомогательные переменные
        // int Gr_x1,Gr_x2,Gr_y1,Gr_y2; - эти переменные стали
        //                                полями класса
        int x1,y1,x2,y2,textheight,w1,w2;
        char buf[80];

        // Рамка графика
        rdsXGSetPenStyle(0,PS_SOLID,1,BorderColor,R2_COPYPEN);
        rdsXGSetBrushStyle(0,RDS_GFS_SOLID,FillColor);
        rdsXGRectangle(DrawData->Left,DrawData->Top,
                       DrawData->Left+DrawData->Width,
                       DrawData->Top+DrawData->Height);

        // Необходимо вычислить координаты поля графика относительно
        // верхнего левого угла блока

        // Установка параметров шрифта с учетом масштаба
        rdsXGSetFontByParStr(&Font,DrawData->DoubleZoom);
        rdsXGGetTextSize("0",NULL,&textheight);
        if(DrawData->DoubleZoom!=OldZoom) // Масштаб изменен
          { // Зазор сверху – половина высоты цифры + 1 точка
            Gr_y1=textheight/2+1;
            // Зазор снизу – полная высота цифры + 1 точка
            Gr_y2=DrawData->Height-textheight-1;
            // Зазор слева – ширина самого длинного числа вертикальной
            // оси или половина ширины Xmin
            sprintf(buf," %.*lf ",YNumDecimal,Ymin);
            rdsXGGetTextSize(buf,&w1,NULL); // Ширина Ymin
            sprintf(buf," %.*lf ",YNumDecimal,Ymax);
            rdsXGGetTextSize(buf,&w2,NULL); // Ширина Ymax
            if(w2>w1) w1=w2;
            sprintf(buf," %.*lf ",XNumDecimal,Xmin);
            rdsXGGetTextSize(buf,&w2,NULL); // Ширина Xmin
            w2/=2;
            if(w2>w1) w1=w2;
            Gr_x1=w1;
            // Зазор справа – половина ширины Xmax
            sprintf(buf," %.*lf ",XNumDecimal,Xmax);
            rdsXGGetTextSize(buf,&w2,NULL); // Ширина Xmax
            w2/=2;
            Gr_x2=DrawData->Width-w2;

            // Запоминание нового масштаба
            OldZoom=DrawData->DoubleZoom;
          } // if(DrawData->DoubleZoom!=OldZoom)

        // Абсолютные (на рабочем поле) координаты поля графика
        x1=DrawData->Left+Gr_x1;
        x2=DrawData->Left+Gr_x2;
        y1=DrawData->Top+Gr_y1;
        y2=DrawData->Top+Gr_y2;

        if(x1>=x2 || y1>=y2) // Негде рисовать
          return;

        // Прямоугольник поля графика
        rdsXGSetPenStyle(0,PS_SOLID,1,PlotBorderColor,R2_COPYPEN);
        rdsXGSetBrushStyle(0,RDS_GFS_SOLID,PlotFillColor);
        rdsXGRectangle(x1,y1,x2,y2);

        // Установка пунктирного стиля линии
        rdsXGSetPenStyle(0,PS_DOT,1,PlotBorderColor,R2_COPYPEN);
        rdsXGSetBrushStyle(0,RDS_GFS_EMPTY,0); // Без заливки

        // Горизонтальная ось с сеткой
        for(double x=Xmin;x<=Xmax+XGridStep*0.5;x+=XGridStep)
          { // ix - координата линии на рабочем поле
            int ix=x1+(x-Xmin)*(x2-x1)/(Xmax-Xmin);
            if(ix>x1 && ix<x2) // Чертим вертикальную линию
              { rdsXGMoveTo(ix,y1);
                rdsXGLineTo(ix,y2);
              }
            // Вывод числа на оси под полем
            sprintf(buf,"%.*lf",XNumDecimal,x);
            rdsXGGetTextSize(buf,&w1,NULL);
            rdsXGTextOut(ix-w1/2,y2,buf);
          }
        // Вертикальная ось с сеткой
        for(double y=Ymin;y<=Ymax+YGridStep*0.5;y+=YGridStep)
          { // iy - координата линии на рабочем поле
            int iy=y2-(y-Ymin)*(y2-y1)/(Ymax-Ymin);
            if(iy>y1 && iy<y2) // Чертим горизонтальную линию
              { rdsXGMoveTo(x1,iy);
                rdsXGLineTo(x2,iy);
              }
            // Вывод числа на оси слева от поля
            sprintf(buf,"%.*lf ",YNumDecimal,y);
            rdsXGGetTextSize(buf,&w1,&textheight);
            rdsXGTextOut(x1-w1-2,iy-textheight/2,buf);
          }

        // Если массивы не пустые – рисовать график
        if(Count)
          { RECT r;
            // Установить область отсечения рисования по полю графика
            r.left=x1+1;
            r.top=y1+1;
            r.right=x2-1;
            r.bottom=y2-1;
            rdsXGSetClipRect(&r);

            // Установить сплошной стиль линии, заданный для
            // графика цвет и толщину линии с учетом масштаба
            rdsXGSetPenStyle(0,PS_SOLID,
                LineWidth*DrawData->DoubleZoom,
                LineColor,R2_COPYPEN);

            // Строим ломанную линию по отсчетам из массивов
            for(int i=0;i<NextIndex;i++)
              { // Преобразуем вещественные отсчеты в целочисленные
                // координаты на рабочем поле
                int ix=x1+(Times[i]-Xmin)*(x2-x1)/(Xmax-Xmin),
                    iy=y2-(Values[i]-Ymin)*(y2-y1)/(Ymax-Ymin);
                if(i) // Не первая точка – строим линию от предыдущей
                  rdsXGLineTo(ix,iy);
                else // Первая точка графика – делаем ее текущей
                  rdsXGMoveTo(ix,iy);
              }

            // Запоминаем последний нарисованный отсчет
            LastDrawnIndex=NextIndex-1;

            // Отмена отсечения
            rdsXGSetClipRect(NULL);
          }
      }
      //=========================================

      // Рисование изменений
      void TSimplePlotData::DrawFast(RDS_PDRAWDATA DrawData)
      { int x1,y1,x2,y2;

        // Если RDS требует полного рисования, или полное рисование
        // еще не проводилось, вызывается старая функция Draw
        if(DrawData->FullDraw || DrawData->DoubleZoom!=OldZoom)
          { Draw(DrawData);
            return;
          }

        // Вычисление абсолютных координат поля графика
        x1=DrawData->Left+Gr_x1;
        x2=DrawData->Left+Gr_x2;
        y1=DrawData->Top+Gr_y1;
        y2=DrawData->Top+Gr_y2;

        if(x1>=x2 || y1>=y2) // Негде рисовать
          return;

        // Если массивы не пустые – рисовать график, начиная
        // с последней уже нарисованной точки
        if(Count)
          { RECT r;
            // Установить область отсечения рисования по полю графика
            r.left=x1+1;
            r.top=y1+1;
            r.right=x2-1;
            r.bottom=y2-1;
            rdsXGSetClipRect(&r);

            // Установить сплошной стиль линии, заданный для
            // графика цвет и толщину линии с учетом масштаба
            rdsXGSetPenStyle(0,PS_SOLID,
                LineWidth*DrawData->DoubleZoom,
                LineColor,R2_COPYPEN);

            // Проверка допустимости LastDrawnIndex – на всякий случай
            if(LastDrawnIndex<0) LastDrawnIndex=0;

            // Строим ломанную линию по отсчетам из массивов,
            // начиная с LastDrawIndex
            for(int i=LastDrawnIndex;i<NextIndex;i++)
              { int ix=x1+(Times[i]-Xmin)*(x2-x1)/(Xmax-Xmin),
                    iy=y2-(Values[i]-Ymin)*(y2-y1)/(Ymax-Ymin);
                if(i!=LastDrawnIndex) rdsXGLineTo(ix,iy);
                else rdsXGMoveTo(ix,iy);
              }

            // Запоминаем последний нарисованный отсчет
            LastDrawnIndex=NextIndex-1;

            // Отмена отсечения
            rdsXGSetClipRect(NULL);
          }
      }
      //=========================================

      // Дополнительное рисование
      void TSimplePlotData::DrawAdditional(RDS_PDRAWDATA DrawData)
      {
        // Проверка доступа к переменной времени
        if(Time==NULL || Time->Data==NULL) // Доступа нет
          { int w,h;
            // Константа, указывающая на стандартную иконку RDS
            DWORD icon=RDS_STDICON_YELCIRCEXCLAM;
            // Определяем размер иконки и выводим ее в центре блока
            if(rdsXGGetStdIconSize(icon,&w,&h))
              rdsXGDrawStdIcon(DrawData->Left+(DrawData->Width-w)/2,
                               DrawData->Top+(DrawData->Height-h)/2,
                               icon);
          }
      }
      //=========================================

      // Отведение памяти под массивы
      void TSimplePlotData::AllocateArrays(void)
      { // Сначала нужно очистить массивы, если они были отведены ранее
        ClearArrays();
        // При нулевом или отрицательном шаге записи отсчетов
        // работа блока невозможна
        if(TimeStep<=0.0) return;
        // Вычисление требуемого числа отсчетов по диапазону оси
        // времени и шагу записи
        Count=(Xmax-Xmin)/TimeStep+1;
        // Число отсчетов должно быть положительным
        if(Count<=0) {Count=0; return; }
        // Отведение памяти – по Count чисел double
        Times=new double[Count];
        Values=new double[Count];
        // Первый свободный индкс массива - 0
        NextIndex=0;
        // Момент записи отсчета – начало диапазона оси времени
        NextTime=Xmin;
      }
      //=========================================

      // Освобождение массивов
      void TSimplePlotData::ClearArrays(void)
      { if(Count) // Массивы были отведены
          { delete[] Times;
            delete[] Values;
          }
        // Обнуление указателей и Count
        Times=Values=NULL;
        Count=NextIndex=0;
      }
      //=========================================

      // Добавление отсчета в массив
      void TSimplePlotData::AddPoint(double v)
      { double t;
        if(NextIndex>=Count) // Весь массив заполнен
          return;
        if(Time==NULL || Time->Data==NULL) // Нет доступа к “DynTime"
          return;
        // Получение значения времени из “DynTime"
        t=*((double*)Time->Data);
        if(t<NextTime) // Еще не пришло время писать отсчет
          return;
        // Достигнуто время записи
        Values[NextIndex]=v;
        Times[NextIndex]=t;
        NextIndex++; // Следующий отсчет – в следующий индекс
        NextTime+=TimeStep; // Время записи следующего отсчета
      }
      //=========================================

      // Сохранение параметров в текстовом виде
      void TSimplePlotData::SaveText(void)
      { RDS_HOBJECT ini;
        char *str;

        // Создание объекта для работы с текстом
        ini=rdsINICreateTextHolder(TRUE);

        // Создание в тексте секции "[General]"
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"General");
        // Запись в секцию различных параметров
        rdsINIWriteDouble(ini,"TimeStep",TimeStep);
        rdsINIWriteDouble(ini,"Xmin",Xmin);
        rdsINIWriteDouble(ini,"Xmax",Xmax);
        rdsINIWriteDouble(ini,"XGridStep",XGridStep);
        rdsINIWriteInt(ini,"XNumDecimal",XNumDecimal);
        rdsINIWriteDouble(ini,"Ymin",Ymin);
        rdsINIWriteDouble(ini,"Ymax",Ymax);
        rdsINIWriteDouble(ini,"YGridStep",YGridStep);
        rdsINIWriteInt(ini,"YNumDecimal",YNumDecimal);

        // Создание в тексте секции "[Visuals]"
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Visuals");
        // Запись в секцию различных параметров
        rdsINIWriteInt(ini,"BorderColor",(int)BorderColor);
        rdsINIWriteInt(ini,"FillColor",(int)FillColor);
        rdsINIWriteInt(ini,"PlotBorderColor",(int)PlotBorderColor);
        rdsINIWriteInt(ini,"PlotFillColor",(int)PlotFillColor);
        rdsINIWriteInt(ini,"LineColor",(int)LineColor);
        rdsINIWriteInt(ini,"LineWidth",LineWidth);

        // Преобразование описания шрифта в строку для сохранения
        str=rdsStructToFontText(&Font,NULL);
        // Запись строки с описанием шрифта
        rdsINIWriteString(ini,"Font",str);
        rdsFree(str); // Освобождение памяти, занятой строкой

        // Запись сформированного текста в файл схемы или буфер обмена
        rdsCommandObject(ini,RDS_HINI_SAVEBLOCKTEXT);

        // Уничтожение вспомогательного объекта
        rdsDeleteObject(ini);
      }
      //=========================================

      // Загрузка параметров в текстовом виде из строки text
      void TSimplePlotData::LoadText(char *text)
      { RDS_HOBJECT ini;
        char *str;

        // Создание объекта для работы с текстом
        ini=rdsINICreateTextHolder(TRUE);

        // Загрузка текста в объект
        rdsSetObjectStr(ini,RDS_HINI_SETTEXT,0,text);

        // Если в тексте есть секция "General", загрузить из нее данные
        if(rdsINIOpenSection(ini,"General"))
          { TimeStep=rdsINIReadDouble(ini,"TimeStep",TimeStep);
            Xmin=rdsINIReadDouble(ini,"Xmin",Xmin);
            Xmax=rdsINIReadDouble(ini,"Xmax",Xmax);
            XGridStep=rdsINIReadDouble(ini,"XGridStep",XGridStep);
            XNumDecimal=rdsINIReadInt(ini,"XNumDecimal",XNumDecimal);
            Ymin=rdsINIReadDouble(ini,"Ymin",Ymin);
            Ymax=rdsINIReadDouble(ini,"Ymax",Ymax);
            YGridStep=rdsINIReadDouble(ini,"YGridStep",YGridStep);
            YNumDecimal=rdsINIReadInt(ini,"YNumDecimal",YNumDecimal);
          }
        // Если в тексте есть секция "Visuals", загрузить из нее данные
        if(rdsINIOpenSection(ini,"Visuals"))
          { BorderColor=(COLORREF)rdsINIReadInt(ini,"BorderColor",
              (int)BorderColor);
            FillColor=(COLORREF)rdsINIReadInt(ini,"FillColor",
              (int)FillColor);
            PlotBorderColor=(COLORREF)rdsINIReadInt(ini,
              "PlotBorderColor",(int)PlotBorderColor);
            PlotFillColor=(COLORREF)rdsINIReadInt(ini,"PlotFillColor",
              (int)PlotFillColor);
            LineColor=(COLORREF)rdsINIReadInt(ini,"LineColor",
              (int)LineColor);
            LineWidth=(COLORREF)rdsINIReadInt(ini,"LineWidth",LineWidth);
            str=rdsINIReadString(ini,"Font","",NULL);
            if(str) rdsFontTextToStruct(str,NULL,&Font);
          }

        // Уничтожение вспомогательного объекта
        rdsDeleteObject(ini);
      }
      //=========================================

      // Функция настройки параметров блока
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      int TSimplePlotData::Setup(void)
      { RDS_HOBJECT window;
        BOOL ok;
        char *str;

        // Создание окна
        window=rdsFORMCreate(TRUE,-1,-1,"Простой график");

        // Вкладка "Оси"
        rdsFORMAddTab(window,1,"Оси");

        rdsFORMAddEdit(window,1,100,
            RDS_FORMCTRL_EDIT | RDS_FORMFLAG_LINE,"Шаг записи",50);
        rdsSetObjectDouble(window,100,RDS_FORMVAL_VALUE,TimeStep);

        // Текстовая метка без возможности ввода
        rdsFORMAddEdit(window,1,1,RDS_FORMCTRL_LABEL,"Ось X:",0);

        // Диапазон (два поля ввода в одной строке)
        rdsFORMAddEdit(window,1,2,RDS_FORMCTRL_RANGEEDIT,"Диапазон",90);
        rdsSetObjectDouble(window,2,RDS_FORMVAL_VALUE,Xmin);
        rdsSetObjectDouble(window,2,RDS_FORMVAL_RANGEMAX,Xmax);

        rdsFORMAddEdit(window,1,3,RDS_FORMCTRL_EDIT,"Шаг сетки",50);
        rdsSetObjectDouble(window,3,RDS_FORMVAL_VALUE,XGridStep);

        // Поле ввода со стрелками увеличения/уменьшения
        rdsFORMAddEdit(window,1,4,
            RDS_FORMCTRL_UPDOWN | RDS_FORMFLAG_LINE,
            "Дробная часть чисел",50);
        rdsSetObjectInt(window,4,RDS_FORMVAL_VALUE,XNumDecimal);
        rdsSetObjectInt(window,4,RDS_FORMVAL_UPDOWNMIN,0);
        rdsSetObjectInt(window,4,RDS_FORMVAL_UPDOWNMAX,5);
        rdsSetObjectInt(window,4,RDS_FORMVAL_UPDOWNINC,1);

        // Текстовая метка без возможности ввода
        rdsFORMAddEdit(window,1,5,RDS_FORMCTRL_LABEL,"Ось Y:",0);

        rdsFORMAddEdit(window,1,6,RDS_FORMCTRL_RANGEEDIT,"Диапазон",90);
        rdsSetObjectDouble(window,6,RDS_FORMVAL_VALUE,Ymin);
        rdsSetObjectDouble(window,6,RDS_FORMVAL_RANGEMAX,Ymax);

        rdsFORMAddEdit(window,1,7,RDS_FORMCTRL_EDIT,"Шаг сетки",50);
        rdsSetObjectDouble(window,7,RDS_FORMVAL_VALUE,YGridStep);

        rdsFORMAddEdit(window,1,8,RDS_FORMCTRL_UPDOWN,
            "Дробная часть чисел",50);
        rdsSetObjectInt(window,8,RDS_FORMVAL_VALUE,YNumDecimal);
        rdsSetObjectInt(window,8,RDS_FORMVAL_UPDOWNMIN,0);
        rdsSetObjectInt(window,8,RDS_FORMVAL_UPDOWNMAX,5);
        rdsSetObjectInt(window,8,RDS_FORMVAL_UPDOWNINC,1);

        // Вкладка "Внешний вид"
        rdsFORMAddTab(window,2,"Внешний вид");

        rdsFORMAddEdit(window,2,9,RDS_FORMCTRL_COLOR,
            "Цвет рамки блока",50);
        rdsSetObjectInt(window,9,RDS_FORMVAL_VALUE,(int)BorderColor);
        rdsFORMAddEdit(window,2,10,
            RDS_FORMCTRL_COLOR | RDS_FORMFLAG_LINE,"Цвет фона блока",50);
        rdsSetObjectInt(window,10,RDS_FORMVAL_VALUE,(int)FillColor);

        rdsFORMAddEdit(window,2,11,RDS_FORMCTRL_COLOR,
             "Цвет рамки графика и сетки",50);
        rdsSetObjectInt(window,11,RDS_FORMVAL_VALUE,
            (int)PlotBorderColor);
        rdsFORMAddEdit(window,2,12,RDS_FORMCTRL_COLOR,
            "Цвет фона графика",50);
        rdsSetObjectInt(window,12,RDS_FORMVAL_VALUE,(int)PlotFillColor);
        rdsFORMAddEdit(window,2,13,RDS_FORMCTRL_COLOR,
            "Цвет линии графика",50);
        rdsSetObjectInt(window,13,RDS_FORMVAL_VALUE,(int)LineColor);
        rdsFORMAddEdit(window,2,14,
            RDS_FORMCTRL_UPDOWN | RDS_FORMFLAG_LINE,
            "Толщина линии графика",50);
        rdsSetObjectInt(window,14,RDS_FORMVAL_VALUE,LineWidth);
        rdsSetObjectInt(window,14,RDS_FORMVAL_UPDOWNMIN,0);
        rdsSetObjectInt(window,14,RDS_FORMVAL_UPDOWNMAX,5);
        rdsSetObjectInt(window,14,RDS_FORMVAL_UPDOWNINC,1);

        // Кнопка открытия диалога выбора шрифта
        rdsFORMAddEdit(window,2,15,RDS_FORMCTRL_FONTSELECT,
            "Шрифт чисел",0);
        // Преобразование шрифта в строку и занесение в поле ввода
        str=rdsStructToFontText(&Font,NULL);
        rdsSetObjectStr(window,15,RDS_FORMVAL_VALUE,str);
        rdsFree(str);

        // Открытие окна
        ok=rdsFORMShowModalEx(window,NULL);
        if(ok)
          { // Нажата кнопка OK - запись параметров обратно в блок
            Xmin=rdsGetObjectDouble(window,2,RDS_FORMVAL_VALUE);
            Xmax=rdsGetObjectDouble(window,2,RDS_FORMVAL_RANGEMAX);
            XGridStep=rdsGetObjectDouble(window,3,RDS_FORMVAL_VALUE);
            XNumDecimal=rdsGetObjectInt(window,4,RDS_FORMVAL_VALUE);

            TimeStep=rdsGetObjectDouble(window,100,RDS_FORMVAL_VALUE);

            Ymin=rdsGetObjectDouble(window,6,RDS_FORMVAL_VALUE);
            Ymax=rdsGetObjectDouble(window,6,RDS_FORMVAL_RANGEMAX);
            YGridStep=rdsGetObjectDouble(window,7,RDS_FORMVAL_VALUE);
            YNumDecimal=rdsGetObjectInt(window,8,RDS_FORMVAL_VALUE);

            BorderColor=(COLORREF)rdsGetObjectInt(window,9,RDS_FORMVAL_VALUE);
            FillColor=(COLORREF)rdsGetObjectInt(window,10,RDS_FORMVAL_VALUE);
            PlotBorderColor=(COLORREF)rdsGetObjectInt(window,11,RDS_FORMVAL_VALUE);
            PlotFillColor=(COLORREF)rdsGetObjectInt(window,12,RDS_FORMVAL_VALUE);
            LineColor=(COLORREF)rdsGetObjectInt(window,13,RDS_FORMVAL_VALUE);
            LineWidth=rdsGetObjectInt(window,14,RDS_FORMVAL_VALUE);

            // Получение параметров шрифта из строки
            str=rdsGetObjectStr(window,15,RDS_FORMVAL_VALUE);
            rdsFontTextToStruct(str,NULL,&Font);
          }
        // Уничтожение окна
        rdsDeleteObject(window);
        // Возвращаемое значение
        return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE;
      }
      //=========================================

      // Всплывающая подсказка
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TSimplePlotData::PopupHint(RDS_PPOPUPHINTDATA hintdata)
      { int x1,x2,y1,y2,index;
        double t;
        char *text,*str_t,*str_v;

        // Проверка доступа к переменной времени
        if(Time==NULL || Time->Data==NULL)
          { rdsSetHintText("ОШИБКА: в схеме нет переменной DynTime");
            return;
          }

        // Определение абсолютных координат поля графика
        x1=hintdata->Left+Gr_x1;
        x2=hintdata->Left+Gr_x2;
        y1=hintdata->Top+Gr_y1;
        y2=hintdata->Top+Gr_y2;

        // Если курсор мыши не попадает в поле графика,
        // подсказку выводить не нужно
        if(hintdata->x<x1 || hintdata->x>x2 ||
           hintdata->y<y1 || hintdata->y>y2)
          return;

        // Преобразование экранной горизонтальной координаты
        // в значение времени согласно масштабу графика
        t=(hintdata->x-x1)*(Xmax-Xmin)/(x2-x1)+Xmin;

        // Поиск отсчета, соответствующего этому моменту времени
        index=FindTimeIndex(t);
        if(index<0) return; // Ошибка – отчет не найден

        // Преобразование времени и значения отсчета в строки
        str_t=rdsDtoA(Times[index],-1,NULL);
        str_v=rdsDtoA(Values[index],-1,NULL);
        // Формирование текста подсказки
        text=rdsDynStrCat("Время: ",str_t,FALSE);
        rdsAddToDynStr(&text,"\nЗначение: ",FALSE);
        rdsAddToDynStr(&text,str_v,FALSE);
        // Установка текста подсказки
        rdsSetHintText(text);
        // Освобождение динамических строк
        rdsFree(text);
        rdsFree(str_t);
        rdsFree(str_v);

        // Изменение параметров подсказки таким образом, чтобы при
        // смещении курсора на одну точку она вывелась снова
        hintdata->HZLeft=hintdata->x;
        hintdata->HZTop=hintdata->y;
        hintdata->HZWidth=hintdata->HZHeight=1;
        // Задержка гашения подсказки – одна минута
        hintdata->HideTimeout=60000;
      }
      //=========================================

      /*
      // Поиск отсчета, соответствующего времени t
      // (линейный поиск)
      int TSimplePlotData::FindTimeIndex(double t)
      {
        if(Count==0) // Массивы пусты
          return -1;

        if(t<=Times[0]) // t раньше начала массива
          return 0; // Ближайший индекс - 0

        // Поиск первого индекса, большего t
        for(int i=1;i<NextIndex;i++)
          if(Times[i]>t) // t между i-1 и i
            { double d1=fabs(t-Times[i-1]),
                     d2=fabs(t-Times[i]);
              return (d1<d2)?(i-1):i; // Возвращаем ближайший
            }
        // Ничего не нашли – значит, t>Times[NextIndex-1]
        return NextIndex-1;
      }
      //=========================================
      */

      // Поиск отсчета, соответствующего времени t
      // (метод деления пополам)
      int TSimplePlotData::FindTimeIndex(double t)
      { int L,R;
        double dl,dr;

        if(Count==0 || NextIndex==0) // Нет данных в массивах
          return -1;
        if(NextIndex==1) // В массиве единственный отсчет
          return 0;

        // В массиве по крайней мере два значения – проверяем границы
        if(t<=Times[0])// t меньше первого отсчета
          return 0;
        if(t>=Times[NextIndex-1]) // t больше последнего отсчета
          return NextIndex-1;

        // t - внутри диапазона массива
        L=0; R=NextIndex-1;
        while(L<R-1)
          { int m=(L+R)/2;
            if(Times[m]<t) L=m; else R=m;
          }
        // t лежит между L и R
        dl=fabs(t-Times[L]),
        dr=fabs(t-Times[R]);
        return (dl<dr)?L:R; // Возвращаем ближайший
      }
      //=========================================

      // Запись состояния блока
      void TSimplePlotData::SaveState(void)
      {
        // Макрос для записи одной переменной
        #define WRITEBLOCKDATAVAR(v) rdsWriteBlockData(&v,sizeof(v))

        WRITEBLOCKDATAVAR(TimeStep);
        WRITEBLOCKDATAVAR(Xmin);
        WRITEBLOCKDATAVAR(Xmax);
        WRITEBLOCKDATAVAR(XGridStep);

        // Запись массивов отсчетов
        rdsWriteBlockData(Times,Count*sizeof(double));
        rdsWriteBlockData(Values,Count*sizeof(double));

        WRITEBLOCKDATAVAR(NextIndex);
        WRITEBLOCKDATAVAR(NextTime);

        // Отмена макроса
        #undef WRITEBLOCKDATAVAR
      }
      //=========================================
      // Загрузка состояния блока
      void TSimplePlotData::LoadState(void)
      {
        // Макрос для загрузки одной переменной
        #define READBLOCKDATAVAR(v) rdsReadBlockData(&v,sizeof(v))

        READBLOCKDATAVAR(TimeStep);
        READBLOCKDATAVAR(Xmin);
        READBLOCKDATAVAR(Xmax);
        READBLOCKDATAVAR(XGridStep);

        // Отведение массивов перед загрузкой
        AllocateArrays();
        rdsReadBlockData(Times,Count*sizeof(double));
        rdsReadBlockData(Values,Count*sizeof(double));

        READBLOCKDATAVAR(NextIndex);
        READBLOCKDATAVAR(NextTime);

        // Отмена макроса
        #undef READBLOCKDATAVAR

        // Сброс параметров оптимизации рисования
        OldZoom=-1.0;
        LastDrawnIndex=-1;
      }
      //=========================================