Заметки, идеи и мысли автора, обзор кода, алгоритмов, инструментов.

среда, 27 января 2010 г.

Создаем ярлыки в коде.

На Delphi есть не мало различных вариантов создания ярыков в коде. И в общем-то они рабочие, но только в том случае если писать на средах младше RadStudio 2009.

Постольку поскольку 2009 студия целиком и полностью перешла на юникод, со старыми вариантами создания ярлыка так же возникли проблемы. Пытался всяко разно решить проблему методом бубна над кодом, изменением кодировок вручную и т.п. Потом мне этот беспорядок надоел и нашёл я метод решения построенный на COM объектах. В общем плюс данной реализации в том, что нету тонких мест, которые ломаются при работе со строками. Проверено лично мной, вердикт - работает :)

Источник реализации.

Привожу самую малость модернизированный код:

uses 
  Registry, 
  ActiveX, 
  ComObj, 
  ShlObj; 

type 
  ShortcutType = (_DESKTOP, _QUICKLAUNCH, _SENDTO, _STARTMENU, _OTHERFOLDER); 

////////////////////////////////////////////////////////////////////////////////
// Выдает директорию системного меню "Программы" по умолчанию
// так же выдает Application Data текущего пользователя. (нужно для Quik Launch)
function GetProgramDir(GetApp: Boolean = False): string;
var
  reg: TRegistry;
begin
  reg := TRegistry.Create;
  try
    reg.RootKey := HKEY_CURRENT_USER;
    reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', False);
    if not GetApp then
      Result := reg.ReadString('Programs')
    else
      Result := reg.ReadString('AppData');
    reg.CloseKey;
  finally
    reg.Free;
  end;
end;

function CreateShortcut(SourceFileName: string; // the file the shortcut points to
                        InkName: String; //Имя самого ярлычка (XIO)
                        Location: ShortcutType; // shortcut location
                        SubFolder,  // subfolder of location
                        WorkingDir, // working directory property of the shortcut
                        Parameters,
                        Description: string): //  description property of the shortcut
                        string;
const
  SHELL_FOLDERS_ROOT = 'Software\MicroSoft\Windows\CurrentVersion\Explorer';
  QUICK_LAUNCH_ROOT = 'Software\MicroSoft\Windows\CurrentVersion\GrpConv';
var
  MyObject: IUnknown;
  MySLink: IShellLink;
  MyPFile: IPersistFile;
  Directory, LinkName: string;
  WFileName: WideString;
  Reg: TRegIniFile;
begin

  MyObject := CreateComObject(CLSID_ShellLink);
  MySLink := MyObject as IShellLink;
  MyPFile := MyObject as IPersistFile;

  MySLink.SetPath(PChar(SourceFileName));
  MySLink.SetArguments(PChar(Parameters));
  MySLink.SetDescription(PChar(Description));

//  LinkName := ChangeFileExt(SourceFileName, '.lnk');
  LinkName := ChangeFileExt(InkName,'.lnk');//ExtractFileName(LinkName); //XIO Edit

  // Quicklauch
  if Location = _QUICKLAUNCH then
  begin
    Reg := TRegIniFile.Create(QUICK_LAUNCH_ROOT);
    try
      Directory := Reg.ReadString('MapGroups', 'Quick Launch', '');
    finally
      Reg.Free;
    end;
  end
  else
  // Other locations
  begin
    Reg := TRegIniFile.Create(SHELL_FOLDERS_ROOT);
    try
    case Location of
      _OTHERFOLDER : Directory := SubFolder;
      _DESKTOP     : Directory := Reg.ReadString('Shell Folders', 'Desktop', '');
      _STARTMENU   : Directory := Reg.ReadString('Shell Folders', 'Start Menu', '');
      _SENDTO      : Directory := Reg.ReadString('Shell Folders', 'SendTo', '');
    end;
    finally
      Reg.Free;
    end;
  end;

  if Directory <> '' then
  begin
    if (SubFolder <> '') and (Location <> _OTHERFOLDER) then
      WFileName := Directory + '\' + SubFolder + '\' + LinkName
    else
      WFileName := Directory + '\' + LinkName;

    if WorkingDir = '' then
      MySLink.SetWorkingDirectory(PChar(ExtractFilePath(SourceFileName)))
    else
      MySLink.SetWorkingDirectory(PChar(WorkingDir));

    MyPFile.Save(PWChar(WFileName), False);
    Result := WFileName;
  end;
