понедельник, 18 июня 2012 г.

Урок 49. Инсталлятор программы - это не сложно

Помните, на предыдущем уроке я поместил в отдельную папку необходимые файлы? 
Это были файлы, разработанные нами в процессе создания программы "Расходы", т.е. обеспечивающие заданный функционал программы. 

Еще раз отмечу, что мною выбран способ хранения настроек в файле ini, а не в реестре Windows, поэтому при создании установщика данный вопрос рассматриваться не будет (иными словами: все своё ношу с собой :-) )

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

Не так все просто.

вторник, 5 июня 2012 г.

Урок 48. Много разных файлов в папке с проектом? Пора разобраться.

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

понедельник, 4 июня 2012 г.

Урок 47. Подскажите как...? Пожалуйста - Справочная система.

Программа "Расходы", о разработке которой я рассказывал последнее время, обросла уже достаточным функционалом, чтобы решать поставленные перед ней задачи. И даже немного больше - я показал, как сделать локализацию интерфейса. А те, кто "следил за развитием событий", надеюсь, нашли для себя что-то новое и интересное. Дописывать и оптимизировать программу можно далее до бесконечности, например: для удобства учета данных из разных источников можно придумать БД и написать механизм импорта из нее в основной массив данных; можно озадачиться и решить вопрос с учетом кредитов и других долговых обязательств.

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

четверг, 31 мая 2012 г.

Урок 46. Обобщающий графический отчет

Пройдет время. Скажем, год. База данных наполнится цифрами...
И Вам обязательно захочется взглянуть на некую общую картину. А мне, как и прежде, уже сейчас хочется рассказать о практическом применении тех компонентов, о которых мы пока не говорили.

вторник, 15 мая 2012 г.

Обновление файла ProDelphiLib

Сегодня я получил вот такое сообщение:

"Boris прокомментировал:

Здравствуйте, Александр! Я подписался на рассылку Ваших уроков про Delphi. Потихоньку изучаю. На вашем блоге есть раздел "Скачать". Так вот у меня такой вопрос. Как скачать библиотеку компонентов ProDelphiLibPack. Я скачал ProDelphiLibPack.dpk, пытался установить но появляется ошибка - Delphi не находит MyComponentsPack. Как установить библиотеку. Поясните, пложалуйста. "

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

Поэтому я выражаю свою благодарность Boris (жаль, мне не известно настоящее имя), с одной стороны.

С другой стороны, получая такие письма, осознаешь, что эта затея приносит кому-то пользу. Это - положительные эмоции. Спасибо.

Я подправил некоторые некорректности в ресурсах библиотеки и обновил архив.

Ссылка для скачивания последней версии библиотеки: https://docs.google.com/open?id=0B0V8YJCT7hdLVS1TTlJnTnhMOWs - добро пожаловать! Получить доступ к этому архиву Вы так же можете со страницы "Скачать".

Надеюсь, что все будет теперь в полном порядке.

Об установке библиотеки. 

1. Скачайте архив библиотеки ProDelphiLib.
2. Распакуйте его в папку, например C:\Program Files\Borland\BDS\4.0\lib\Pro-Delphi
3. Откройте в среде разработки Delphi файл ProDelphiLibPack.bdsproj, полученный из архива в папку на шаге 2.
4. Выполните компиляцию и установку пакета:


в окне Project Manager кликните правой кнопкой на файле .bpl и в контекстном меню выберите "Install".

При успешном окончании процесса, Вы получите вот такое сообщение:


5. Обязательно нажмите кнопку сохранить:


6. Выполните меню File - Close All.

7. Проверьте наличие установленных компонентов, для чего выполните меню Component - Install Packages:



8. Проверьте наличие пути к файлам установленной библиотеки, выполнив меню Tools-Options- Library Win32:


Если путь к библиотеке (см. пункт 2) в списке Directories отсутствует, то нажмите кнопку с многоточием, найдите нужную папку и добавьте ее кнопкой "Add".


понедельник, 14 мая 2012 г.

пятница, 11 мая 2012 г.

Урок 44. Drag & Drop

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

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

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

четверг, 10 мая 2012 г.

Урок 43. Группировка затрат

Очевидно, что быстро получить картинку для оценки, например, всех коммунальных платежей за определенный период не сложно. Но будет ли информативна детализированная картинка?

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

Но теперь настало время объединить счета по какому-то признаку в группы, чтобы в дальнейшем давать обобщенное представление группы затрат.

пятница, 4 мая 2012 г.

Урок 42. Обороты по счету

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

Этот инструмент позволит ответить на вопрос:

А из чего складывается тот или иной оборот по конкретному счету за выбранный период?


