Укрощение автопрокрутки

Люблю я Delphi. И нравятся мне в ней такие фичи, как: Ctrl+Shift+Enter (Find Local References), Ctrl+Shift+E (Refactor Rename), и многие другие. Вот окошки озвученных фич у меня расположены как-то так:
image
Мне так удобнее – слева от редактора кода. А когда результаты поиска большие (или список для рефакторинга большой) – то на экране так больше помещается. Есть один минус – при клике в строчку, которая не помещается на экране по ширине – происходит автоматическая прокрутка по горизонтали (на картинке выше такая строчка выделена подчёркиванием). Т.е. дерево уезжает влево и родительские узлы прячутся за прокруткой – меня это сильно отвлекает и раздражает.
Не секрет, что эти деревья реализованы на VirtualTreeView-компоненте, поэтому такое “лечится” в одно действие:

  VTV.TreeOptions.AutoOptions := VTV.TreeOptions.AutoOptions + [toDisableAutoscrollOnFocus];

Добавляем в пакет, устанавливаем – и готово!
Исходники доступны по ссылке.
P.S.: Однажды я писал, как боролся со шрифтом Object Inspector’а Delphi XE7. Чуть позже я доработал тот пакет, чтобы он исправлял шрифт и в остальных окошках/вкладках. Но оно не прижилось – среда время-от времени восстанавливает свой шрифт – получается вообще ерунда – шрифт скачет туда-сюда. Странное поведение. Но оно исчезает, если отключить модную тему оформления, которая установлена как обычный пакет (ModernTheme210.bpl). Отключить можно через реестр:
image
Отключив этот пакет, все иконки вернутся к старому стилю, зато IDE будет работать чуть стабильнее (у меня было несколько ошибок с указанием на ModernTheme210.bpl), и не надо мудрить со шрифтами.

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

Общее оформление файла

Эта заметка является продолжением к заметкам Стиль оформления кода. Вместо вступления, Настройки окружения и Регистр букв.

(Я планировал немного другой порядок публикаций, но отсутствие времени (ну т.е. лень) не даёт мне возможности навести порядок в некоторых промежуточных моментах.)

Копирайт

Авторские права исходных текстов, разрабатываемых внутри компании, принадлежат самой компании и лицензируются согласно правилам лицензирования, установленных внутри компании.

Следует разделять два уровня доступа к исходным текстам:

  • внутри компании (при создании и сопровождении проектов);
  • вне компании (при публикации в открытый доступ, либо при передаче третьим лицам).

Размещение копирайта в исходном тексте модулей в первом случае не нужно (это трудоёмко и снижает читаемость), но является необходимым во втором случае. Это делается непосредственно перед публикацией/передачей (либо при размещении в системе контроля версий) автоматизировано, необходимый текст генерируется по шаблону и вставляется в каждый исходный файл в самом начале в виде комментария. Кроме копирайта, такой комментарий может содержать информацию о лицензионном соглашении, номер версии (или ревизии) файла, историю изменений и другую информацию.

Начало

В зависимости от своего типа, файл начинается с одного из зарезервированных слов: package, program, library или unit. Следом за ним, через один пробел, следует имя файла (без расширения) с точкой с запятой на конце. Например:

unit UnitName;

В случае отсутствия копирайта, такая строка должна быть самой первой в файле, либо отстоять через одну пустую строку после копирайта.

Это требование исходит из соображений: в случае последовательного просмотра файлов из «продвинутых» файловых менеджеров — имя файла всегда находится на одном и том же уровне от начала файла, а в случае отсутствия копирайта переход к имени модуля осуществляется всего в три клавиши: Ctrl+Home, Ctrl+Right.

Если файл имеет неявную для компилятора связь с другим текстовым файлом, который обрабатывается до (либо после) компиляции, то следующей строкой может идти комментарий вида:

// UnitName.xml

Это удобно для быстрого перехода к редактированию указанного в комментарии файла по сочетанию Ctrl+Enter.

