Работа с MapWindow GIS. Новая версия 4.9.2. Обзор обновлений
Всем доброго времени суток. Давненько я не писал ничего про MapWindow GIS в Delphi. Может оно и к лучшему, так как летом вышла очередная новая версия данного ActiveX-компонента, в которой наконец-то появилась поддержка Google-карт.
Мы уже с Вами рассматривали один проект, который позволяет в своих программах добавлять интерактивные карты Google. Эта библиотека называется GMLib и я уже о ней рассказывал на этом блоге. Но многим нравиться именно MapWindows GIS, так как по мне, она более функциональная и универсальная, да и разобрали в данном компоненте мы уже прилично вопросов.
Так вот, здесь я хотел бы поговорить о новой версии, которая вышла в августе 2014 года – MapWinGis 4.9.2. Вам лишь необходимо скачать последнюю версию с официального сайта (ссылка) для своей операционной системы.
Затем на всякий случай удалите старую версию компонента и установите новую, как это сделать я рассказывал в данной статье. Сейчас поговорим о самых интересных и больших обновлениях в этой версии, а в следующей статье уже будем рассматривать, как можно работать с этим компонентом.
Вообще, карты Google теперь в проекте Вашем могут выглядеть следующим образом:
Горячие клавиши по умолчанию
Теперь на карте есть свои встроенные горячие клавиши, которыми можно с легкостью управлять своим проектом, например:
- «+» — увеличение на карте
- «-» — уменьшение на карте
- «*» — увеличение на карте до ближайшей плитки
- «M» — измерение расстояния
- «Колесо мыши» — увеличение или уменьшение масштаба, в независимости от того, какой тип курсора у Вас установлен
- «Z» — увеличение
- «Shift+Left» – увеличение до предыдущего слоя
- «Shift+Right» – увеличение до следующего слоя
Горячие клавиши будут работать только в том случае, если на вашей карте установлен фокус. А стрелки влево и вправо будут работать, только при выполнение некоторого кода, об этом будет рассмотрено в следующих статьях, так как здесь в основном рассматриваем новые возможности.
Измерения расстояния и площади
Теперь Вы можете очень легко и просто измерять площадь и расстояние на своих картах. Поэтому, чтобы Вы могли начать измерение расстояния, необходимо в свойствах компонента CursorMode выбрать cmMeasure, либо же использовать горячую клавишу «M», но в данном случае карта должна находиться в фокусе.
Чтобы измерить площадь необходимо включить режим измерения площади, это делается следующим образом:
Measuring.MeasuringType:=MeasureArea;
Вот так будет выглядеть измерение расстояния:
А вот так измерение площади:
Данный режим также поддерживает различные горячие клавиши:
- Щелчок левой кнопки мыши – добавление новой точки
- Щелчок правой кнопки мыши – стирание последней добавленной точки
- Двойной щелчок мыши – разделение измерения пути
- Ctrl+щелчок мыши – закрывает полигон для измерения
- Shift+щелчок мыши – привязывает линию к ближайшей вершине на слое карты
Кроме этого, теперь можете создавать слои без создания нужных объектов. В версии 4.9.2 еще много различных вкусностей: он поддерживает и гибридные карты Яндекс, Yahoo и других картографических систем, о которых мы будем говорить в следующих статьях.
Обновление предыдущей версии MapWindowGIS
Если Вы не знаете, как можно установить новую версию ActiveX-компонента, то посетите данную статью. Если же она у Вас установлена, то можно просто ее обновить, например, при помощи регистрации библиотеки с помощью команды regsvr32 (Пуск-Выполнить), либо же просто импортируйте новую версию компонента в Delphi.
На этом все, хотели бы Вы дальше читать статьи по MapWindowGIS в Delphi?
Похожие записи
10. События формы. Лабораторные Delphi, C++ (6)
События перемещения мыши на форме в Delphi и C++Builder
Как добавить "резиновый" SplashScreen в XE7
Как известно, в XE7 упростили возможность добавления заставки (SplashScreen) для нашего приложения. В этой заметке я расскажу как добавлять «статичную» и «резиновую» заставки. Данная заметка
Немного про замыкания
В этом легко убедиться:
program closuretest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
procedure Test;
var
i: Integer;
m: string;
proc, proc2: TProc;
function GetClosure1:TProc;
begin
Result := proc;
end;
function GetClosure2:TProc;
begin
Result := proc2;
end;
var
intf1, intf2: IInterface;
begin
i := 0;
proc := procedure()
begin
inc(i,2);
Writeln(i);
end;
proc2 := procedure()
begin
inc(i,3);
Writeln(i);
end;
proc();
proc2();
GetClosure1.QueryInterface(IInterface, intf1);
GetClosure2.QueryInterface(IInterface, intf2);
Writeln(GetClosure1 = GetClosure2); //False
Writeln(intf1 = intf2); // TRUE, что и требовалось доказать
Read(m);
end;
begin
try
Test;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Android 4.4 и запись на внешнюю карту памяти…
До Android 4.4 (API 19), нам приходилось читать файлы (vold.fstab, mounts), чтобы понять, установлена ли внешняя карта памяти и какой до неё путь. Теперь у
Переход с Delphi 2010 на Delphi XE7
Чуть больше года назад я писал об успешном переходе с Delphi 7 на Delphi 2010 приложений в нашей компании (Вот и мы перешли на Юникод).
В этой заметке кратенько опишу об особенностях перехода с Delphi 2010 на Delphi XE7.
Требования остались прежними – надо чтобы работало и там, и там. Впоследствии, возможно, с отказом от использования Delphi 2010. (От Delphi 7 мы уже отказались… ну за исключением совсем старых программ, которые не развиваются.)
Алгоритм тоже не претерпел сильных изменений – сначала обновление сторонних компонентов, затем обновление компонент и библиотек, которые уже не поддерживаются первоначальными разработчиками, затем обновление своих исходников.
Черновой переход – компиляция и запуск программ со случайным “прокликиванием” интерфейсов и без прогонки тестов – у меня занял всего несколько часов (менее одного рабочего дня). Тяжелее скучнее всего было делать замены такого плана:
с DecimalSeparator на {$if CompilerVersion >= 24}FormatSettings.{$ifend}DecimalSeparator
потому что надо было:
- найти номер версии Delphi, где это произошло в RTL (в данном случае это XE3 – произошёл отказ аж от 20 штук глобальных переменных в пользу всего одной);
- таких подобных мест очень много – кроме групповой замены я себе ещё помечал, что не плохо бы пересмотреть код.
Удивила так называемая нормализация типов TPoint, TRect, TSize (в XE2). В одном месте даже написал такой хелпер (хелпер оказался лишним, всё решилось проще, путём переименования локальной переменной).
Ну и некоторые другие редкие мелочи, типа UNIT_PTR или LRESULT.
Переход пока черновой, ради спортивного интереса. Скорость полной сборки приложений можно сказать, что осталась прежней – я проводил по несколько измерений после разных сценариев использования IDE – разница в среднем на 0.4 сек дольше, чем в Delphi 2010 (редко бывало и наоборот). Размер исполняемого exe-файла, получаемого на выходе – в XE7 в среднем на 2.5 мегабайта больше (в Release-сборке, проверял на семи проектах с размером экзешников от 6 до 19 мегабайт).
При этом, стоит отметить как плюс в сторону IDE – Find Declaration (он же Ctrl+Click) стал стабильно добираться до цели, в Delphi 2010 же он через какое-то время просто отваливается.
Сейчас в XE7 пока достаёт только то, что при открытии проекта IDE сразу загружает все модули,объявленные в dpr-файле. И из-за того, что компоненты в IDE я пока не устанавливал, Delphi выдаёт десятки окон вида:
Приходится зажимать Escape и ждать… ну секунд 10, терпимо.
Пока я доволен.
P.S.: не спешно (ну очень не спешно) готовлю обновление для BaseForms и dnSplitter. Ваши комментарии могут сыграть роль волшебного пендаля :с).
Про OCIBreak и принудительное прерывание обращения к БД
Я как-то уже не раз писал о том, что мы не используем стандартные компоненты доступа к БД. Почти всё самописное. И работаем мы с Oracle.
Недавно я, наконец-таки, сделал “фишку”, без которой вполне можно жить, но с ней приятнее.
Представьте, что у вас есть запрос к БД, который выполняется длительное время. Ну, например, пользователь указал слишком мягкие критерии для фильтрации данных. Или индекса в БД нет. Или запрос изначально “кривой”. Или всё вместе взятое… Для прерывания выполнения текущего обращения к серверу в OCI есть стандартная функция – OCIBreak.
У нас я реализовывал так: в отдельном потоке запускается запрос к серверу. Если запрос выполняется длительное время, то появляется модальное окошко с кнопкой [Прервать]:
По завершению запроса – окошко скрывается. Если пользователь успеет нажать кнопку – вызывается OCIBreak, и запрос корректно прерывается.
Таймаут до появления такого окошка установлен в две секунды – большинство запросов отрабатывают гораздо быстрее. Но если вдруг пользователь видит это окно, то на интуитивном уровне он может догадаться, что надо как-то ограничивать параметры своего запроса.
Однако иногда бывает так, что OCIBreak не прерывает запрос. Точнее он его прерывает, но приходится долго ждать. Это встречается у нас всё реже, но встречается, обычно в старых запросах, когда клиент говорит серверу – мол сделай то-то, а я подожду. И пока сервер не закончит транзакцию – приложение как бы “висит”. А если пользователь испугался и нажал [Перервать] – начинается откат транзакции. И пользователь снова ждёт, пока сервер не отпустит транзакцию. А приложение – продолжает “висеть”. И, по хорошему, дождаться бы. Но это раздражает, и есть “продвинутые” пользователи, которые тупо прекращают выполнение программы через диспетчер задач.
Вот для таких, довольно редких случаев, я реализовал дополнительную “фишку” – принудительное прерывание. Работает так: если в течении 5 секунд OCIBreak не отпустил обращение к БД, то кнопка [Перервать] превращается в [Принудительно] и её снова можно нажать.
Что же происходит в этом случае? (Сначала я пробовал убить поток, выполняющий обращение к серверу, но это, конечно же, ничем хорошим не кончилось.)
При нажатии на кнопку [Принудительно] я делаю две вещи:
- запускаю отдельным потоком вторую сессию к БД и выполняю: alter system kill session »:sid, :serial» immediate;
- разрываю текущее TCP-соединение на стороне приложения.
Первый пункт нужен, чтобы сервер понял о наших намерениях – мы не собираемся больше ждать. Второй пункт для меня был не тривиальным. Не вдаваясь в подробности поиска решения, привожу код модуля, который у меня получился:
unit MyMIBUtils; interface uses Windows; type ULONG = Integer; PVOID = Pointer; const ANY_SIZE = 1; AF_INET = 2; type PMIB_TCPROW = ^MIB_TCPROW; _MIB_TCPROW_W2K = packed record dwState: DWORD; dwLocalAddr: DWORD; dwLocalPort: DWORD; dwRemoteAddr: DWORD; dwRemotePort: DWORD; end; MIB_TCPROW = _MIB_TCPROW_W2K; TMibTcpRow = MIB_TCPROW; PMibTcpRow = PMIB_TCPROW; const MIB_TCP_STATE_CLOSED = 1; MIB_TCP_STATE_LISTEN = 2; MIB_TCP_STATE_SYN_SENT = 3; MIB_TCP_STATE_SYN_RCVD = 4; MIB_TCP_STATE_ESTAB = 5; MIB_TCP_STATE_FIN_WAIT1 = 6; MIB_TCP_STATE_FIN_WAIT2 = 7; MIB_TCP_STATE_CLOSE_WAIT = 8; MIB_TCP_STATE_CLOSING = 9; MIB_TCP_STATE_LAST_ACK = 10; MIB_TCP_STATE_TIME_WAIT = 11; MIB_TCP_STATE_DELETE_TCB = 12; type TCP_TABLE_CLASS = Integer; const TCP_TABLE_BASIC_LISTENER = 0; TCP_TABLE_BASIC_CONNECTIONS = 1; TCP_TABLE_BASIC_ALL = 2; TCP_TABLE_OWNER_PID_LISTENER = 3; TCP_TABLE_OWNER_PID_CONNECTIONS = 4; TCP_TABLE_OWNER_PID_ALL = 5; TCP_TABLE_OWNER_MODULE_LISTENER = 6; TCP_TABLE_OWNER_MODULE_CONNECTIONS = 7; TCP_TABLE_OWNER_MODULE_ALL = 8; type PMIB_TCPROW_OWNER_PID = ^MIB_TCPROW_OWNER_PID; MIB_TCPROW_OWNER_PID = packed record dwState: DWORD; dwLocalAddr: DWORD; dwLocalPort: DWORD; dwRemoteAddr: DWORD; dwRemotePort: DWORD; dwOwningPid: DWORD; end; TMibTcpRowOwnerPid = MIB_TCPROW_OWNER_PID; PMibTcpRowOwnerPid = PMIB_TCPROW_OWNER_PID; PMIB_TCPTABLE_OWNER_PID = ^MIB_TCPTABLE_OWNER_PID; MIB_TCPTABLE_OWNER_PID = packed record dwNumEntries: DWord; Table: array [0..ANY_SIZE - 1] of MIB_TCPROW_OWNER_PID ; end; TMibTcpTableOwnerPid = MIB_TCPTABLE_OWNER_PID; PMibTcpTableOwnerPid = PMIB_TCPTABLE_OWNER_PID; function SetTcpEntry(const pTcpRow: MIB_TCPROW): DWORD; stdcall; function GetExtendedTcpTable(pTcpTable: PVOID; var dwSize: DWORD; bOrder: BOOL; ulAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWORD; stdcall; function KillProcessAllTCPConnections(AProcessId: DWORD): DWORD; implementation const iphlpapilib = 'iphlpapi.dll'; function SetTcpEntry; external iphlpapilib name 'SetTcpEntry'; function GetExtendedTcpTable; external iphlpapilib name 'GetExtendedTcpTable'; function KillProcessAllTCPConnections(AProcessId: DWORD): DWORD; var TCPTable: PMibTcpTableOwnerPid; Size: DWORD; Res: DWORD; I: DWORD; TCPRow: TMibTcpRow; begin Result := 0; TcpTable := nil; Size := 0; Res := GetExtendedTcpTable(TCPTable, Size, False, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0); if Res <> ERROR_INSUFFICIENT_BUFFER then Exit; GetMem(TCPTable, Size); try Res := GetExtendedTcpTable(TCPTable, Size, False, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0); if Res <> NO_ERROR then Exit; for I := 0 to TCPTable^.dwNumEntries - 1 do if TCPTable^.Table[I].dwOwningPID = AProcessId then with TCPTable^.Table[I] do begin TCPRow.dwState := MIB_TCP_STATE_DELETE_TCB; TCPRow.dwLocalAddr := dwLocalAddr; TCPRow.dwLocalPort := dwLocalPort; TCPRow.dwRemoteAddr := dwRemoteAddr; TCPRow.dwRemotePort := dwRemotePort; Res := SetTCPEntry(TCPRow); if Res = NO_ERROR then Inc(Result); end; finally FreeMem(TCPTable); end; end; end.
Этот код работает на Windows XP with SP2 и выше.
Соответственно я вызываю:
KillProcessAllTCPConnections(GetCurrentProcessId);
и все текущие TCP-соединения моего процесса прерываются (а у меня оно всего одно). Получить информацию о TCP-соединении, которое используется именно текущим OCI-обращением к серверу мне не удалось, да я особо и не пытался. Если вдруг понадобится – это можно сделать, просмотрев активные соединения непосредственно до и после коннекта к БД.
P.S.: Пару слов про NonBlocking-mode, который есть в OCI. В современном мире многопоточных операционных систем его не рекомендуется использовать вовсе.
Delphi Event bus
- Объявление слушателя — аннотация
- Синхронность отправки в шину — по умолчанию отправка синхронна.
- Асинхронность — в планах на будующее
- Фильтрация событий — статическая, т.е. у обработчиков в листенера можно задать декларативно фильтры и их значения. При посылки события задаются значения предопределенных фильтров для конкретного события
- Иерархия событий — да. Событие это объект. Есть базовый класс всех событий. Обработчик может ждать события определенного класса и всех его наследников
- Строгость ссылочности листенера — строгая. Обязательная дерегистрация. Регистрируются и дерегистрируются сразу все обработчики в листенере
- Приоритет обработчиков — отсутствует. Но дополнительно реализован механизм хуков, похожий на виндовый механизм.
- Реализованы хуки — возможность игнорировать фильтры и выстраивать цепочки обработки. Вызов хуков строго обратен порядку регистрации.