Ну, например: в оборотной ведомости за январь (YYYY года) есть расход в строке со счетом "Коммунальные платежи", но сейчас, в мае, вы нашли под кроватью квитанцию за январь, и не помните, вносили Вы эту сумму в январе или нет. Да и вообще, из чего складывается общая цифра коммунальных платежей за январь, из каких сумм и кому заплаченных?

пятница, 27 апреля 2012 г.

Еще один большой привет программистам МТС!

Какие НЕ нужно писать программы


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

При попытке изменить набор услуг с помощью Интернет-помощника, а точнее - отключить услугу "БИТ 2011", мне было выдано сообщение:

"Запрос не выполнен. На номере телефона имеются незавершенные операции..."



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

Начинаю долгий поиск по меню, отыскивая возможность посмотреть, чем я так провинился перед МТС.

После полуторачасовых изысканий, к которым был привлечен и мой коллега, удалось найти вот такую незавершенную операцию:


Обратите внимание: программа сообщает мне о невыполненном переходе на тариф "MAXI", показывая справа одновременно, что моему номеру сопоставлен именно этот тариф.

И еще очень "хорошо", что случилось это совсем недавно - 25 октября прошлого 2011 года, т.е. 5 месяцев назад!!!

Молодцы, коллеги из МТС! Так держать!!!

Зачем озадачиваться такими мелкими проблемами, как написание программ, способных анализировать ошибки, ляпы, результаты сбоев, повисалок и других непоняток?

четверг, 26 апреля 2012 г.

Урок 41. Графическое представление информации

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

Любой бухгалтер скажет: "Мне это зачем? Мне Журнал хозяйственных операций дай и Главную книгу".

Но, во-первых, мы пишем не бухгалтерскую, а очень очень похожую программу.
Во-вторых, у нас уже есть нечто, отдаленно напоминающее ЖХО. Не хватает отчета в удобной форме - сделаем. У нас есть замечательная оборотка, из которой только ленивый не построит Главную книгу.
В-третьих, графическое представление информации удобно в тех случаях, когда нужно быстро проанализировать огромный массив данных, чтобы принять какое-то конструктивное решение.
В-четвертых, интереснее рассказывать о чем-то, о чем еще не рассказывал...

Надеюсь, что я привел убедительные доводы в поддержку своих же намерений :-)

среда, 25 апреля 2012 г.

Урок 40. Кодификаторы

Создавать первые базы данных приходилось в условиях полного отсутствия литературы. Я уже не говорю про переведенные на русский язык книги... Вообще не было никаких. Я помню, как писал запрос в представительство Майкрософта: посодействуйте в получении руководства по MS Access...

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

И вывел я для себя вот такую формулу много лет назад:

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

среда, 18 апреля 2012 г.

Немного юмора...


Сидят два админа, грусные тупо смотрят в монитор,

Заходит третий

-Чего такие грусные?

-Да вчера пиво пили… и пароли меняли…


***




Ушел сисадмин в отпуск. Через неделю на мобилу звонит начальник отдела:

- Андрей извини, что побеспокоили. Срочно надо в компе информацию найти. Скажи пожалуйста пароль...

- Как вы меня все з@@бали. Без пробелов и с маленькой буквы.


***




Рaзговор в чaте:

- Помогите, уменяпробелнерaботaет!

Ответ:

- Нaстоящему_прогрaммисту_пробел_не_нужен!


***

четверг, 12 апреля 2012 г.

Сам нашел опечатку в заголовке 33 урока

В заголовке 33-го урока была допущена досадная опечатка.
Для исправления необходимо поменять слово "импорт" на "экспорт", что я уже и сделал.


Приношу свои извинения.




среда, 11 апреля 2012 г.

Урок 39. "Товары", сопутствующие Insert

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

при попытке сохранения пустой строчки происходит ошибка...


написали "Апрель", но не ввели номер месяца - другая...


Если пользователь - Вы сам, то беды большой нет, поскольку Вы знаете в чем тут дело и как поправить. Но я стараюсь как можно чаще давать понять моему читателю, что пугать неподготовленного пользователя служебными сообщениями есть моветон. Как плохой манерой является и исполнение маневров, подобных тем, что исполняют в МТС.

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

Найдите на форме компонент ADOTABLEMOS и напишите обработчик свойства AfterInsert:

procedure TMainFrm.ADOTableMOsAfterInsert(DataSet: TDataSet);
var
  year, month, day: Word;
  monthName:array[1..12] of string[8];       // Объявление одномерного массива фиксированного размера