Информацию о версии, даты создания и модификации, а также историю изменений можно вести в отдельном текстовом файле, сославшись на него аналогичным образом:

// UnitName.hst

Однако это является излишнем при наличии системы контроля версия с обязательными комментариями при коммитах.

Далее может следовать многострочный комментарий с описанием предназначения файла и нюансы его использования. Текст комментария обрамляется либо фигурными скобками { и }, либо парой (* и *) и отстоит от левого края на один отступ (см. пример). Однако вместо такого комментария рекомендуется использовать отдельный текстовый файл и его имя размещать в простом комментарии, например:

// UnitName.txt

После всех комментариев следуют директивы компилятора, относящиеся ко всему исходному тексту модуля. См. также: определения условной компиляции.

Секция uses

Зарезервированное слово uses занимает отдельную строку. Объявляемые далее модули группируются по принадлежности, а внутри группы располагаются в порядке использования (см. пример).

В секции implementation слово uses следует располагать после директив, относящихся ко всему разделу реализации модуля (см. пример).

Эта рекомендация следует из соображения, что секция uses может быть многострочной и имеет тенденцию к расширению. Съезжающие вниз директивы отвлекают внимание.

Секция interface

Все объявления из интерфейсной секции модуля доступны другим модулям и становятся глобальными. Поэтому секция interface должна быть максимально компактной и лаконичной, и не должна содержать объявления, которые не могут или не должны использоваться за пределами модуля. Это в особенности касается переменных, создаваемых автоматически при создании новых форм/фрейм:

var
  Form1: TForm1;

Такие переменные (за исключением единичных случаев) следует сразу удалять.

В случае, когда модуль является служебным и его код исполняется во время инициализации и/или финализации, интерфейсная часть модуля может быть пустой.

Секция implementation

Это секция реализации модуля. Она может быть пустой, либо содержать только код инициализации и/или финализации — это секции initialization и finalization.

Конец модуля

Модуль заканчивается ключевым словом end с точкой на конце. Размещение любого текста после последнего end не запрещено, но приводит к выдаче компилятором предупреждения, потому считается крайне не желательным.

Директивы компилятора

Директивы компилятора, относящиеся ко всему исходному тексту модуля, располагаются перед интерфейсной частью между ключевыми словами unit и interface. Директивы, относящиеся к секции реализации модуля, располагаются сразу за словом implementation. Остальные директивы могут располагаться в тексте в любом месте, согласно правилам форматирования кода. (Также см. пример.)

Порядок объявления методов

В VCL принято сначала объявлять конструктор класса, затем деструктор, затем все методы в алфавитном порядке. Это удобно, когда реализуемый класс получается большим (содержит большое количество методов) и является законченным и готовым к использованию. Однако алфавитный порядок следования методов неудобен при чтении кода и при его сопровождении. Для удобства рекомендуется подход, описанный ниже.

При объявлении методов (в секции interface) сначала объявляются обработчики методов. Обработчики группируются по принципу их принадлежности — сначала обработчики формы (фрейма), затем обработчики остальных компонентов (Action’ов, кнопок, грида и т.п.). Внутри группы рекомендуется использовать порядок в соответствии порядка использования обработчиков в RunTime, т.е. сначала идут OnCreate, затем OnShow (или OnInit, OnLoadData, OnLoadState), а затем OnClose, OnDestroy (OnSaveState, OnSaveData, OnDone).

«Парные» методы можно располагать рядом, т.е. обработчики OnCreate/OnDestroy, OnShow/OnClose и т.п. Это удобно для контроля парности действий, выполняемых в таких обработчиках.

Для обработчиков событий Action’ов — сначала OnUpdate, затем OnExecute. Это положение вытекает из таких моментов:

  • Action.Execute всегда внутри себя сначала вызывает OnUpdate, а затем OnExecute, следование обработчиков в коде логично сопоставить с порядком вызова обработчиков. (Кстати, это документированная особенность TAction — обработчик OnExecute не будет вызван, если в OnUpdate свойство Action.Enabled сбрасывается в False.)
  • Читая код OnExecute часто приходится на какой-то момент переключаться на код обработчика OnUpdate, для глаз это удобнее делать, когда расстояние между обработчиками (ключевыми словами procedure) фиксированное. Как правило, код обработчика OnUpdate занимает одну-две строки, а код OnExecute может занимать и более 10 строк. Расположение обработчиков в порядке OnUpdate/OnExecute повышает скорость читаемости кода.