end;


четверг, 21 января 2010 г.

SQL синтаксис для MS Access

Нашел отличное описание слабо документированной стороны MS Access, а именно синтаксис SQL команд, которые к сожалению некоторыми деталями отличаются от стандартного SQL.

Источник - 1
А вот на MSDN'е

Бета Версия! Блокнотика.

И года как говориться не прошло. Обозревал я тут в прошлом году программку свою smile.gif

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

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

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

Следующая версия в ближайшие недели надеюсь, как получится, но финал будет это стабильно smile.gif Вопрос лишь в том, что в него войдет smile.gif

А пока жду помидорки и яйца в адрес бета версии dandy.gif

Качать тут

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

- Переименовать дату ухода в дату смерти.
- Спрятать дату смерти до востребования, что бы не моячила при забивании субъектов.
- Подумать о дополнительном столбце "ближайшее событие" в главной таблице.
- Поиск по реквизитам/контактам подфорсировать.
- Базу замаскировать.
- с импортом и экспоротом с Аутлуком/батом буду ещё думать. Дело не в базе, у аутлука есть возможность подключения к нему через OLE Objects, и можно там делать что угодно, опираясь на описанные APi функции. Но проблема в отличиях этих самых APi в разных версиях Офиса, пока просто в плане разобраться с этим поподробнее.
- Оживить Контекстное меню главной таблички. Доделать.
- Просмотр инфы о контакте по даблКлику в табличке
- Исключить из употребление в программе термин "Субъект"
- Подумать об объединении Напоминаний и Событий в одну группу.
- В окошках с табличками кнопки по добавлению/удалению и т.п. продублировать снизу окон. Для лучшей юзабильности.
- Для активации фильтра по категориям вместо нового окна сделать выпадающий список под кнопкой.
- Если загружен неизвестный формат фотографии, на поле фото написать "Неизвестный формат".
- В напоминаниях, реквизитах, позволено оставлять пустые записи, запретить... сделать как в справочниках.
- добавить формочку с календарем, где был бы календарь и помечено, на каких числах висят события и напоминания, ДР и т.п. ... Как в стандартных ежедневниках.
- Проверить события, подглючивают мал-мал. С добавлением и с оповещением О_о.. Запретить оставлять поля с датами пустыми.
- Не обновляется Today ("Сегодня") после добавления событий, напоминаний и т.п. После закрытия привязанных окон делать Requery().

вторник, 12 января 2010 г.

Открыть файл и дождаться закрытия.

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

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

 Файлы ведь могут быть разных форматов, и ворд и эксель, и текстовый и может даже фотография формата GIMP. Из этого следует что CreateProcess нам не подходит, так как в коде определять какой программой открывать файл - лишние проблемы и подводные грабли. Не стоит так извращаться.
В свою очередь ShellExecute имеет другой недостаток, он хоть и способен автоматически открыть файл в ассоциированной ему программе, но он не дает хэнд процесса, что не позволяет нам дождаться завершения процесса. А раз мы не можем дождаться завершения процесса, стало быть как мы узнаем через какое время пользователю надумается сохранить изменения в файле и закрыть его, для того что бы затянуть измененный файл назад в базу. Это основная проблема на которую я наткнулся при решении задачи.

Но Google никогда не подводил, буквально час ушел на поиск нужного решения. Есть такой промежуточный метод между CreateProcess и ShellExecute. И имя его ShellExecuteEx.

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