begin

  // Присвоение начальных значений элементам массива
  monthName[1]:='Январь';
  monthName[2]:='Февраль';
  monthName[3]:='Март';
  monthName[4]:='Апрель';
  monthName[5]:='Май';
  monthName[6]:='Июнь';
  monthName[7]:='Июль';
  monthName[8]:='Август';
  monthName[9]:='Сентябрь';
  monthName[10]:='Октябрь';
  monthName[11]:='Ноябрь';
  monthName[12]:='Декабрь';

  // Декодирование текущей даты  Date() функцией  DecodeDate(). Результат - в переменных year, month, day
  DecodeDate(Date(), year, month, day);

  // Добавление значений по умолчанию в список мемориальных ордеров
  ADOTableMOs.FieldByName('Mes').Value:= IntToStr(month);
  ADOTableMOs.FieldByName('Name').Value:= monthName[month];  // Значение из массива по номеру месяца

end;


Теперь при добавлении новой записи программа поможет оператору:


Вы можете смело продолжить работу, а я попутно хочу заметить
1. Массив monthName можно объявить не в этой процедуре, а выше - на уровне модуля, сделав его не локальным, а глобальным по отношению к данному модулю, если его значения  необходимы в других процедурах. Тогда объявление массива можно было бы совместить с присвоением его элементам значений:

monthName: array[1..12] of String[8]=
('Январь','Февраль','Март',и т.д. до,'Декабрь');



2. Алгоритм "угадывания" названия месяца по его номеру можно вынести в DLL в виде отдельной функции, так как он может понадобится не только в этой программе, но и в других.

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

Все хорошо, пока пользователь не стер одно из подставленных данной процедурой значений. 
В такой ситуации ошибки повторятся. 

Ладно бы стер... он может нажать кнопку "Добавить" пару раз, и в справочнике легко появятся два, а то и три апреля...



Помните, я говорил в уроке 38, что обработчик ошибок нужен при выполнении Post? Давайте поправим ситуацию, пока не поздно.

Займемся этим оператором. А точнее - событием, предшествующим сохранению введенных в Grid значений в источнике, под сеткой подложенной таблице. Событие это носит название BeforePost. Именно в этот момент, перед выполнением операции сохранения данных, уместно сделать проверку на предмет: "Подходят ли нам введенные значения?"

Для этого напишем вот такой обработчик события BeforePost

procedure TMainFrm.ADOTableMOsBeforePost(DataSet: TDataSet);

  function TheTestMos(): Boolean;
  var
    MyStr: String;
    p: boolean;
  begin

    p:= True;            // Признак успешности проверки

    MyStr:= ADOTableMos.FieldByName('Name').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в названии не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    Result:=p;
  end;

begin

  If not TheTestMos
  then
    ADOTableMOs.Cancel;
end;


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

Но это - так... лирика. Смоделировав некорректный ввод пользователя, мы получим на экран сообщение:


Первая часть проверки (попытка ввести мемориальный ордер без названия) успешно сработала. 

Аналогично нужно сделать проверку введенного значения в поле Mes.

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

Для этого понадобится запрос TADOQuery, который необходимо разместить на форме. Я назову его TempQuery.