Остальные методы (которые не являются обработчиками событий) также рекомендуется группировать по смыслу и располагать в порядке их использования.

При реализации методов (в секции implementation), рекомендуется соблюдать те же принципы, что и при объявлении, единственным отличием может быть тот факт, что некая группа методов в разделе объявления может быть разделена на части по области видимости.

Пример

unit Example;
// Example.hst

(*
  Необязательное описание предназначения файла и нюансы его использования.
  Вместо данного описания рекомендуется давать понятные имена файлам, чтобы предназначение
  было самим-собой разумеющимся, а нюансы использования вытекали автоматически из интерфейсной части модуля.
  А в случае "раздувания" такого комментария его рекомендуется вынести в отдельный текстовый файл.
*)

{$I Common.inc}
{$ALIGN ON}
{$MINENUMSIZE 4}

interface

uses
  // Delphi units
  SysUtils, DateUtils, Windows, Classes, Forms,
  // My units
  MyUnit1, MyUnit2;

type
  TExample = class(TForm)
    procedure FormLocalize(Sender: TObject);
    // OnCreate, OnDestroy, OnShow, OnClose ...
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

implementation

{$R *.dfm}

uses
  MyUnitA, MyUnitB;

{ TExample }

constructor TExample.Create(AOwner: TComponent);
begin
  inherited;
  // do something
end;

destructor TExample.Destroy;
begin
  // do something
  inherited;
end;

procedure TExample.FormLocalize(Sender: TObject);
begin
  {$include *.mui.inc}
end;

// OnCreate, OnDestroy, OnShow, OnClose ...

end.

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

Регистр букв

Эта заметка является продолжением к заметкам Стиль оформления кода. Вместо вступления и Настройки окружения.

Регистр букв

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

  • все зарезервированные слова и директивы пишутся в нижнем регистре;
  • при повторном использовании идентификатора используется тот вариант написания, который использовался при первом объявлении этого идентификатора.

Во время проектирования, при составлении новых идентификаторов, необходимо руководствоваться общим правилом (см. ниже), а также соглашением об именовании (планируется отдельной заметкой).

Особым образом пишутся директивы компилятора (см. ниже).

Общее правило для идентификаторов

При написании новых идентификаторов необходимо использовать стиль CamelCase, в котором каждая осмысленная часть идентификатора начинается с заглавной буквы, а символы подчёркивания (для соединения частей) не используются. Примеры и допустимые отступления от этого стиля представлены в таблице ниже.

HINT: соглашение об именовании может содержать исключения из этого правила. Например идентификаторы вида: WM_ACTIVATE, DWORD, BITMAPFILEHEADER, some_table_filed и другие.

Если наименование идентификатора

то

Пример

1

состоит из одной буквы

допускается любой регистр

i, j, k: Integer;
s, t, u: string;
C: Cardinal;
D: TDateTime;

2

состоит из одного слова (в том числе аббревиатуры и сокращения)

начинается с заглавной буквы

Count
Value
Next
Rtti
Vcl
Db
Eof
Tmp
Src
Dst
Cnt

3

состоит из нескольких слов

слова пишутся слитно, каждое с заглавной буквы

FirstChar
DoSomethingElse
RttiContext
ItemsCnt

4

начинается с префикса, указывающего на тип идентификатора

префикс пишется строчными буквами

poScreenCenter
fsModal

btnOk
frmMain
acSetup

5

начинается с буквы, указывающей на вид идентификатора

буква пишется в верхнем регистре

SResourceString
EMyException
ISomeInterface
TAnotherType
FClassField
AMethodParameter
LLocalVariable