Код процедуры:
procedure RunAndWaitShell(Executable, Parameter: STRING; ShowParameter: INTEGER);
  var
     Info: TShellExecuteInfo;
     pInfo: pShellExecuteInfo;
     exitCode: DWord;
  begin
     pInfo := @Info;
     Info.cbSize := SizeOf(Info);
     Info.fMask  := SEE_MASK_NOCLOSEPROCESS;

     Info.wnd    := application.Handle;
     Info.lpVerb := NIL;
     Info.lpFile := PChar(Executable);
     Info.lpParameters := PChar(Parameter + #0);
     Info.lpDirectory  := NIL;
     Info.nShow        := ShowParameter;
     Info.hInstApp     := 0;
     ShellExecuteEx(pInfo);
     repeat
        exitCode := WaitForSingleObject(Info.hProcess, INFINITE); //было 500
        Application.ProcessMessages;
     until (exitCode <> WAIT_TIMEOUT);
  end;
{Если форма приложения имеет свойство AlwaysOnTop заменить SEE_MASK_NOCLOSEPROCESS на SEE_MASK_FLAG_DDEWAIT }

Теперь пример использования:

//...
  RunAndWaitShell('D:\test.txt','',SW_SHOWNORMAL); //Открываем файлик, где бы он ни находился.

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

//Тут в общем-то берем этот же файл и пишем его  в базу, заменяя старый вариант, по желанию
//можно устроить проверку на изменения в файле.
//  ShowMessage('The End or the programm.');
//...

Такое в общем простое решение на такую в общем-то нужную в жизни задачу, если у кого-то есть более оптимальные решения - пишите :) Если что-то не понятно, тоже пишите =)

понедельник, 4 января 2010 г.

Скажем НЕТ! - оператору "GoTo"

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

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

Теперь немного ближе к теме. Прошли мы значит в колледже этот оператор, учитель сказал нам что это "кака", но вот проходит пара лекций и вдруг нам дается официальное разрешение использовать данный оператор для создания цветового меню. Как же я тогда в душе злился. Я помню тогда подошел к преподавателю и показал ей свой вариант решения задачи, где не нужно было использовать никакого оператора Goto, и при этом все прекрасно работало, но в ответ получил примерно следующую фразу "Молодец, ты можешь так придумать, а другие не могут, поэтому пусть делают как умеют". Я помню пытался одногруппникам донести свой вариант, и объяснить почему так лучше, но понимание нашел лишь у пары человек. Остальным было пофиг.

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

А теперь демонстрирую свой вариант реализации цветого меню в Паскале, с возможностью строить многоуровневые меню. Кстати в данном варианте все ещё есть недостатки, то есть, имеются "штуки" которые можно сделать ещё лучше, но это вы уж сами :)
Код моего варианта, с примером использования:

Uses crt;
 var str,str2,STR3:array[1..20]of string;
     I:integer;
     k:char;
     X11,X22:integer;

