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

Описания RDS

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

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

§2.16. Программное изменение схемы

§2.16.2. Добавление и удаление блоков и связей

Полный исходный текст на языке C++ для библиотеки (DLL) с моделью блока, создающего в своей родительской подсистеме фрагмент схемы по описаниям соединений в текстовом файле.

      // Программное добавление и удаление блоков и связей
      #include <windows.h>
      #include <stdio.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;
      }
      //========= Конец главной функции =========

      //=========================================
      // "Обрезание" отрезка по прямоугольнику
      // (вспомогательная функция)
      //=========================================
      void ClipLineByRect(
          int x1,int y1,   // Центр прямоугольника
          int w,int h,     // Размеры прямоугольника
          int x2,int y2,   // Конец отрезка
          int *px,int *py) // Координаты точки на границе
      { int xv,yg,xres,yres;
        double tv,tg,t;
        int status=0; // Наличие точек пересечения

        if(x2!=x1) // Отрезок не строго вертикален
          { if(x2>x1) // Отрезок уходит вправо
              xv=x1+w/2;
            else // Отрезок уходит влево
              xv=x1-w/2;
            // Параметр точки пересечения с вертикальной прямой
            tv=((double)(xv-x1))/((double)(x2-x1));
            status=1; // Есть точка пересечения с вертикалью
          }

        if(y2!=y1) // Отрезок не строго горизонтален
          { if(y2>y1) // Отрезок уходит вниз
              yg=y1+h/2;
            else // Отрезок уходит вверх
              yg=y1-h/2;
            // Параметр точки пересечения с горизонтальной прямой
            tg=((double)(yg-y1))/((double)(y2-y1));
            status+=10; // Есть пересечение с горизонталью
          }

        switch(status)
          { case 0:  // Ошибка: (x1,y1)==(x2,y2)
              *px=x1; *py=y1; return;
            case 1:  // Строго горизонтальный отрезок
              t=tv; break;
            case 10: // Строго вертикальный отрезок
              t=tg; break;
            default: // Диагональный отрезок
             t=(tv<tg)?tv:tg;
          }
        // Вычисление координат по параметру t
        *px=x1+t*(x2-x1);
        *py=y1+t*(y2-y1);
      }
      //=========================================

      //=========================================
      // Вывод сообщения об ошибке в файле
      // (вспомогательная функция)
      //=========================================
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void FileErrorMessage(int line,      // Номер строки или 0
                            char *file,    // Имя файла или NULL
                            char *message) // Описание ошибки
      { char *msg=NULL; // Здесь будет формироваться текст

        if(message) // Есть текст описания
          rdsAddToDynStr(&msg,message,FALSE); // Копируем в msg
        if(file) // Есть имя файла
          { // Добавляем к динамической строке msg
            rdsAddToDynStr(&msg,"\nФайл: ",FALSE);
            rdsAddToDynStr(&msg,file,FALSE);
          }
        if(line>0) // Есть номер строки
          { char buf[80]; // Преобразуем в текст и добавляем к msg
            sprintf(buf,"\nСтрока: %d",line);
            rdsAddToDynStr(&msg,buf,FALSE);
          }
        // Выводим сообщение пользователю
        rdsMessageBox(msg,"Ошибка",MB_OK|MB_ICONWARNING);
        // Освобождаем динамическую строку msg
        rdsFree(msg);
      }
      //=========================================

      //=========================================
      // Блок, добавляющий и удаляющий блоки и
      // связи в своей подсистеме
      //=========================================

      // Личная область данных блока
      class TLoadGraphData
      { public:
          RDS_HOBJECT List; // Объект-список добавленных блоков и связей

          // Функция добавления блоков по списку из файла
          BOOL LoadBlocks(RDS_BHANDLE thisblock,char *blockfile,
                          char *blocklist);
          // Функция добавления связей по списку из файла
          void LoadConnections(RDS_BHANDLE thisblock,char *connlist);
          // Функция удаления добавленного
          void DeleteByList(void);

          // Функция настройки (в нее передаются номера статических
          // переменных, в которых хранятся параметры блока)
          int Setup(RDS_BHANDLE Block,int BlockFileVar,
                    int BlockLstVar,int ConnLstVar);

          // Конструктор класса
          TLoadGraphData(void){List=NULL;};
          // Деструктор класса
          ~TLoadGraphData(){rdsDeleteObject(List);};
      };
      //=========================================

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

        // Создаем окно
        window=rdsFORMCreate(FALSE,-1,-1,"Добавление блоков");

        //----- Файл с описанием блока -----
        // Значение по умолчанию переменной с номером BlockFileVar
        defval=rdsGetBlockVarDefValueStr(Block,BlockFileVar,NULL);
        // Поле ввода с возможностью выбора файла
        rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_OPENDIALOG,
            "Добавляемых блок:",300);
        // Фильтр имен файлов для поля
        rdsSetObjectStr(window,1,RDS_FORMVAL_LIST,
            "Файлы блоков (*.blk)|*.blk\nВсе файлы|*.*");
        // Значение поля ввода
        rdsSetObjectStr(window,1,RDS_FORMVAL_VALUE,defval);
        // Динамическая строка defval больше не нужна
        rdsFree(defval);

        // Список блоков
        defval=rdsGetBlockVarDefValueStr(Block,BlockLstVar,NULL);
        rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_OPENDIALOG,
            "Список блоков:",300);
        rdsSetObjectStr(window,2,RDS_FORMVAL_LIST,
            "Текстовые файлы (*.txt)|*.txt\nВсе файлы|*.*");
        rdsSetObjectStr(window,2,RDS_FORMVAL_VALUE,defval);
        rdsFree(defval);

        // Список связей
        defval=rdsGetBlockVarDefValueStr(Block,ConnLstVar,NULL);
        rdsFORMAddEdit(window,0,3,RDS_FORMCTRL_OPENDIALOG,
            "Список связей:",300);
        rdsSetObjectStr(window,3,RDS_FORMVAL_LIST,
            "Текстовые файлы (*.txt)|*.txt\nВсе файлы|*.*");
        rdsSetObjectStr(window,3,RDS_FORMVAL_VALUE,defval);
        rdsFree(defval);

        // Открытие окна
        ok=rdsFORMShowModalEx(window,NULL);
        if(ok)
          { // Запись значений в переменных блока
            defval=rdsGetObjectStr(window,1,RDS_FORMVAL_VALUE);
            rdsSetBlockVarDefValueStr(Block,BlockFileVar,defval);
            defval=rdsGetObjectStr(window,2,RDS_FORMVAL_VALUE);
            rdsSetBlockVarDefValueStr(Block,BlockLstVar,defval);
            defval=rdsGetObjectStr(window,3,RDS_FORMVAL_VALUE);
            rdsSetBlockVarDefValueStr(Block,ConnLstVar,defval);
          }
        // Уничтожение окна
        rdsDeleteObject(window);
        return ok?1:0;
      }
      //=========================================

      // Создание блоков по списку
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      BOOL TLoadGraphData::LoadBlocks(
        RDS_BHANDLE thisblock, // Идентификатор этого блока
        char *blockfile,       // Файл добавляемого блока
        char *blocklist)       // Файл списка блоков
      { RDS_BHANDLE block=NULL;
        RDS_HOBJECT csv;
        RDS_BLOCKDESCRIPTION descr;
        BOOL ok=TRUE;
        BOOL Modified=FALSE; // Флаг внесения изменений в схему
        int line=0; // Номер строки в списке блоков

        if(List) // Уже есть список объектов – очишаем его
          rdsCommandObject(List,RDS_HBCL_CLEAR);
        else // Списка нет – создаем новый (пустой)
          { List=rdsBCLCreateList(NULL,0,FALSE);
            // Включаем автоматическое обнуление удаленных блоков и связей
            rdsSetObjectInt(List,RDS_HBCL_AUTODELETE,0,1);
          }

        // Получаем описание нашего блока (нужны его родитель и имя)
        descr.servSize=sizeof(descr);
        rdsGetBlockDescription(thisblock,&descr);

        // Создаем объект для разбора формата CSV
        csv=rdsCSVCreate();

        // Открываем файл со списком блоков для чтения в csv
        rdsSetObjectStr(csv,RDS_CSV_OPENFILEREAD,0,blocklist);

        // Проверяем, открылся ли файл
        if(!rdsGetObjectInt(csv,RDS_CSV_FILEISOPEN,0))
          { FileErrorMessage(0,blocklist,
                "Невозможно открыть список блоков");
            rdsDeleteObject(csv);
            return FALSE;
          }

        // Читаем строки файла в цикле
        for(;;)
          { char *name;
            int x,y;
            // Читаем очередную строку из файла в строку объекта 0
            if(!rdsCommandObjectEx(csv,RDS_CSV_STRFROMFILE,0,NULL))
              break; // Строки в файле кончились
            line++;
            name=rdsCSVGetItem(csv,0,0); // Имя блока (элемент 0)
            if(*name==0) // Имя блока пустое
              { FileErrorMessage(line,blocklist,"Пустое имя блока");
                ok=FALSE;
                break;
              }
            // Читаем из объекта координаты блока (элементы 1 и 2)
            x=atoi(rdsCSVGetItem(csv,0,1));
            y=atoi(rdsCSVGetItem(csv,0,2));
            // Если имя блока, который нужно добавить, совпадает с именем
            // нашего блока – переименовываем наш
            if(strcmp(name,descr.BlockName)==0)
              { // Имена совпали – подбираем новое для нашего
                char *newname=rdsMakeUniqueBlockName(
                                  descr.Parent,descr.BlockName);
                // Переименовываем наш блок и получаем его новое описание
                rdsRenameBlock(thisblock,newname,&descr);
                rdsFree(newname); // Освобождаем строку имени
                Modified=TRUE;
              }

            if(block==NULL) // Добавляем самый первый блок (из файла)
              block=rdsCreateBlockFromFile(blockfile,descr.Parent,x,y,NULL);
            else // Уже добавляли блок раньше – копируем добавленный
              block=rdsDuplicateBlock(block,descr.Parent,x,y,NULL);
            if(block==NULL) // Ошибка при добавлении блока
              { FileErrorMessage(line,blocklist,
                    "Не удалось добавить блок");
                ok=FALSE;
                break;
              }
            Modified=TRUE; // Схема изменилась
            // Заносим добавленный блок в список List
            rdsBCLAddBlock(List,block,FALSE);
            // Даем добавленному блоку имя, считанное из списка
            if(!rdsRenameBlock(block,name,NULL))
              { FileErrorMessage(line,blocklist,"Повтор имени блока");
                ok=FALSE;
                break;
              }
          } // for(;;)

        // Удаляем объект csv (файл закроется автоматически)
        rdsDeleteObject(csv);
        if(Modified) // Добавлены блоки
          rdsSetModifiedFlag(TRUE);
        return ok;
      }
      //=========================================

      // Функция создания связей по списку
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TLoadGraphData::LoadConnections(
        RDS_BHANDLE thisblock, // Идентификатор этого блока
        char *connlist)        // Файл списка связей
      { RDS_HOBJECT csv;
        RDS_BLOCKDESCRIPTION descr;
        BOOL ok=TRUE;
        BOOL Modified=FALSE;
        RDS_HOBJECT editor=NULL;
        RDS_BLOCKDIMENSIONS dim1,dim2;
        int line=0; // Счетчик считанных строк

        // Заполнение поля размера служебных структур
        dim1.servSize=sizeof(dim1);
        dim2.servSize=sizeof(dim2);

        // Получаем описание нашего блока (нужен его родитель)
        descr.servSize=sizeof(descr);
        rdsGetBlockDescription(thisblock,&descr);

        // Создаем объект для разбора формата CSV
        csv=rdsCSVCreate();

        // Открываем файл со списком связей для чтения в csv
        rdsSetObjectStr(csv,RDS_CSV_OPENFILEREAD,0,connlist);

        // Проверяем, открылся ли файл
        if(!rdsGetObjectInt(csv,RDS_CSV_FILEISOPEN,0))
          { FileErrorMessage(0,connlist,
                "Невозможно открыть список связей");
            rdsDeleteObject(csv);
            return;
          }

        // Читаем строки файла в цикле
        for(;;)
          { char *name1,*name2,*var1,*var2;
            RDS_BHANDLE block1,block2;
            int xc1,xc2,yc1,yc2,pnum1,pnum2,x1,y1,x2,y2;
            RDS_CHANDLE conn;
            // Читаем очередную строку из файла в строку объекта 0
            if(!rdsCommandObjectEx(csv,RDS_CSV_STRFROMFILE,0,NULL))
              break;
            line++;
            // Считываем из строки имена соединяемых блоков и переменных
            name1=rdsCSVGetItem(csv,0,0); // Имя блока 1
            var1=rdsCSVGetItem(csv,0,1);  // Имя переменной 1
            name2=rdsCSVGetItem(csv,0,2); // Имя блока 2
            var2=rdsCSVGetItem(csv,0,3);  // Имя переменной 2
            if(*name1==0 || *name2==0 || *var1==0 || *var2==0)
              { FileErrorMessage(line,connlist,
                    "Мало данных в строке связи");
                break;
              }
            // Ищем в подсистеме блоки с указанными именами
            block1=rdsGetChildBlockByName(descr.Parent,name1,NULL);
            block2=rdsGetChildBlockByName(descr.Parent,name2,NULL);
            if(block1==NULL || block2==NULL) // Какого-то нет
              { FileErrorMessage(line,connlist,
                    "Не найден один из блоков");
                break;
              }
            // Получаем координаты и размеры блоков
            rdsGetBlockDimensions(block1,&dim1,FALSE);
            rdsGetBlockDimensions(block2,&dim2,FALSE);
            // Вычисляем координаты центров блоков
            xc1=dim1.Left+dim1.Width/2;
            yc1=dim1.Top+dim1.Height/2;
            xc2=dim2.Left+dim2.Width/2;
            yc2=dim2.Top+dim2.Height/2;
            // "Обрезаем" прямую по границам первого блока
            ClipLineByRect(xc1,yc1,dim1.Width,dim1.Height,xc2,yc2,
                &x1,&y1);
            // "Обрезаем" прямую по границам второго блока
            ClipLineByRect(xc2,yc2,dim2.Width,dim2.Height,xc1,yc1,
                &x2,&y2);
            // Создаем или очищаем объект для редактирования связи
            if(editor==NULL) // Нужно создать
              editor=rdsCECreateEditor();
            else // Очищаем ранее созданный
              rdsCommandObject(editor,RDS_HCE_RESET);
            // Добавляем в объект editor две точки
            pnum1=rdsCEAddBlockPoint(editor,block1,var1,
                      x1-dim1.BlockX,y1-dim1.BlockY,FALSE);
            pnum2=rdsCEAddBlockPoint(editor,block2,var2,
                      x2-dim2.BlockX,y2-dim2.BlockY,FALSE);
            // Добавляем соединяющую их линию
            rdsCEAddLine(editor,pnum1,pnum2);
            // Создаем по данным объекта editor новую связь
            conn=rdsCECreateConnBus(editor,descr.Parent,RDS_CTCONNECTION,NULL);
            if(conn!=NULL) // Создание связи удалось
              { Modified=TRUE;
                // Добавляем связь в список
                rdsBCLAddConn(List,conn,FALSE);
              }
            else // Ошибка при создании связи
              { FileErrorMessage(line,connlist,
                    "Не удалось создать связь");
                break;
              }
          } // for(;;)

        // Удаляем объект csv (файл закроется автоматически)
        rdsDeleteObject(csv);
        // Удаляем объект-редактор
        rdsDeleteObject(editor);
        if(Modified) // Добавлены связи
          rdsSetModifiedFlag(TRUE);
      }
      //=========================================

      // Удаление добавленных блоков и связей
      void TLoadGraphData::DeleteByList(void)
      { RDS_CHANDLE *conns;
        RDS_BHANDLE *blocks;
        int count;

        if(List==NULL) // Списка нет
          return;

        // Отключаем автоматическое обнуление удаляемых объектов
        rdsSetObjectInt(List,RDS_HBCL_AUTODELETE,0,0);

        // Получаем указатель на массив идентификаторов связей и его размер
        conns=(RDS_CHANDLE*)rdsGetObjectArray(List,RDS_HBCL_CONNARRAY,0,&count);
        // Удаляем все связи из этого массива
        for(int i=0;i<count;i++)
          rdsDeleteConnection(conns[i]);

        // Получаем указатель на массив идентификаторов блоков
        // и его размер
        blocks=(RDS_BHANDLE*)rdsGetObjectArray(List,RDS_HBCL_BLOCKARRAY,0,&count);
        // Удаляем все связи из этого массива
        for(int i=0;i<count;i++)
          rdsDeleteBlock(blocks[i]);

        // Удаляем объект-список и обнуляем List
        rdsDeleteObject(List);
        List=NULL;
        // Взводим флаг измененности схемы
        rdsSetModifiedFlag(TRUE);
      }
      //=========================================

      // Модель блока
      extern "C" __declspec(dllexport)
        int RDSCALL LoadGraph(int CallMode,
              RDS_PBLOCKDATA BlockData,
              LPVOID ExtParam)
      { // Приведение указателя на личную область данных
        // к правильному типу
      TLoadGraphData *data=(TLoadGraphData*)(BlockData->BlockData);
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define BlockFile (*((char **)(pStart+2*RDS_VSZ_S)))
      #define BlockList (*((char **)(pStart+2*RDS_VSZ_S+RDS_VSZ_A)))
      #define ConnList (*((char **)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_A)))
        switch(CallMode)
          { // Инициализация модели
            case RDS_BFM_INIT:
              BlockData->BlockData=new TLoadGraphData();
              break;

            // Очистка
            case RDS_BFM_CLEANUP:
              delete data;
              break;

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

            // Настройка параметров блока
            case RDS_BFM_SETUP:
              return data->Setup(BlockData->Block,
                                    2,  // BlockFile
                                    3,  // BlockList
                                    4); // ConnList

            // Вызов контекстного меню блока
            // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
            //   в противном случае необходимо использовать версии функций
            //   с суффиксом "W" и символьные константы с префиксом "L"
            case RDS_BFM_CONTEXTPOPUP:
              rdsAdditionalContextMenuItemEx(
                  "Добавить блоки и связи",0,1,0);
              rdsAdditionalContextMenuItemEx(
                  "Удалить добавленное",
                  data->List==NULL?RDS_MENU_DISABLED:0,2,0);
              break;

            // Выбор пункта в контекстном меню
            case RDS_BFM_MENUFUNCTION:
              switch(((RDS_PMENUFUNCDATA)ExtParam)->Function)
                { case 1: // Добавить блоки и связи
                    // Подготовка к серьезным изменениям
                    rdsSetSystemUpdate(FALSE);
                    if(data->LoadBlocks(BlockData->Block,
                                        BlockFile,BlockList))
                      data->LoadConnections(BlockData->Block,ConnList);
                    // Серьезные изменения завершены
                    rdsSetSystemUpdate(TRUE);
                    break;
                  case 2: // Удалить добавленное
                    // Подготовка к серьезным изменениям
                    rdsSetSystemUpdate(FALSE);
                    data->DeleteByList();
                    // Серьезные изменения завершены
                    rdsSetSystemUpdate(TRUE);
                    break;
                }
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef ConnList
      #undef BlockList
      #undef BlockFile
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================