Работа с MapWindow GIS. Новая версия 4.9.2. Обзор обновлений

MapWindowGIS в DelphiВсем доброго времени суток. Давненько я не писал ничего про MapWindow GIS в Delphi. Может оно и к лучшему, так как летом вышла очередная новая версия данного ActiveX-компонента, в которой наконец-то появилась поддержка Google-карт.

Мы уже с Вами рассматривали один проект, который позволяет в своих программах добавлять интерактивные карты Google. Эта библиотека называется GMLib и я уже о ней рассказывал на этом блоге. Но многим нравиться именно MapWindows GIS, так как по мне, она более функциональная и универсальная, да и разобрали в данном компоненте мы уже прилично вопросов.

Так вот, здесь я хотел бы поговорить о новой версии, которая вышла в августе 2014 года – MapWinGis 4.9.2. Вам лишь необходимо скачать последнюю версию с официального сайта (ссылка) для своей операционной системы.

Затем на всякий случай удалите старую версию компонента и установите новую, как это сделать я рассказывал в данной статье. Сейчас поговорим о самых интересных и больших обновлениях в этой версии, а в следующей статье уже будем рассматривать, как можно работать с этим компонентом.

Вообще, карты Google теперь в проекте Вашем могут выглядеть следующим образом:

MapWindowGIS 4.9.2

Горячие клавиши по умолчанию

Теперь на карте есть свои встроенные горячие клавиши, которыми можно с легкостью управлять своим проектом, например:

  • «+» — увеличение на карте
  • «-» — уменьшение на карте
  • «*» — увеличение на карте до ближайшей плитки
  • «M» — измерение расстояния
  • «Колесо мыши» — увеличение или уменьшение масштаба, в независимости от того, какой тип курсора у Вас установлен
  • «Z» — увеличение
  • «Shift+Left» – увеличение до предыдущего слоя
  • «Shift+Right» – увеличение до следующего слоя

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

Измерения расстояния и площади

Теперь Вы можете очень легко и просто измерять площадь и расстояние на своих картах. Поэтому, чтобы Вы могли начать измерение расстояния, необходимо в свойствах компонента  CursorMode выбрать cmMeasure, либо же использовать горячую клавишу «M», но в данном случае карта должна находиться в фокусе.

Чтобы измерить площадь необходимо включить режим измерения площади, это делается следующим образом:

Measuring.MeasuringType:=MeasureArea;

Вот так будет выглядеть измерение расстояния:

MapWindowGIS 4.9.2

А вот так измерение площади:

MapWindowGIS 4.9.2

Данный режим также поддерживает различные горячие клавиши:

  • Щелчок левой кнопки мыши – добавление новой точки
  • Щелчок правой кнопки мыши – стирание последней добавленной точки
  • Двойной щелчок мыши – разделение измерения пути
  • Ctrl+щелчок мыши – закрывает полигон для измерения
  • Shift+щелчок мыши – привязывает линию к ближайшей вершине на слое карты

Кроме этого, теперь можете создавать слои без создания нужных объектов. В версии 4.9.2 еще много различных вкусностей: он поддерживает и гибридные карты Яндекс, Yahoo и других картографических систем, о которых мы будем говорить в следующих статьях.

Обновление предыдущей версии MapWindowGIS

Если Вы не знаете, как можно установить новую версию ActiveX-компонента, то посетите данную статью. Если же она у Вас установлена, то можно просто ее обновить, например, при помощи регистрации библиотеки с помощью команды regsvr32 (Пуск-Выполнить), либо же просто импортируйте новую версию компонента в Delphi.

На этом все, хотели бы Вы дальше читать статьи по MapWindowGIS в Delphi?

Читать на сайте автора.

10. События формы. Лабораторные Delphi, C++ (6)

События перемещения мыши на форме в Delphi и C++Builder

Здесь будут исследованы события формы, появляющиеся при перемещениях пользователем указателя мыши по форме:

Читать на сайте автора.

Как добавить "резиновый" SplashScreen в XE7

Как известно, в XE7 упростили возможность добавления заставки (SplashScreen) для нашего приложения. В этой заметке я расскажу как добавлять «статичную» и «резиновую» заставки. Данная заметка

Читать на сайте автора.

Спрайтовая анимация в FM

Читать на сайте автора.

Немного про замыкания

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

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 выдаёт десятки окон вида:

image

Приходится зажимать Escape и ждать… ну секунд 10, терпимо.

Пока я доволен.

P.S.: не спешно (ну очень не спешно) готовлю обновление для BaseForms и dnSplitter. Ваши комментарии могут сыграть роль волшебного пендаля :с).

Читать на сайте автора.

Про OCIBreak и принудительное прерывание обращения к БД

Я как-то уже не раз писал о том, что мы не используем стандартные компоненты доступа к БД. Почти всё самописное. И работаем мы с Oracle.

Недавно я, наконец-таки, сделал “фишку”, без которой вполне можно жить, но с ней приятнее.

Представьте, что у вас есть запрос к БД, который выполняется длительное время. Ну, например, пользователь указал слишком мягкие критерии для фильтрации данных. Или индекса в БД нет. Или запрос изначально “кривой”. Или всё вместе взятое… Для прерывания выполнения текущего обращения к серверу в OCI есть стандартная функция – OCIBreak.

У нас я реализовывал так: в отдельном потоке запускается запрос к серверу. Если запрос выполняется длительное время, то появляется модальное окошко с кнопкой [Прервать]:

image

По завершению запроса – окошко скрывается. Если пользователь успеет нажать кнопку – вызывается 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

Решились на работе поддержать Delphi сообщество и начать выкладывать в open source собственные наработки. 

Первой ласточкой суждено было стать пакету DelphiEventBus — реализация паттерна проектирования Event Bus.  
В Jаva мире есть такие пакеты как guava-libraries, но в Delphi ничего похожего найти не удалось. Потому решено было запилить нечто подобное. 

Из статьи Java event bus library comparison можно выцепить характеристики библиотек реализующих шину сообщений. 

Для DelphiEventBus получается следующие:

  • Объявление слушателя — аннотация

  • Синхронность отправки в шину — по умолчанию отправка синхронна. 

  • Асинхронность — в планах на будующее

  • Фильтрация событий — статическая, т.е. у обработчиков в листенера можно задать декларативно фильтры и их значения. При посылки события задаются значения предопределенных фильтров для конкретного события

  • Иерархия событий — да. Событие это объект. Есть базовый класс всех событий. Обработчик может ждать события определенного класса и всех его наследников

  • Строгость ссылочности листенера — строгая. Обязательная дерегистрация. Регистрируются и дерегистрируются  сразу все обработчики в листенере

  • Приоритет обработчиков — отсутствует. Но дополнительно реализован механизм хуков, похожий на виндовый механизм.

  • Реализованы хуки — возможность игнорировать фильтры и выстраивать цепочки обработки. Вызов хуков строго обратен порядку регистрации.

Должно работать на XE3 и выше. Еще бы readme перевести на английский…


Читать на сайте автора.