{Универсальная процедура для вывода вертикального меню, параметры описаны ниже}
Procedure menu(kl,XX,YY:integer; pun:array of string;
          fon,txt,fon1,txt1:integer;var key:char; X1:integer);
    {колич.пунк.}{место по Х}{место поУ}{имена пунк.}{нор.фон}
    {нор.текст}{выд.фон}{выд.текст}{код клавиши}{положение курсора}
      begin
       textbackground(fon);
        textcolor(txt);
       { ClrScr;}
{         GotoXY(XX,YY); Кому надо можно использовать рамку, но для вложенных менюх такой вариант не пойдет.
            write('г============МЕНЮ============¬');
          For I:=1 to kl+1 do begin
          GotoXY(XX,YY+i);
            write('¦                            ¦');
          end;
           GotoXY(XX,YY+i);
            write('L============================-');}
              For I:=1 to kl do begin
                GotoXY(XX+1,YY+i);
                  write(pun[i]);
              end;
              x1:=1;
            repeat
              if key=#80 then X1:=X1+1;
              if key=#72 then X1:=X1-1;
                     if X1>KL then X1:=1;
                     if X1<1 then X1:=KL;
              For I:=1 to kl do begin
               If x1=i then begin textcolor(txt1); textbackground(fon1); end
                 else begin textcolor(txt); textbackground(fon); end;
                   GotoXY(XX+1,YY+i);
                     write(pun[i]);
              end;
            key:=#0;
            key:=readkey;
            Until (key=#13)or(key=#27);
      end;
    {Процедура для вывода горизонтального меню, для отдельных частных случаев требует некоторой правки.
    Желающие могут усовершенствовать до универсального вида.}
        Procedure Main_menu(var Y1:integer;Key:char);
             begin
             Y1:=1;
             repeat
             GotoXY(1,1);
               write('Файлы БД    Задачи    Выход');
                 if key=#75 then Y1:=Y1-1;
                 if key=#77 then Y1:=Y1+1;
                   if Y1>3 then Y1:=1;
                   if Y1<1 then Y1:=3;
         GotoXY(1,1);
          If y1=1 then begin textcolor(15); textbackground(4); end
            else begin textcolor(15); textbackground(9); end;
             write(' Файлы БД  ');
          GotoXY(12,1);
           If y1=2 then begin textcolor(15); textbackground(4); end
            else begin textcolor(15); textbackground(9); end;
             write('  Задачи  ');
          GotoXY(22,1);
           If y1=3 then begin textcolor(15); textbackground(4); end
            else begin textcolor(15); textbackground(9); end;
             write(' Выход ');
            key:=#0;
            key:=readkey;
            Until key=#13;
           end;
     
      Begin
    {А вот и простейший пример использования данных процедур в комбинации.}
      ClrScr;
    {Заполняем массив пунктов первого меню}
      str[2]:='  Файл 1   ';
      str[3]:='  Файл 2   ';
      str[4]:='  Файл 3   ';
      str[5]:='  Файл 4   ';
      str[6]:='  Файл 5   ';
      str[7]:='  Выход    ';
   
    {Заполняем массив пунктов второго меню}
      str2[2]:=' Задача 1 ';
      str2[3]:=' Задача 2 ';
      str2[4]:=' Задача 3 ';
      str2[5]:=' Задача 4 ';
      str2[6]:=' Задача 5 ';
      str2[7]:=' Выход    ';

    {Заполняем массив пунктов третье меню(вложенность третьего уровня)}
      str3[2]:=' Ввод в файл             ';
      str3[3]:=' Дополнение файла        ';
      str3[4]:=' Удаление из файла       ';
      str3[5]:=' Замена компонент файла  ';
      str3[6]:=' Вставка компонент файла ';
      str3[7]:=' Просмотр файла          ';
      str3[8]:=' Выход                   ';
     
      Main_menu(X11,K); {вызовим главное меню(горизонтальное).}
      {Получив    в переменную X11 номер выбранного пункта, вызовим соответствующее подменю.}
      If (X11=1) then
    begin
          Menu(6,0,1,str,9,15,4,3,k,x22);
          If (k=#13) then
            Menu(7,11,X22+1,str3,9,15,4,3,k,x22);
    end;
      if (X11=2) then
        Menu(6,11,1,str2,9,15,4,3,k,x22);

      end.

Код конечно нуждается в хорошем рефакторинге, но сильно строго не судите, писал я его в 2005 году будуче не опытным студентом :) Но в качестве примера сойдет, да и основная мысль думаю ясна. Если все же что-то не понятно постите в комментариях свои вопросы, поясню.
К коду добавлю пару слов. Все операторы Goto были заменены такой конструкцией как процедура/функция, и циклами, на мой взгляд куда лучше чем перемещаться по дублирующим друг друга кускам кода которые в дальнейшем крайне тяжело править. Понятное дело что такие с позволения сказать программы, нужны наверное только студентам и тем кто делает на студентах деньги, но просто не мог смолчать =)

