Полный исходный текст на языке 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 } //=========================================