Директивы компилятора

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

  • все однобуквенные директивы всегда записываются в верхнем регистре;
  • директивы, влияющие на текст компилируемого кода, записываются в нижнем регистре;
  • все остальные директивы — записываются в верхнем регистре.

Параметры всех директив пишутся в соответствии с общим правилом (см. выше).

Включение части кода из другого файла

Для включения (вставки) текста из указанного файла в текущий фрагмент кода при компиляции используется директива {$include} или {$I}.

Если включаемый файл содержит набор директив для компилятора и не содержит «полезный» код, следует использовать краткую форму написания в верхнем регистре:

{$I OurCompanyDefinitions.inc}

В противном случае используется полная форма, и записывается в нижнем регистре:

{$include SomeFileWithCode.inc}

Условная компиляция

Директивы условной компиляции кода, такие как {$define SomeSymbol}, {$ifdef SomeSymbol}, {$else}, {$endif} и т.п., пишутся в нижнем регистре.

См. также: определения условной компиляции.

Регионы

Редактор кода Delphi для удобства навигации позволяет сворачивать/разворачивать логические единицы исходного кода.

Для выделения произвольной части кода в сворачиваемую единицу используются регионы. Для этого необходимый текст обрамляется директивами {$region ‘Region Description’} и {$endregion}, которые пишутся в нижнем регистре. Описание региона (‘Region Description’) игнорируется компилятором, поэтому должно быть удобочитаемым и подчиняться правилам естественного языка.

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

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

Настройки окружения

Эта заметка является продолжением к предыдущей.

Программное окружение, в котором работает программист, а точнее его настройки – сугубо индивидуальная вещь. Но есть несколько моментов, которые прямо или косвенно влияют на результат. Ниже приведены рекомендации, которые будут частью будущего документа; на них будут ссылаться другие разделы документа.

Настройки окружения

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

  • масштабирование текста в ОС Windows должно быть установлено в 100%;
  • тема оформления ОС Windows должна быть классической (с использованием шрифта Tahoma по умолчанию).

Масштабирование текста

При разработке форм в Delphi учитывается текущее разрешение экрана, а точнее параметр PPI — логическое количество точек на дюйм. Во время исполнения приложения, при создании формы, если текущее значение PPI не совпадает с тем, которое было сохранено в dfm-файле, происходит масштабирование.

К сожалению в VCL механизм масштабирования содержит ошибки. Подробно об этом описано здесь.

На уровне BaseForms.pas большая часть проблем исправлена, но новый механизм масштабирования требует, чтобы все dfm-файлы проекта разрабатывались в едином PPI. На текущий момент это значение должно равняться 96.

Это настройка операционной системы, называется «масштабирование текста». Чтобы PPI равнялось 96, масштабирование должно быть установлено в 100%.

Тема оформления

В разных темах оформления используются разные шрифты и разные размеры неклиентских элементов диалоговых окон (строка заголовка, граница окна, полосы прокрутки и другие). Это влияет на внешний вид разрабатываемых в Delphi форм (на разницу между значениями ClientWidth (ClientHeight) и Width (Height) формы, а также на высоту некоторых компонентов типа TEdit и TCombobox, для которых высота определяется по размеру шрифта). Использование разных тем оформления может привести к случайному/лишнему изменению свойств формы и её компонентов.

Классическая тема и шрифт Tahoma являются наиболее распространёнными в корпоративной среде, диалоговые окна и пользовательский интерфейс логичнее всего разрабатывать именно для них.

Настройки IDE

Настройки IDE Delphi — главное меню Tools Options. Эту настройку достаточно выполнить один раз, сразу после установки Delphi.

VCL Designer

Environment Options VCL Designer

VCLDesigner

Use designer guidelines необходимо сбросить.

Snap to grid – установить.

Grid size необходимо выставить именно в 4 и все компоненты располагать по сетке (Snap to grid) потому, что в Windows есть масштабирование. К числам, кратным 4, хорошо применяются стандартные масштабы текста – 75%, 125%, 150% и выше.