И дополню функцию проверки: 


    // 1-я проверка на отсутствие названия мемориального ордера
    MyStr:= ADOTableMos.FieldByName('Name').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в названии не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    // 2-я проверка на отсутствие номера месяца
    MyStr:= ADOTableMos.FieldByName('Mes').AsString;
    if (MyStr='')
    then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Пустые значения в поле Месяц не допустимы';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      abort;            // Не показывать пользователю стандартное сообщение об ошибке
    end;

    // 3-я проверка на ввод повторной записи
    TempQuery.Active:=False;
    TempQuery.SQL.Clear;
    MyStr:='SELECT * FROM MOs WHERE (Name='''+MyStr+''') AND (Mes='+ADOTableMos.FieldByName('Mes').AsString+')';
    TempQuery.SQL.Add(MyStr);
    TempQuery.Active:=True;
    If not TempQuery.Eof
    Then
    begin
      p:=False;
      MyMessenger.TitleString:='Ошибка';
      MyMessenger.MessageString:='Запись с такими значениями уже имеется в базе данных...';
      MyMessenger.Buttons:=[mbOK];
      MyMessenger.ShowMessage;
      Abort;
    end;
    TempQuery.Active:=False;


Запустив программу, после нажатия кнопки "Добавить" мемориальный ордер, ничего не меняя нажму "Сохранить" и опять увижу два апреля. Почему?

Вот тут я готов начать разговор о средствах отладки.
Простейшее из имеющегося богатого набора - это точка останова. Щелкните мышкой на сером поле в строке №967 ( TempQuery.SQL.Add(MyStr);). Появившаяся красная точка - точка, где программа остановится в процессе выполнения.

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


В название месяца из той же переменной MyStr подставляется номер (4), полученный ею на этапе предыдущей проверки. Выход прост: переставьте местами 1-ю и 2-ю проверку:



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


PS Точку останова нужно всегда ставить на следующей строчке, чтобы оператор присвоения значения (:=) уже был выполнен.

PPS Можно ли было вместо запроса использовать Locate? 





пятница, 6 апреля 2012 г.

четверг, 5 апреля 2012 г.

Приятно, черт возьми

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

delphi Ehlib Grid EditMask

и натыкаюсь на собственную публикацию в третьей позиции...


Читать не стал :-) нет там ответа на мой вопрос :-)



пятница, 23 марта 2012 г.

Урок 38. Обработчик ошибок

Во времена фортрана меня учили обходиться без оператора Goto. Использование оператора безусловного перехода считалось моветоном равным по грешности полному отсутствию комментариев в программе.

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

Первый вопрос, который я предвижу от слабо посвященных: "А зачем?"
Действительно, пишешь задачу, как "честная кнопка", пытаешься ничего не упустить, все предусмотреть, собственное творчество, как правило, себе нравится, и не вызывает сомнений правильность его алгоритма... И все же! Если задачка небольшая - все просто и прозрачно. Но, если алгоритм сложен... как говорят "глаз замыливается", перестает находить узкие места. К тому же накладывается человеческий фактор: откуда Вам было знать, что пользователь не введет значение в поле и нажмет "Enter"?.. В этот момент Ваша программа напугает его каким-нибудь англоязычным сообщением да еще и с пиктограммой "Error"... А, если Вы помните, я с самого начала просил Вас уважать пользователя и заказчика. Стоит ли доводить ситуацию до общения с плохо вменяемым пиплом?! Если Вы не хотите общаться с человеком, у которого от испуга вытаращены глаза и трясутся руки, нужно научиться предугадывать (перехватывать) ошибки и выдавать на экран сообщения на родном для пользователя языке, желательно с номером Вашего телефона... Это и есть часть понятия "дружественный интерфейс".

Следующий вопрос, к которому я хочу приблизить тебя, дорогой читатель, это вопрос:

где и когда нужен обработчик ошибок?

Стоит ли, на эту тему рыться в учебниках? И "да", и "нет". Почему? Программа, о которой в настоящий момент идет речь ("Расходы"), работает с базой данных, а найти в книжках этот материал - ой как не просто, поскольку большую часть книжного пространства авторы отводят, как правило, обучению работе с базой данных (создание, подключение, запросы и т.п.). Можно долго и упорно рыться в интернете, гуглить яндекс и задавать вопросы на форумах - в конце концов, Вы соберете нужную инфу, но упустите время...

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

Чем большим количеством подобных вопросов Вы себя озадачите, тем меньше вероятность того, что Ваша программа в руках незадачливого пользователя перейдет в состояние "Не отвечает".

Наиболее общая схема обработчика ошибок выглядит примерно так:

  • Объявление необходимых переменных и присвоение им стартовых значений.
  • Выполнение основного алгоритма, в процессе которого проверяются возможные условия возникновения ошибок
  • Обработка ошибок
  • Выдача сообщения оператору
Я предлагаю вернуться к предыдущему уроку и на примере описанных в нем процедур "сочинить" несложный обработчик ошибок.

Добавьте в объявление переменных в процедуре SetLang() еще парочку:

Var
  ini: TIniFile;
  ErrKod: integer;
  ErrMsg: String;

а так же - объявление метки:

Label
  ErrLabel;

и стартовые значения:


begin

  ErrKod:=0;                                    // Это значение при отсутствии ошибок
  ErrMsg:= 'Операция завершена успешно';

Настройте заранее (в начале процедуры) компонент, выдающий сообщения:


  MyMessenger.TitleString:='Ошибка...';
  MyMessenger.MessageType:=mtError;
  MyMessenger.Buttons:=[mbOK];


Затем - напишите проверку наличия файла языковой поддержки:


  If Not FileExists(GetCurrentDir + '\'+Lang)
  then
  begin
    ErrKod:=1;
    ErrMsg:='При попытке открытия файла языковой настройки произошла ошибка';
  end;


Почему именно здесь нужно предусмотреть какие-то действия? А вдруг в файле настроек указан несуществующий файл? Или его кто-то с диска того... нечаянно стер...

Следом должна идти проверка на наличие ошибок:


  if ErrKod>0
  then
    Goto ErrLabel;


...


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


...


ErrLabel:


    MyMessenger.MessageString:=ErrMsg;
    MyMessenger.ShowMessage;


end;        // Процедуры


Давайте нашалим: переименуем файл Rus.lng, например в Rus1.lng, чтоб программа его уж точно не нашла.

Тогда при старте Rashod.exe будет произведена попытка настроить интерфейс приложения с помощью указанного в файле настроек Rashod.ini, в секции [Constants], в переменной LangFileName файла Rus.lng, которого в рабочем каталоге нет. Сработает обработчик ошибки, и на экране будет выведено придуманное нами сообщение:


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

Верните переименованному файлу его законное название и запустите программу еще раз.
Вы получите еще одно сообщение:


не корректное по оформлению и неуместное по сути (если ошибок нет, нужно ли в данном случае вообще выводить какое-то сообщение?!).

Давайте поправим ситуацию:

ErrLabel:

  Case ErrKod of
    0:                               // Ошибок нет
    else
    begin
      MyMessenger.MessageString:=ErrMsg;
      MyMessenger.ShowMessage;
    end;

  End;

end;        // Процедуры


Теперь в отсутствии ошибок программа загрузится в штатном режиме, т.е. без лишних сообщений.

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





А знаете, кто написал первый компьютерный вирус?

Ваш покорный слуга.

Шучу, конечно.

Дело было во времена фортрана.

У нас на кафедре было заведено так: сочинил программу к курсовому или еще по какому торжественному случаю - набивай её на перфокартах. Колоду перфокарт упакуй аккуратненько, чтоб не рассыпалась (не приведи Господи!), подпиши разборчиво, положи ее в специальный шкафчик и иди гуляй, потому что когдааа-а-а оператор вычислительного зала возьмет ее своими бесценными ручками и запустит в считыватель M4030, не известно... К тому же, вдруг кто-нибудь совсем безрукий по неосторожности опять паяльник разогретый в процессор уронит... Тогда - совсем плохо...

Короче: "Приходите завтра" - это не название фильма а ваш девиз с того памятного момента и до бесконечности...

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

И вот пока колода перфокарт лежит в первом шкафчике и ждет прикосновения чудотворящих рук оператора, можно было поупражняться в написании первых вирусов, благо - машинка по набивке перфокарт располагалась тут же, рядом. Спросите любого программиста, какую переменную он чаще всего использует для счетчика, и окажется, что любимая буква программистов "I".

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

Покоритель вирусов набивал три карточки примерно такого содержания (в синтаксисе языка я  могу ошибаться, но не в сути):

I=0
Label1: I=I+1
GOTO Label1

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

Дальше, если вредоза не написала глупостей и мертвых ветвей в ее алгоритме нет, то через какое-то время из машинного зала раздавался мат по поводу переполнения оперативной памяти, зависания процессора и упавшего на ногу кому-то раскаленного паяльника...

Далеко разносился вопль негодования по гулким коридорам высшей школы и затихал где-то под прокуренной лестницей...

Значит: сработало...

А скоро - первое апреля...





среда, 21 марта 2012 г.

Урок 37. Локализация интерфейса

Наверное, не правильно говорить о переводе всех названий пунктов меню и кнопок на другие языки, если Вы пишете программу для использования в домашнем хозяйстве. Да, это так, если не учитывать, что на страницах этого сайта я преследую несколько иную цель: не дать исходники, а дать "know how" - умения.

Я уже где-то говорил, и не устану повторять:  для решения той или иной задачи существует несколько способов. Вдаваться в полемику в пользу того или иного мне не интересно. Лишь обозначу, что для решения задачи локализации можно использовать библиотеку, т.е. Dll-ку, а можно - файл настроек. Обязательно найдутся охотники по ратовать за использование реестра Windows. Это, еще раз повторю - дело вкуса. В каждом из способов есть свои достоинства и свои недостатки.

Известно, что файл настроек может иметь вот такую структуру:

[Название секции]
Имя переменной1=Значение переменной1
Имя переменной2=Значение переменной2
...
Имя переменнойN=Значение переменнойN


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

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

Чтобы файлы языковой поддержки не путались с файлами настроек (ini), предлагаю дать им расширение *.lng.

Вот примерное содержимое двух файлов для отображения интерфейса программы "Расходы" на русском и английском языках:

Rus.lng


[MainFrm]
MainFormCap=Расходы (главная форма)
Menu_File=&Файл
Menu_File_Exit=&Выход
Menu_Data=&Данные
Menu_Data_Edit=&Ввести
Menu_Data_Insert=&Новая запись
Menu_Data_Filter=&Фильтр
Menu_Data_Credit=&Кредиты
Menu_Data_Export=&Экспорт
Menu_Data_Import=&Импорт
Menu_Reports=&Отчетные формы
Menu_Reports_Reverse_statement=&Оборотная ведомость
Menu_Reports_Turnovers_account=&Обороты по счету
Menu_Reports_Cost_Analysis_All=&Анализ затрат (все)
Menu_Reports_Cost_Analysis_Gruops=Анализ затрат (по &группам)
Menu_Reports_Cost_Analysis_Trends=Анализ &тенденций
Menu_Codifiers=&Кодификаторы
Menu_Codifiers_Accounts=&Счета
Menu_Codifiers_Group_costs=&Группы затрат
Menu_Codifiers_Memorial_warrants=&Мемориальные ордера
Menu_Codifiers_Valuta=&Валюты
Menu_Service=&Настройки
Menu_Service_SelectDisk=Выбор &диска (пути к базе данных)
Menu_Service_Language=&Выбор языка
Menu_Service_Clear=&Очистка БД
Menu_Help=&Справка
Menu_Help_About=&О программе


Eng.lng


[MainFrm]
MainFormCap=Expenses (Main form)
Menu_File=&File
Menu_File_Exit=&Exit
Menu_Data=&Data
Menu_Data_Edit=&Edit
Menu_Data_Insert=&New Record
Menu_Data_Filter=&Filter
Menu_Data_Credit=&Credits
Menu_Data_Export=&Export
Menu_Data_Import=&Import
Menu_Reports=&Reports
Menu_Reports_Reverse_statement=&Reverse statement
Menu_Reports_Turnovers_account=&Turnovers account
Menu_Reports_Cost_Analysis_All=&Cost analysis (All)
Menu_Reports_Cost_Analysis_Gruops=Cost analysis (&gruops)
Menu_Reports_Cost_Analysis_Trends=Check &Trends
Menu_Codifiers=&Codifiers
Menu_Codifiers_Accounts=&Accounts
Menu_Codifiers_Group_costs=&Group costs
Menu_Codifiers_Memorial_warrants=&Memorial warrants
Menu_Codifiers_Valuta=&Valuta
Menu_Service=&Service
Menu_Service_SelectDisk=&Select path
Menu_Service_Language=&Language
Menu_Service_Clear=&Clear data
Menu_Help=&Help
Menu_Help_About=&About prog

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

Кстати, в Rashod.ini должна пополниться секция:

[Constants]
LangFileName=Rus.lng

- пусть уж программа стартует с привычного нам языка, чтоб самих себя не испугать :-)

На этом предварительные танцы с бубнами и беличьими хвостиками закончены. Открываем проект "Rashod".

И первое, что необходимо сделать - создать процедуру, устанавливающую локализацию, назову ее SetLang (не забудьте декларировать ее в секции private):

procedure TMainFrm.SetLang();
Var
  ini: TIniFile;
begin

  // Настройка интерфейса программы (по выбранному языку)
  ini:=TiniFile.Create(GetCurrentDir + '\'+Lang);

  MainFrm.Caption:=Ini.ReadString('MainFrm','MainFormCap','');
  N_File.Caption:=Ini.ReadString('MainFrm','Menu_File','');
  N_Exit.Caption:=Ini.ReadString('MainFrm','Menu_File_Exit','');
  N_Data.Caption:=Ini.ReadString('MainFrm','Menu_Data','');
  N_Edit.Caption:=Ini.ReadString('MainFrm','Menu_Data_Edit','');
  N12_Ins.Caption:=Ini.ReadString('MainFrm','Menu_Data_Insert','');
  N10_Filter.Caption:=Ini.ReadString('MainFrm','Menu_Data_Filter','');
  N_Credit.Caption:=Ini.ReadString('MainFrm','Menu_Data_Credit','');
  N_ExportToMDB.Caption:=Ini.ReadString('MainFrm','Menu_Data_Export','');
  N_Import.Caption:=Ini.ReadString('MainFrm','Menu_Data_Import','');
  N_Reports.Caption:=Ini.ReadString('MainFrm','Menu_Reports','');
  N3.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Reverse_statement','');
  N4.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Turnovers_account','');
  N_Analiz.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_All','');
  N_AnalizGroup.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_Gruops','');
  N_Analiz_Trends.Caption:=Ini.ReadString('MainFrm','Menu_Reports_Cost_Analysis_Trends','');
  N_Kodifikators.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers','');
  N1.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Accounts','');
  N_ExpGr.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Group_costs','');
  N2.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Memorial_warrants','');
  N_Val.Caption:=Ini.ReadString('MainFrm','Menu_Codifiers_Valuta','');
  N_Serv.Caption:=Ini.ReadString('MainFrm','Menu_Service','');
  N7_SelectDisk.Caption:=Ini.ReadString('MainFrm','Menu_Service_SelectDisk','');
  N_Serv_SelLang.Caption:=Ini.ReadString('MainFrm','Menu_Service_Language','');
  N12_ServClear.Caption:=Ini.ReadString('MainFrm','Menu_Service_Clear','');
  N_Help.Caption:=Ini.ReadString('MainFrm','Menu_Help','');
  N_About.Caption:=Ini.ReadString('MainFrm','Menu_Help_About','');

  ini.Free;

end;

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

Необходимая переменная Lang, хранящая имя языкового файла, должна быть объявлена в секции interface модуля формы. А вот значение в нее попадет в момент создания формы (Create), откуда - из Rashod.ini:

procedure TMainFrm.FormCreate(Sender: TObject);
var
  ini: TIniFile;
begin

  // Подключение и считывание из файла настроек ini
  ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
  Lang:=Ini.ReadString('Constants','LangFileName','');
  ini.Free;

  SetLang;
...

- И что? - скажете Вы после компиляции проекта, - никаких изменений нет.
Видимых изменений нет. Попробуйте поменять в Rashod.ini Rus.lng на Eng.lng и еще раз запустить программу.

Можно радоваться? 
Рано.

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

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

procedure TMainFrm.N_Serv_SelLangClick(Sender: TObject);  // Настройка. Выбор языка
var
  ini: TIniFile;
begin

  // Настройка фильтра файлов определенного типа в окне диалога
  OpenDialog1.Filter:='Файлы языковой поддержки|*.lng';

  // Выбор файла на диске
  If not OpenDialog1.Execute
  then                           // Отмена выбора
  begin
    exit;
  end;

  // Извлекается
  Lang:=OpenDialog1.FileName;    // Полный путь
  Lang:=ExtractFileName(Lang);   // Только имя файла

  // Запись в соответствующую секцию Rashod.ini, чтобы
  // в следующий раз программа стартовала с этими настройками
  ini:=TiniFile.Create(GetCurrentDir + '\Rashod.ini');
  Ini.WriteString('Constants','LangFileName',Lang);
  ini.Free;

  // Смена надписей на элементах управления
  SetLang;

end;

В комментариях я постарался дать необходимые пояснения. Вот теперь программа "Rashod" еще больше стала похожа на "взрослую" :-)

Я не затрагиваю так или иначе вопросы оптимизации уже написанного кода - это отдельная непростая история. Но здесь меня просто подмывает сделать оговорку:

вместо двух строк

  Lang:=OpenDialog1.FileName;    // Полный путь
  Lang:=ExtractFileName(Lang);   // Только имя файла

можно написать одну

  Lang:=ExtractFileName(OpenDialog1.FileName);

Иногда приходится жертвовать красотой ради наглядности :-)

Если остались вопросы - пожалуйте на страницу "Обратная связь"







вторник, 20 марта 2012 г.

Урок 36. Продолжаем разговор...

Именно так любил приговаривать очаровательный мультяшный герой по имени Карслон, который живет на крыше...

Прежде, чем углубиться дальше в лес Delphi и не только,  я бы хотел оживить некоторые пункты меню проекта "Расходы".

Шаг 1. Пункт меню "О программе"


Когда-то, в уроке №13, я рассказал о том, как сделать компонент "О программе". Вам придется его немного подправить :-) и разместить на форме MainFrm, а затем назначить соответствующему пункту меню вот такой простой обработчик:


procedure TMainFrm.N_AboutClick(Sender: TObject);
begin
  PrDAboutBoxDlg1.Execute;
end;


Наделите компонент следующими свойствами:



Щаг 2. Никто не обратил внимания, что кнопка Cancel (PrDCancelButton1) на верхней панельке осталась без обработчика. Добавьте обработку действий, когда пользователь ничего не ввел в поля новой записи или передумал добавлять новую запись:


procedure TMainFrm.PrDCancelButton1Click(Sender: TObject);
begin


  // Отказ от редактирования
  ADOTableMain.Cancel;
  DBGridEh1.Enabled:=True;                                   // Переключение грида в режим доступности
  Panel3.Visible:=False;                                            // Скрытие верней панели


end;

Шаг 3. Новый Action Data_Insert, обрабатывающий процесс добавления новой записи, необходимо добавить в список ActionList и подложить его под соответствующие пункты главного меню и PopupMenu:


Обработчик события выполнения экшена:


procedure TMainFrm.Data_InsertExecute(Sender: TObject);
begin


  // Новая запись
  ADOTableMain.Insert;


end;



Внимание! Не забыть вернуться к процедуре:

procedure TMainFrm.Data_SelectMOExecute(Sender: TObject);    // Соответствует N_EditClick
begin
...
  //   Сделать доступным именно Action, а пункты меню станут доступными автоматически
  Data_Insert.Enabled:=True;


Шаг 4. Выбор базы данных

Добавьте Action в категорию Service


Создайте обработчик Action:




procedure TMainFrm.Service_Select_DiskExecute(Sender: TObject);
begin


  // Выбор диска с базой данных
  SetDisk;
  MainConnecting;
  TableActive(True);


  // Инфо на статусбаре
  StatusBarUpdate;


end;

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

На этом пока все.
В следующем уроке я предполагаю показать как "перевести" интерфейс программы на другой язык.



понедельник, 19 марта 2012 г.

Урок 35. Оборотная ведомость. Печать отчета

Если Вы, изучив предыдущие уроки, получили в свое распоряжение файл Excel, Вам не составит труда вывести его на печать. Но, не всегда такой вариант получения отчета приемлем.

Для конструирования отчетов существует ряд средств. Я не берусь здесь обсуждать их достоинства и недостатки. Так исторически сложилось, что я пользуюсь платной версией Fast Report.

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

Почему именно FastReport? Посмотрите здесь. Не только из гордости за отечественного производителя, еще и за практически безграничные возможности.

Установка компонентов FastReport не должна вызвать никаких трудностей. После установки, на Tool Palette появится ряд вкладок.

Разместите на форме два компонента, с вкладки FastReport:

TfrxDBDataset - источник данных для отчета, TfrxReport - конструктор отчета.

Теперь необходимо немного подготовиться и временно включить:

1. Форма MainFrm: ADOConnection  - Connected = True (если не задана строка подключения, задать ее).
2. Форма OborotFrm, компонент CDS - свойство Connection = MainFrm.ADOConnection1 (т.е. связать этот компонент с ADOConnection  на главной форме) и указать свойство TableName = Oborot. Собственно, это все проделывается программно, но нам нужно временно для создания отчета.

Не забудьте по окончании создания отчета очистить значения этих свойств.

Настройте компонент TfrxDBDataset, указав его свойство DataSet:

А затем два раза щелкните по компоненту TfrxReport, чтобы перейти в конструктор отчета.
Первое, что необходимо сделать - выбрать меню Report - Data и назначить источник данных для отчета, как показано на рисунке:


Затем - развернуть страницу на Landscape (альбомную) в меню File - Page Settings, т.к. отчет наш "широкий".

Добавить "бэнды" - так называются области для размещения в них различных данных и группировке этих данных, как минимум - два - ReportTitle (заголовок отчета) и MasterData (мастер - для вывода данных отчета), связав последний с источником данных.


Сохраните свой макет под именем Oborot.fr3.

Теперь настало время дать отчету заголовок, нажав на пиктограмму "ab":



Форматируйте надпись подобно тому, как Вы это делали в MS WORD, здесь инструменты очень похожи:


Перетащите одно поле для начала на MasterData (можно добавить еще заголовок страницы (PageHeader), где разместить заголовки колонок отчета):


Опция Create caption удобна, но не в нашей языковой зоне: подписи = имена полей на латинице, так что подписи придется сделать ручками.

Сохраните и закройте конструктор. Теперь нужно в Popup меню формы добавить новый пункт, по которому будет вызываться отчет:


procedure TOborotFrm.N2Click(Sender: TObject);
begin
  // Вывод предварительного просмотра отчета
  frxReport1.ShowReport(True);
end;

Если все сделано правильно, на этом этапе Вы получите список счетов с заголовком "Оборотная ведомость".

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


Главное рассказал, а важное и нужное - еще впереди.
Мой дорогой читатель, наверное уже догадался, что в документе не хватает подзаголовка, т.е. периода, за который он составлен.

Далее я покажу один из способов передачи данных посредством переменных в отчет.

Добавьте перед строкой  frxReport1.ShowReport(True) ровно такую же строку, какой задавался период при импорте отчета в Excel:

frxReport1.Script.Variables['Period']:='Период: '+FormatDateTime('dd/mm/yy',Date_N.Value)+' - '+FormatDateTime('dd/mm/yy',Date_K.Value);  
здесь Period - имя переменной, которую нужно разместить в конструкторе отчета в текстовом поле (memo) в квадратных скобках:


Результат, прямо скажем еще далек от совершенства:


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

Если не сможете разобраться - пишите (координаты Вы найдете через страницу "Обо мне"), и тогда будем разбираться вместе.

А.В.