Delphi: Программное открытие файла PDF (Sumatra, Foxit и Adobe) в указанном месте назначения в PDF

Я главный разработчик проекта с открытым исходным кодом (http://nbcgib.uesc.br/lec/software/editores/tinn-r/en).

Теперь руководство пользователя проекта в формате PDF (сделано под LaTeX).

Я пытаюсь создать процедуру для программного открытия руководства пользователя в указанном месте назначения/разделе в PDF.

Поскольку я не знаю программу просмотра PDF по умолчанию для пользователей, я разрабатываю процедуру для поддержки основных: Sumatra, Foxit и Adobe.

Процедура отлично работает для Sumatra, но не для Foxit и Adobe.

Под кодом:

function GetAssociation(const DocFileName: string): string;
var
  FileClass: string;
  Reg: TRegistry;

begin
  Result:= '';
  Reg:= TRegistry.Create(KEY_EXECUTE);
  Reg.RootKey:= HKEY_CLASSES_ROOT;
  FileClass:= '';
  if Reg.OpenKeyReadOnly(ExtractFileExt(DocFileName)) then
  begin
    FileClass:= Reg.ReadString('');
    Reg.CloseKey;
  end;
  if FileClass <> '' then begin
    if Reg.OpenKeyReadOnly(FileClass +
                           '\Shell\Open\Command') then
    begin
      Result:= Reg.ReadString('');
      Reg.CloseKey;
    end;
  end;
  Reg.Free;
end;

procedure TfrmTinnMain.OpenUserGuidePDF(sWhere: string);
var
  sFile,
   sViewerDefault,
   sParameter: string;

begin
  sViewerDefault:= GetAssociation('.pdf');

  if pos('Sumatra',                     // Sumatra: OK
         sViewerDefault) > 0 then
    sParameter:= '-reuse-instance ' +
                 '-named-dest ' +
                 sWhere
  else if pos('Foxit',                  // Foxit: opens the file, but not at the named destination
              sViewerDefault) > 0 then
    sParameter:= '/A ' +
                 'page=100'
  else if pos('Adobe',                  // Adobe: opens the file, but not at the named destination
              sViewerDefault) > 0 then begin
    sWhere:= StringReplace(sWhere,
                           '"',
                           '',
                           [rfReplaceAll]);

    sParameter:= '/A ' +
                 '"' +
                 'nameddest=' +
                 sWhere +
                 '"';
  end
  else
    sParameter:= '';

  sFile:= sPathTinnR +
          '\doc\User guide.pdf';
  try
    // Open PDF viewer
    ShellExecute(0,
                 'open',
                 Pchar(sFile),
                 Pchar(sParameter),
                 nil,
                 sw_shownormal);

  except
    MessageDlg('PDF viewer is not accessible!',
               mtInformation,
               [mbOk],
               0);
  end;
end;

procedure TfrmTinnMain.menHelUserGuideClick(Sender: TObject);
begin
  OpenUserGuidePDF('"Contents"');
end;

person jcfaria    schedule 20.01.2014    source источник
comment
Какой у Вас вопрос?   -  person David Heffernan    schedule 20.01.2014
comment
Каким образом он не работает нормально? Что вы ожидаете от программы и что вместо этого происходит? Что вы узнали из отладчика?   -  person Rob Kennedy    schedule 20.01.2014
comment
PDF - очень плохой выбор для интерактивного справочного центра.   -  person Free Consulting    schedule 20.01.2014
comment
Я хочу открыть руководство пользователя (Sumatra, Foxit и Adobe) в любом указанном месте. Например: База данных, Конфигурация и т.д. Процедура уже работает для Sumatra, но не для Foxit и Adobe Reader.   -  person jcfaria    schedule 20.01.2014
comment
Вы уже сказали это. Хотя в чем вопрос. Чего вы ожидаете? Что на самом деле происходит? В чем фактическое поведение не соответствует вашим ожиданиям? Как мы можем воспроизвести вашу тестовую среду, если мы не хотим устанавливать ваше программное обеспечение.   -  person David Heffernan    schedule 20.01.2014
comment
Нет необходимости устанавливать мое программное обеспечение! См. новые комментарии в процедуре OpenUserGuidePDF.   -  person jcfaria    schedule 20.01.2014
comment
Где PDF-файл? Вы не можете сделать SSCCE? Что меня здесь расстраивает, так это то, что вопрос не имеет никакого отношения к Delphi. Это 100% проблема, связанная с передачей параметров командной строки в определенные программы просмотра PDF. Почему бы вам не задать вопрос в духе: Я передаю следующие аргументы в Acrobat.exe с намерением открыть документ в определенном месте. Документ открывается, но не в нужном месте. Какие параметры мне нужно передать, чтобы достичь моей цели? Если вы хотите добиться прогресса, вам нужно научиться упрощать задачу.   -  person David Heffernan    schedule 20.01.2014


Ответы (2)


Я хотел бы поблагодарить всех замечаний и предложений!

Я счел более практичным распространять (совместно с Tinn-R) портативную версию СуматраPDF. Если Sumatra является системой по умолчанию, она будет использоваться. В противном случае будет использоваться портативная версия.

Ниже расчетное решение:

procedure TfrmTinnMain.OpenUserGuidePDF(sWhere: string);
var
  sFile,
   sViewerDefault,
   sPathSumatra,
   sParameter: string;

begin
  sFile:= sPathTinnR +
          '\doc\User guide.pdf';

  sParameter:= '-reuse-instance ' +
               '-named-dest ' +
               sWhere;
  try
    sViewerDefault:= GetAssociation('.pdf');

    if pos('Sumatra',
           sViewerDefault) > 0 then
      // Open default PDF viewer
      ShellExecute(0,
                   'open',
                   Pchar(sFile),
                   Pchar(sParameter),
                   nil,
                   sw_shownormal)
    else begin
      sPathSumatra:= sPathTinnR +
                     '\sumatra\SumatraPDF.exe';

      // Open SumatraPDF viewer
      OpenCmdLine(sPathSumatra +
                  ' "' +
                  sFile +
                  '"' +
                  sParameter,
                  sw_shownormal);
    end;
  except
    MessageDlg('PDF viewer is not accessible!',
               mtInformation,
               [mbOk],
               0);
  end;
end;

Звонок:

procedure TfrmTinnMain.menHelUserGuideClick(Sender: TObject);
begin
  OpenUserGuidePDF('"Contents"');
end;

Необходимые функции и процедуры:

{ Execute a complete shell command line without waiting. }
function OpenCmdLine(const CmdLine: string;
                     wWindowState: Word): Boolean;
var
  sUInfo: TStartupInfo;
  pInfo : TProcessInformation;

begin
  { Enclose filename in quotes to take care of long filenames with spaces. }
  FillChar(sUInfo,
           SizeOf(sUInfo),
           #0);
  with SUInfo do
  begin
    cb         := SizeOf(sUInfo);
    dwFlags    := STARTF_USESHOWWINDOW;
    wShowWindow:= wWindowState;
  end;
  Result:= CreateProcess(nil,
                         PChar(CmdLine),
                         nil,
                         nil,
                         False,
                         CREATE_NEW_CONSOLE or
                         NORMAL_PRIORITY_CLASS,
                         nil,
                         nil {PChar(ExtractFilePath(sFileName))},
                         sUInfo,
                         pInfo);
end;

function GetAssociation(const DocFileName: string): string;
var
  FileClass: string;
  Reg: TRegistry;

begin
  Result:= '';
  Reg:= TRegistry.Create(KEY_EXECUTE);
  Reg.RootKey:= HKEY_CLASSES_ROOT;
  FileClass:= '';
  if Reg.OpenKeyReadOnly(ExtractFileExt(DocFileName)) then
  begin
    FileClass:= Reg.ReadString('');
    Reg.CloseKey;
  end;
  if FileClass <> '' then begin
    if Reg.OpenKeyReadOnly(FileClass +
                           '\Shell\Open\Command') then
    begin
      Result:= Reg.ReadString('');
      Reg.CloseKey;
    end;
  end;
  Reg.Free;
end;

Любое предложение будет приветствоваться!

Всего наилучшего,

Дж. К. Фариа

person jcfaria    schedule 20.01.2014
comment
В вашем коде много проблем. Во-первых, второй параметр CreateProcess должен быть доступен для записи. Ваш код не будет работать с литералом в сборке Unicode. Используйте UniqueString, чтобы сделать этот параметр изменяемым. Вам нужно использовать try/finally для защиты объектов. Например, экземпляр TRegistry. Ваш вызов CreateProcess приводит к утечке дескрипторов, возвращенных в параметре информации о процессе. - person David Heffernan; 21.01.2014

Позвольте Windows решить, какое средство просмотра по умолчанию для этого типа файлов:

ShellExecute(0, 'open', Filename, nil, nil, SW_SHOW);
person jpfollenius    schedule 20.01.2014
comment
-1 Пользователь хочет перейти к определенному месту в документе - person David Heffernan; 20.01.2014
comment
Я знаю этот общий вызов ShellExecute. Но я хочу быть более конкретным: я хочу открыть руководство пользователя (Sumatra, Foxit и Adobe) в любом указанном месте назначения. Например: База данных, Конфигурация и т.д. Процедура уже работает для Sumatra, но не для Foxit и Adobe Reader. - person jcfaria; 20.01.2014