New forms as text – для того, чтобы новые dfm-файлы сохранялись в виде текстовых файлов; это необходимо при сравнении двух разных версий одного файла.

Auto create forms & data modules – выключено, т.к. формы создаются в Run-Time по мере необходимости.

Embedded designer – установить, настройка преследует две цели: а) избежать лишнего/случайного изменения положения формы (свойства Left и Top) в dfm-файл (когда программист просто подвинул форму, чтобы не мешала); б) вообще не сохранять эти свойства в dfm-файл (реализовано на уровне BaseForms – когда значения равны нулю, они не сохраняются).

Editor Options

Editor Options Display

Display

Right margin = 120 – значение 80 использовалось на старых мониторах; сейчас подавляющее большинство мониторов – широкоформатные, значение 120 является наиболее оптимальным как для чтения кода, так и по использованию экранного пространства. Именно это значение используется как максимально допустимая ширина строки в данном документе.

Остальные параметры на данной вкладке являются рекомендуемыми, но необязательными.

Formatter Options

Formatter Delphi Indentation

FmtIndentation

Formatter Delphi Spaces

FmtSpaces

Formatter Delphi Line breaks

FmtLineBreaks

Formatter Delphi Capitalization

FmtCapitalization

Formatter Delphi Align

FmtAlign

 

DDevExtensions

DDevExtensions – это расширение для IDE Delphi, скачивается и устанавливается отдельно со страницы: http://andy.jgknet.de/blog/ide-tools/ddevextensions/.

После установки необходимо выполнить настройку: IDE Delphi — главное меню Tools DDevExtensions Options.

Extended IDE Settings:

  • Disable alpha-sort class completion
    Эту настройку необходимо включить, т.к. данный документ вместо автоматического расположения методов в алфавитном порядке обязует это делать осмысленно — см. Порядок объявления методов

Form Designer:

  • Active
    Включить
  • Set TLabel.Margins.Bottom to zero
    Включить
  • Do not store the Explicit* properties into the DFM
    Включить

Пара комментариев

Первые два скриншота сделаны в Delphi 2010. От использования Delphi 7 мы отказались.

Скриншоты настроек форматировщика кода – из Delphi XE7, здесь он уже более-менее прилично работает, хотя часто промахивается, когда пытаешься отформатировать только выделенный кусок кода.

Насчёт масштабирования – на самом деле не важно, какое оно выставлено в системе. Важно, чтобы у всех членов команды оно было одинаковым (два момента: визуальное наследование и случайное/лишнее обновление dfm-файла). Есть ещё такой момент: определив его равным 100% я себе облегчаю задачу при описании правил создания пользовательских интерфейсов (grid size, размеры компонентов и расстояния между ними).

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

Стиль оформления кода. Вместо вступления

Читая чужой код, или даже свой собственный (спустя неделю-месяц-год), наверняка каждый хоть раз да испытывал это ощущение “вырви глаз”. Ощущение, когда не покидает мысль: “да это проще переписать заново, чем разбираться в этом”. Речь конечно идёт о стиле оформления кода.

Стиль оформления – это форматирование кода (пробелы, отступы, переносы строк), соглашение об именовании (регистр букв и допустимые символы при написании идентификаторов, директив и зарезервированных слов), а также набор неких правил, используемых при кодировании (именование идентификаторов, комментирование кода, расположение программных блоков относительно друг-друга в рамках одного исходного текста и разделение кода по файлам).

Неделю назад (17.10.2014) я попросил сообщество проголосовать. Голосование продолжается, но уже очевидно, что вопрос о стиле оформления кода в Delphi остаётся актуальным.

Немножко лирики

Если Вы утверждаете, что Вам всё равно, как оформлен код, то выделяйте пожалуйста, Вам ещё всё равно, или уже всё равно. И вот почему.

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

Затем, набивая раз за разом себе шишки о собственный же код, рано или поздно многие программисты “прозревают”, и по-тихоньку приходят к неким стилевым правилам самостоятельно. И это хорошо. Назовём такое прозрение первой ступенью.