Это не большое отступление так сказать о наболевшем. В следующий раз постараюсь обозреть процесс создания игры-арканойда все на том же добром Турбо-Паскале, а затем уже буду потихоньку прекращать описывать "баяны" написанные на Паскале =)

пятница, 1 января 2010 г.

Эффект "Матрицы" в Турбо-Паскаль

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

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

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

Uses crt, Dos;
  var s,i,x,y,D,n: integer;
        r: real;
      regs: Registers;
   begin
     clrscr;
      regs.ah := 1;
      regs.ch := $20;
      regs.cl := 0;
      regs.bh := 0;
      Intr($10,regs);
      x:=1; y:=1;
      TextColor(RED);
      Writeln('                                ВНИМАНИЕ!!!');
      Writeln('Для полного эффекта просмотра разверните окно на весь экран (Alt+Enter)');
      writeln('Затем нажмите любую клавишу...');
      readkey;
      clrscr;
      REPEAT
        textattr:=10;
        r:=1;
         while r<=128 do
          r:=r+0.001;
             Randomize;
       N:=random(9);
       for I:=1 to N do begin
        d:=random(70);
        If d<10 then d:=10;
           gotoxy(x,y);
         write(CHR(d));
        y:=y+1;
       if y=25 then y:=1;
         end;
        X:=random(80);
       if x>=80 then x:=1;
        writeln;
       writeln;
      UNTIL KEYPRESSED;
         textattr:=138;
          gotoxy(32,12);
         writeln('г==============¬');
          gotoxy(32,13);
         writeln('¦ Matrix error ¦');
          gotoxy(32,14);
         writeln('L==============-');
           readln;
             gotoxy(57,24);
             TextColor(yellow);
           writeln('Автор: Мукомело Евгений');
       readln;
     end.

Теперь давайте попробуем разобрать что же мы настрочили :)
Первые строки думаю ни у кого вопросов не вызовут, а вот этот кусок:
      regs.ah := 1;
      regs.ch := $20;
      regs.cl := 0;
      regs.bh := 0;
      Intr($10,regs);
нам необходим для того что бы спрятать курсор. Если этого не сделать то по экрану будет бегать курсор и будет портить всю красоту того что мы изображаем. Для реализации используется пример из книги "Профессиональное программирование на Турбо-Паскале" А. Файсмана. Для тех кто хочет изучить всю глубину - может найти эту книгу либо погуглить, а если в крации то устанавливаются определенные параметры переменной типа Регистрс, затем инициируется вызов прерывания $10 используя при этом функционал DOS.TPU библиотеки.

И так, курсор мы спрятали. Далее по коду проводим подготовку, присваиваем первичные значения переменным, и далее.. Далее открываем цикл с постусловием, который заканчивается тогда, когда срабатывает событие нажатия клавиши на клавиатуре. А в этом самом цикле и начинается все самое интересное. Алгоритм у нас примерно следующий:
Цикл
- Выполняем импровизированную задержку, путем выполнения цикла сложения чисел.
- Далее с помощью генерации случайных (псевдослучайных) числе определяем то, какой длинны у нас будет выводимый столбец (ряд символов).
ПодЦикл
- С помощью функции генерации псевдослучайных чисел Random выберем произвольный символ из таблицы ACSII. Лично я использовал символы из диапазона от 10 до 70.
- Перемещаем курсор в нужную часть экрана с помощью gotoxy() и выводим случайный символ.
- Инкрементируем позицию курсора Y (по вертикали)
Конец ПодЦикла
- Выбираем  случайным образом, в какой позиции X (по горизонтали) будем выводить следующий ряд символов.
- Выводим новую строку, для смещения экрана вверх
Конец Цикла

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


 


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

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

В следующей статье поговорим о том как плохо использовать операторы GOTO.

Постоянные читатели