Одно плохо. Чувство стиля – оно сугубо субъективно. При постоянном кодировании оно имеет тенденцию к развитию. И чем сильнее это чувство развито, тем больнее воспринимается “плохой” код. Назовём это второй ступенью, когда при чтении кода, оформленного в “другом” стиле вызываются негативные эмоции вплоть до не понимания кода.

Ну и наконец может настать момент, когда программисту уже всё равно, как оформлен код (главное чтобы он был оформлен хоть как-то). Назовём это третьей ступенью.

И на нулевой ступени и на третьей – нам всё равно. Но между ними лежит огромная пропасть.

 

Конечно, эти ступени весьма условны (я их вот сейчас сходу выдумал). Но есть очень важный момент – развить в себе правильное чувство стиля. Стремиться к тому стилю, который используют большинство программистов. И тогда негативные эмоции на второй ступени можно свести к минимуму.

Проблема

Не существует общепринятых правил. Даже справка в самой Delphi пестрит разнообразием стилей (мол смотрите, вы можете написать и таК, и ТаК и ТАк, а мы это откомпилируем). Ну и конечно наследие: когда-то давно мониторы были маленькими, текста на экране помещалось мало, не было подсветки синтаксиса и т.п. А времена меняются, язык расширяется, среда IDE Delphi обзаводится всякими вкусностями… Мне вот становится очевидным, что должен быть официальный, поддерживаемый (обновляемый) документ с набором рекомендаций от Embarcadero. А придерживаться этого документа, или нет – конечно дело каждого. Но для фрилансеров (которые отдают свой код заказчику), бибилотеко/компонентописателей (которые публикуют свой код) и для программистских отделов/компаний (где работает более одного программиста) такой документ был бы крайне полезен. Мм.. преподавателей забыл упомянуть – это тоже важно.

Но чего нет – так нет. А когда самодисциплины не хватает – приходится самостоятельно составлять некую инструкцию и принимать её внутри компании. Конечно есть вещи, которые нельзя вынести за пределы компании, но я надеюсь получить документ, который можно будет опубликовать. (Т.е. в  итоге, наверное, будет два документа – общие требования и внутренние.)

Сейчас такой процесс у нас (наконец-то?) запущен. Скорее всего, я буду постепенно публиковать некоторые правила (ну чтобы получить фидбэк – это очень важно), а потом в конце всё сведу в отдельный файл.

С чего начать

Если Вы находитесь на нулевой или первой стадии (а также просто для общего развития) рекомендуется к прочтению:

А также:

 

Если у Вас есть полезные ссылки – добавляйте в комментарии. Если у Вас есть свой набор правил или свои инструкции – пишите, можно на e-mail (есть в профиле) или g+. Я постараюсь принять к сведению, может быть получится что-то обобщить.

 

P.S.: Вот у меня сейчас очень близко к третьей ступени, и я боюсь, что когда я на неё поднимусь, мне будет всё равно настолько, что я не буду делать никаких документов и писать на эту тему заметки в блог. А пока меня это тема ещё волнует – не проходите мимо, Ваши комментарии (советы, замечания, критика) служат хорошим стимулом для продолжения.

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

Переход с 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. В современном мире многопоточных операционных систем его не рекомендуется использовать вовсе.

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

Коррекция шрифта в Object Inspector’е

Пробую Delphi XE7. Пока нравится. Поставил GExperts – очень уж я привык к сочетаниям Ctrl+Alt+Down и Ctrl+Alt+Up – переход к следующему/предыдущему объявлению идентификатора. CnPackWizards пока не вышел, но и без него вполне можно жить.
И тут вдруг бросилось в глаза, что у Object Inspector’а шрифт не такой, как везде… (везде – Tahoma 8, а тут – Segoe UI 9)
В GExperts вроде и была возможность менять шрифт инспектора, но почему-то пропала. Поэтому “накидал” на скорую руку пакет, исправляющий данную особенность, результат на картинке (было – стало):
image
Исходник пакета доступен на GitHub, или можно скачать zip-архив отсюда – в  папке ObjInspFntChngr. В модуле uObjInspFntChngr.pas через константы:

const
  PreferParentFont = True;
  PreferFontName = 'Tahoma';
  PreferFontSize = 8;

можно настроить шрифт по своему вкусу.

HINT: Возможно пакет поможет тем, у кого масштабирование текста в Windows больше 100%.

Для установки – открыть dproj-файл, правой кнопкой мыши в Project Manager’е – Install. Пакет можно использовать и в предыдущих версиях Delphi, для этого достаточно удалить dproj-файл и открыть пакет через dpk-файл.

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

Helper для TMessage

В предыдущей заметке я упомянул о хелпере для отладки сообщений Windows. Эта заметка будет предельно краткой: ссылки на исходники и скриншот демки.

Исходник: модуль MsgHlpr.pas. Использовать можно так:

procedure TfrmMain.ApplicationEvents1Message(var Msg: TMsg; var Handled: Boolean);
begin
  Memo1.Lines.Add(Msg.ToString);
..

Демо-приложение:

image

Скачать: MsgHlpr.pas + демо — zip-архив, MsgHlpr.pas на GitHub, исходник демо на GitHub, исполняемый exe-файл демки (zip-архив, 447 КБ).

Работает в Delphi 2010 и выше, при желании легко адаптировать и к предыдущим версиям Delphi.

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

Используем макросы в IDE-редакторе Delphi

Занимаясь отладкой процедур, связанных с обработкой сообщений, постоянно приходится делать сопоставление между кодом сообщения и его строковым наименованием. Ну, к примеру, «прилетает» код 6 — это WM_ACTIVATE. Или сложнее: код 274 (0x0112) = WM_SYSCOMMAND.

Мне это порядком надоело — решил сделать Helper для TMessage (попутно и для TMsg). Ну и в планах попробовать сделать Debugger Visualizer.
Однако о самом хелпере я постараюсь написать в следующий раз. В этой заметке хочу описать, как можно использовать кнопочки Record Macro и Playback Macro, которые находятся в левом нижнем углу строки состояния редактора кода.

(Кстати, мне впервые в жизни пришло в голову попробовать их использовать.)

Итак, я хочу получить код вида:
  case Msg of
    WM_NULL: Result := 'WM_NULL';
    WM_CREATE: Result := 'WM_CREATE';
    ...
    WM_APP: Result := 'WM_APP';
  end;

Идём в модуль Messages и копируем оттуда код:
  {$EXTERNALSYM WM_NULL}
  WM_NULL             = $0000;
  {$EXTERNALSYM WM_CREATE}
  WM_CREATE           = $0001;
  ...
  {$EXTERNALSYM WM_APP}
  WM_APP = $8000;

Вставляем в новый модуль и начинаем макрос. По шагам:
  1. Нажимаю Ctrl+F (Панель поиска), указываю пробел, снимаю все флажки. Enter — чтобы запомнилось.
  2. Устанавливаю курсор на первой строке, нажимаю «Record Macro»:.
  3. Ctrl+Y — удаляем строку
  4. Ctrl+Вправо — курсор к началу идентификатора
  5. F3 — поиск до пробела
  6. Влево — курсор к концу идентификатора
  7. Ctrl+Shift+Влево — выделили идентификатор
  8. Ctrl+C — скопировали выделенное в буфер обмена
  9. Повторяем 5. и 6. — курсор к концу идентификатора
  10. Shift+End — выделение до конца строки
  11. Delete — удаляем выделенное
  12. Набираем на клавиатуре
    : Result := ‘
  13. Ctrl+V — вставили скопированное
  14. Набираем на клавиатуре
    ‘;
  15. Home — переход к началу строки
  16. Вниз — переход к следующей строке
  17. Нажимаю «Stop Recording Macro»:.
Макрос готов, теперь просто жамкаем в «Playback Macro»
пока не достигнем нужного результата.

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