• 0
Авторизация  
Вадим Смоленский

Хук на клавиатуру

Вопросы

В декабре я задавал здесь вопрос о борьбе с перехватами нажатий клавиш компонентом TWebBrowser. Продвинутый пользователь Kami посоветовал тогда, раз уж меня интересует только Windows, поставить хук на клавиатуру. Поделился полезной ссылкой. Добавил, что можно еще много чего нагуглить. Что-то действительно нагуглилось - но не в том объеме, чтобы я смог четко понять, как это следует делать. Вопросов много. Куда именно должна быть воткнута функция KeyboardProc? Что в ней должно содержаться, чтобы управление передавалось уже написанному обработчику события FormKeyDown? Многие также упоминают о возникающих проблемах с юникодом, и хорошо было бы понять, как уберечься от них.

Буду очень признателен, если кто-нибудь осветит эту темную для меня материю.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

24 ответа на этот вопрос

  • 0

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
4 минуты назад, Barbanel сказал:

Могу выслать в личку если интересует.

Очень обяжете. Можно даже сразу на электронную почту:  vsСОБАКАsusi.ru

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 1
3 часа назад, Вадим Смоленский сказал:

Куда именно должна быть воткнута функция KeyboardProc?

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

3 часа назад, Вадим Смоленский сказал:

Что в ней должно содержаться, чтобы управление передавалось уже написанному обработчику события FormKeyDown?

Ну вот например: https://ru.stackoverflow.com/a/538552/192901  (просмотрел бегло, но криминала нет, по крайней мере - система не помрет, что, кстати, весьма возможно при использовании глобальных хуков).

вместо SendMessage подставить myForm.OnKeyDown(...);

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

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
9 часов назад, kami сказал:

Ссылка внятная, спасибо. Но пока не помогло. Вот такой конструктор формы у меня:

constructor TForm1.Create(AOwner: TComponent);
begin
 inherited;
 CurrentHook := SetWindowsHookEx(WH_KEYBOARD, @KeyboardProc, HInstance, 0);
end;

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

Кстати, в комментариях по ссылке вот еще что пишут:

Цитата

...ваша ловушка не будет работать для 64-битных процессов (подразумевая, что ваша DLL - 32-битная) - потому что 32-битную DLL нельзя загрузить в 64-битный процесс (и наоборот).

Иными словами, чтобы установить действительно глобальную ловушку, вам нужно написать два приложения и две DLL - на 32 и 64 бита. И устанавливать/удалять их одновременно.

У меня 64. Не в этом ли закавыка? Или же сказанное относится лишь к WH_KEYBOARD_LL ?  Я не смог понять.

Изменено пользователем Вадим Смоленский

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 1
9 часов назад, Вадим Смоленский сказал:

Вот такой конструктор формы у меня:

Было бы неплохо почитать, чем отличается WH_KEYBOARD от WH_KEYBOARD_LL. Принципы действия абсолютно разные.

Цитата

This hook may be called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

Вполне возможно, что окна браузера находятся в другом потоке. Посему для начала экспериментов вам нужен именно WH_KEYBOARD_LL

 

9 часов назад, Вадим Смоленский сказал:

CurrentHook как был равен нулю, так и остался

Ну так нужно же понимать причину - почему он равен нулю. Обращаемся к первоисточнику.

Цитата

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

Смотрите, что вернется и узнавайте причину. Можно так:

if CurrentHook = 0 then
  RaiseLastOSError.
9 часов назад, Вадим Смоленский сказал:

У меня 64. Не в этом ли закавыка?

Нет. Здесь имеется ввиду не разрядность операционной системы, а именно разрядность процессов, запущенных в ней (в 64-битный процесс может быть загружена только 64-битная dll. В 32бита - 32). Кроме того, _LL хуки не зависят от разрядности процесса, поскольку работают в контексте установившего хук потока, им dll не требуется.

Изменено пользователем kami

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
4 часа назад, kami сказал:

Ну так нужно же понимать причину - почему он равен нулю.

RaiseLastOSError помог установить причину:

System Error.  Code: 1428.
Cannot set nonlocal hook without a module handle

Только не знаю пока, как это трактовать и что с этим делать.

Интересно и то, что с WH_KEYBOARD_LL  хук срабатывает: CurrentHook ненулевой, и по нажатию клавиши управление переходит в KeyboardProc. Но ведь это, как я понял, глобальный хук, он будет перехватывать нажатия у других приложений. А это уже будет лишним.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 1
15 минут назад, Вадим Смоленский сказал:

Только не знаю пока, как это трактовать и что с этим делать.

по тексту этой ошибки гуглится очень многое.

Основной посыл: для WH_KEYBOARD, если указывать HInstance, то он должен быть инстансом dll. Потому что exe не инжектнется в чужой процесс.

Можно указать вместо HInstance - 0, а последний параметр выставить в TThread.Current.ThreadID, но (повторюсь) я не уверен, что веббраузер работает только в контексте основного потока приложения.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, kami сказал:

Можно указать вместо HInstance - 0, а последний параметр выставить в TThread.Current.ThreadID

Попробовал, и получилось! С веббраузером проблем не наблюдается - даже когда фокус на нем, управление переходит в KeyboardProc.

Осталось последнее: понять, как из KeyboardProc(nCode: integer; wParam: integer; lParam: integer) вызвать FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState). В идеале хотелось бы суметь передать без искажений все три параметра: Key, KeyChar и Shift.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, Вадим Смоленский сказал:

В идеале хотелось бы суметь передать без искажений все три параметра: Key, KeyChar и Shift. 

Так а в чем загвоздка-то?
внутри этого хука доступен сканкод нажатой клавиши. Есть GetAsyncKeyState и много чего еще

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
37 минут назад, kami сказал:

Так а в чем загвоздка-то?

Загвоздка в моей дремучести, которую не ликвидировать враз. Нашел описание GetAsyncKeyState, но это несколько не о том, туда надо передавать параметром уже известный код клавиши. А что за снанкод? Где про него можно узнать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 минуту назад, Вадим Смоленский сказал:

А что за снанкод?

https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644984(v=vs.85).aspx

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Более того - там уже есть виртуальный код клавиши. Еще проще.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Да, с виртуальным кодом клавиши проблем нет. Сложнее оказалось с параметром Shift  -  но нагуглилась страничка с хорошим примером, там я взял всё, что мне было нужно. Вот как теперь выглядит функция:

function KeyboardProc(nCode: integer; wParam: integer; lParam: integer ): LongWord; stdcall;
var W: Word;
    C: Char;
    KeyUp : Boolean;
    KeyState: TKeyboardState;
    TheShift: TShiftState;
begin
  if (nCode < 0) then
  begin
    Result := CallNextHookEx(CurrentHook, nCode, wParam, lParam);
    Exit;
  end;
  KeyUp := ((lParam AND (1 shl 31)) <> 0);
  if not KeyUp then
     begin
      W:=wParam;
      C:=Chr(wParam);
      TheShift:=[];
      if GetKeyboardState(KeyState) then
          begin
           if (lParam AND (1 shl 29))<>0 then TheShift:=[ssAlt];
           if (GetKeyState(VK_CONTROL) AND (1 shl 15))<>0 then TheShift:=TheShift+[ssCtrl];
           if (GetKeyState(VK_SHIFT) AND (1 shl 15))<>0 then TheShift:=TheShift+[ssShift];
          end;
      Form1.FormKeyDown(Form1,W,C,TheShift);
     end;
  Result := CallNextHookEx(CurrentHook, nCode, wParam, lparam);
end;

Придирчиво еще не тестировал, но в первом приближении всё работает.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Выявилась проблемка. Когда FormKeyDown вызывался как обработчик события, в самом его конце выполнялся оператор Key:=0. Это обнуляло нажатие клавиши, гарантировало, что оно не сработает каким-то дополнительным, непредвиденным образом. В новой конфигурации это не работает, я уже столкнулся с досадными побочными эффектами. Нажатие нужно обнулить в обработчике хука KeyboardProc. Но как?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
10 минут назад, Вадим Смоленский сказал:

Нажатие нужно обнулить в обработчике хука KeyboardProc. Но как?

не вызывать следующий хук

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 минуты назад, kami сказал:

не вызывать следующий хук

Вы имеете в виду, убрать эту строчку:

Result := CallNextHookEx(CurrentHook, nCode, wParam, lparam);

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

И что присваивать результату в таком случае? Ноль?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
7 минут назад, Вадим Смоленский сказал:

Вы имеете в виду, убрать эту строчку:

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

 

7 минут назад, Вадим Смоленский сказал:

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

А вы считаете, что единственный на всю систему установили хук на клавиатуру ? :))) Их десятки, если не сотни. Тот же WH_KEYBOARD: система будет вызывать последовательно все KeyboardProc в порядке (емнип), обратном времени установки хука. Естественно, если вы скажете системе "можно отдать на обработку следующему хуку".

 

7 минут назад, Вадим Смоленский сказал:

И что присваивать результату в таком случае? Ноль

Внимательно читаем раздел Result в https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644984(v=vs.85).aspx

Там, оказывается, уже всё предусмотрено и потенциально не надо даже пропускать вызов последующего хука. Хотя - лучше пропустить.

Изменено пользователем kami

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, kami сказал:

Внимательно читаем раздел Result

Такого раздела там нет. Вы, видимо, имели в виду раздел "Return value". В частности, там сказано, что CallNextHookEx нужно вызывать в том случае, если (цитирую) the hook procedure did not process the message. То есть, насколько я понял, если моя процедура FormKeyDown установила Key:=0, то это можно трактовать в том ключе, что message обработан, и тогда CallNextHookEx вызывать необязательно. Так или не так?

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

1 час назад, kami сказал:

Тот же WH_KEYBOARD: система будет вызывать последовательно все KeyboardProc в порядке (емнип), обратном времени установки хука.

Я так и не смог понять разницы между WH_KEYBOARD_LL и WH_KEYBOARD. Мне казалось, что лишь первый вариант относится ко всей системе целиком и проверяется всеми приложениями, где есть хук, в то время как второй ограничен лишь одним-единственным приложением. Всё сложнее?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 минуты назад, Вадим Смоленский сказал:

тогда CallNextHookEx вызывать необязательно. Так или не так?

Мало. If the hook procedure processed the message, it may return a nonzero value to prevent the system from passing the message to the rest of the hook chain or the target window procedure.

3 минуты назад, Вадим Смоленский сказал:

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

Не вижу ничего запутанного. В штатной ситуации - вызываете следующий хук. А когда нужно, чтобы клавиша не добралась до адресата - не вызываете.
Или же - вызываете, но Result возвращаете <>0.
Я не знаю, какой из двух вариантов лучше - я давно уже не работал с WinAPI в этом ракурсе. Эксперименты вам всё покажут, там делов на 20 минут.

5 минут назад, Вадим Смоленский сказал:

Я так и не смог понять разницы между WH_KEYBOARD_LL и WH_KEYBOARD.

Разные механизмы работы. Оба - system-wide.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
4 часа назад, kami сказал:

Или же - вызываете, но Result возвращаете <>0.

Это непонятно. Какой именно <>0? Любой кроме ноля? А если, как советуют, присвоить CallNextHookEx(CurrentHook, nCode, wParam, lparam), то это всегда будет ноль, что ли?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
В 15.06.2018 в 19:23, Вадим Смоленский сказал:

Какой именно <>0?

Что может быть непонятно во фразе non-zero value из первоисточника? Скорее всего, возвращаемый результат интерпретируется как BOOL. У любого булеан-типа есть два значения: 0 = False, не 0 = True.

Что там присваивают конкретные компиляторы для значения True - это их проблемы. Сравнение всегда ведется с нулем.

 

В 15.06.2018 в 19:23, Вадим Смоленский сказал:

А если, как советуют, присвоить CallNextHookEx(CurrentHook, nCode, wParam, lparam), то это всегда будет ноль, что ли?

Может да, а может и нет. Откуда вы знаете, какую логику заложили другие разработчики в свои хуки? Это исключительно их дело - считают ли они нужным

Цитата

to prevent the system from passing the message to the rest of the hook chain or the target window procedure.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
17 часов назад, kami сказал:

Что может быть непонятно во фразе non-zero value из первоисточника?

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

Так что теперь в тех случаях, когда нажатие клавиши точно обработано моим приложением и я больше не хочу от него никаких сюрпризов, просто присваиваю Result:=1. Всё работает, как часы. Огромное спасибо!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
6 часов назад, Вадим Смоленский сказал:

внутри функции CallNextHookEx еще много всякого разного может происходить

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

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

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти
Авторизация  

  • Похожий контент

    • От Вадим Смоленский
      Как преобразовать тип HString в обычную строку?
      Нагуглил упоминания о функции TWindowsString.HStringToString, которая должна находиться в System.WinrtHelpers. Но такого юнита в поставке Delphi не наблюдаю. Может, его можно где-нибудь раздобыть? Или существуют иные способы?
    • От Вадим Смоленский
      Пытаясь разобраться с проблемой размещения файлов в UWP-приложениях, пришел к необходимости создать в своем коде объект класса ApplicationData. Этот класс описан на соответствующей странице майкрософтовской документации, где в самом начале обозначено следующее:
      Namespace:  Windows.Storage
      Assemblies:  Windows.Storage.dll, Windows.dll
      Юнита с именем Windows.Storage или Winapi.Windows.Storage я в поставке Delphi не наблюдаю. Неудивительно, что попытки вставить соответствующие идентификаторы в раздел uses ни к чему не ведут. Как в таких случаях поступают? Откуда берут необходимое?
    • От Вадим Смоленский
      Хочу еще раз обратиться к коллективному разуму в надежде все-таки разобраться с тем, как должен быть устроен пакет appx для размещения в Microsoft Store. А именно - как организовать размещение настроек и пользовательских файлов в специально отведенных для этого папках, а не в установочном каталоге, что запрещено.
      Вся информация, которую я смог к сегодняшнему дню накопать, размещена на этой странице майкрософтовской документации и сводится к тому, что при инсталляции пакета appx автоматически создается хранилище пользовательских данных из трех папок. Цитирую: one for local files, one for roaming files, and one for temporary files. В общем-то, это удобно - по крайней мере, не нужно заботиться о деинсталляции: ровно эти же папки при удалении программы и сотрутся.
      Вопрос в том, как к этому хранилищу обратиться. В идеале, конечно же, хотелось бы иметь возможность уже в Deployment Manager обозначить для некоторых файлов, что они должны быть положены в это хранилище, а не в установочный каталог. Но как это можно сделать и можно ли сделать вообще, мне понять не удалось. Списка констант для параметра Remote Path я найти не смог, а отдельные упоминаемые тут и там константы ('res', 'assets', 'classes', 'library') явно относятся к мобильным платформам, а не к Windows.
      Если так поступить нельзя, остается класть всё в установочный каталог, а при первом запуске приложения переносить в нужное место. Но опять же: как приложению заполучить полный адрес этого места? Ведь это вовсе не привычные нам CSIDL_APPDATA и не CSIDL_COMMON_APPDATA, это нечто новое, доселе невиданное, в документации называемое "local app data store". На той же странице есть пример соответствующего кода с использованием класса ApplicationData, но он написан на незнакомом мне C#. Там есть также ссылки на описание класса ApplicationData, но по ссылкам тоже C# и C++. Был бы очень признателен, если бы кто-нибудь показал мне, как заполучить адрес local app data store средствами Delphi.
    • От Вадим Смоленский
      Целый месяц бодаюсь с Microsoft Store, пытаясь разместить у них свое UWP-приложение в виде пакета appx. Получаю отлуп за отлупом. Проблема: приложение должно располагать папкой для пользователя, отличной от установочного каталога, где он мог бы сохранять файлы. Там же должны храниться файлы настроек. Раньше, создавая дистрибутив в InnoSetup, я всегда предусматривал создание такой папки по адресу <user>\AppData\Roaming\MyApp. Теперь всё полагается указывать в разделе Deployment - но как там указать такой адрес? Вроде бы, есть графа "Remote path", и логично существовать каким-то макросам для нее, вроде AppData - но никакой информации на эту тему я найти не смог. Видел лишь упоминания таких макросов, как res, assets, classes, library - однако все они, как я понял, относятся к мобильным платформам.
      Попытки обходных маневров не удались. Сначала я решил всё класть в установочный каталог, а при первом запуске создавать нужную папку и перебрасывать в нее несколько файлов. На моем компьютере это работает, но при тестировании в Microsoft Store отчего-то валится (присылают скриншот с сообщением "Access is denied"). То ли нельзя стирать файлы в установочном каталоге, то ли нельзя в таком режиме создавать новый каталог. Потом я прочитал где-то, что UWP-приложениям всё равно, где лежат файлы, они могут их менять даже в установочном каталоге. Попробовал всё валить в одну кучу и так работать. Увы, опять отрапортовали о падении ("The app tries to create a file under WindowsApps folder").
      Получается, что все-таки нужно как-то заставить appx-дистрибутив создавать при установке папку по адресу <user>\AppData\Roaming\MyApp. Но как?
      Спрашивал на experts-exchange, там знатоков не нашлось. Если и здесь нет, может, кто-нибудь хотя бы подскажет, в каких местах есть смысл спросить?
    • От Вадим Смоленский
      Проект для Windows, автономная БД SQLite, никаких клиент-серверных дел, компоненты и операции самые простые: SQL-запрос в TFDQuery, вызов метода Open. Всё работает нормально, но отдельные пользователи жалуются на эпизодические непредсказуемые падения при поиске. Интересно, что после каждого такого падения всё опять функционирует нормально, но потом приложение не удается нормально закрыть, приходится вызывать диспетчер задач.
      Стабильно воспроизвести не могут ни пользователи, ни я сам. Мне удалось это считанные несколько раз - я лишь смог убедиться через отладчик, что проблема при закрытии связана именно с базой данных: вставлял в обработчик FormCloseQuery оператор TFDConnection.Close - и программа пару раз упала именно на этом операторе.
      Найти корень зла пока не удалось. Показалось только, что проблема возникает лишь тогда, когда поиск приводит к слишком большому (несколько тысяч) числу записей в TFDQuery.
      Может, стоит поменять какие-нибудь установки в TFDConnection или TFDQuery ?
    • От Вадим Смоленский
      Упаковываю свое приложение в appx при помощи кнопки Deploy в Deployment Manager. При этом в разделе Опций "Manifest File" выставлено "Auto Generate". В итоге получается файл AppxManifest.xml; насколько я понимаю, этот манифест также включается в состав итогового пакета appx.
      Пробую загрузить получившийся appx в Microsoft Store. Грузится очень долго и в итоге выдает ошибку: "You don't have permissions to specify the following namespaces in the appx manifest file of the package MyApp.appx: restricted namespace: http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
      В файле AppxManifest.xml действительно отыскивается такой фрагмент. Убираю его, заодно убираю фрагмент  <Capabilities> ... </Capabilities> (если не убрать, получившийся xml даже не отобразится в браузере), переименовываю в MyApp.manifest, меняю установку для манифеста на "Custom", указываю имя. Всё повторяю. Результат ровно тот же.
      Полностью меняю содержимое MyApp.manifest, беря шаблон отсюда. Всё повторяю. Результат опять тот же. Опять магазину не нравится фрагмент манифеста насчет restricted capabilities, которого теперь, по идее, там быть не должно.
      Такое ощущение, что при формировании пакета appx не имеют никакого значения установки по поводу манифеста. Он всегда запихивается в appx в некоем дефолтном варианте, повлиять на который невозможно.
      Или все-таки возможно?
    • От Вадим Смоленский
      При упаковке Windows-приложения в appx все дополнительные файлы приходится класть в одну папку с исполнимым файлом, ибо Deployment Manager, судя по всему, не предусматривает возможности сразу положить их в AppData. Но я все-таки хочу, чтобы некоторые файлы были легко доступны пользователям, поэтому организовал приложение так, что оно при первом запуске создает новый каталог в AppData:
      MyDirectory:=GetSpecialFolderPath(CSIDL_APPDATA)+'\MyApp'; CreateDir(MyDirectory); TDirectory.SetAttributes(MyDirectory,[TFileAttribute.faNormal]); После этого в созданный каталог переносятся некоторые файлы, и всё работает хорошо, за исключением одного момента: этот новый каталог невозможно открыть, например, в Проводнике. Из самого приложения легко можно открыть диалоговое окно и увидеть в нем этот каталог и все файлы; можно их читать и в них писать, но вне приложения этот каталог невидим. Всё равно что не существует.
      Мне казалось, что присвоение каталогу атрибута faNormal дожно все проблемы решить. Увы, не решает. В чем тут закавыка?
    • От Вадим Смоленский
      Устанавливая и запуская свое Windows-приложение, предназначенное для Microsoft Store и запакованное в appx, наткнулся на интересный феномен. Чтобы узнать адрес текущего каталога, я всегда использовал функцию SysUtils.GetCurrentDir. Полученный полный адрес был мне нужен, например, чтобы показывать в TWebBrowser файлы хелпов (относительные адреса там почему-то не прокатывают). Раньше адресом текущего каталога всегда был адрес, где лежит исполнимый файл - условно говоря, C:\Program Files\MyApp. Теперь, когда пакет создается по принципам UWP,  исполнимый файл и прочее хозяйство кладется в каталог C:\Program Files\WindowsApps\MyApp_1.0.0.0_x86__sp51hrchc9zqj.  При этом функция GetCurrentDir почему-то возвращает совершенно другой адрес, а именно C:\WINDOWS\system32. Соответственно, TWebBrowser ничего не показывает.
      Функция SysUtils.GetDir ведет себя так же.
      Как быть?
    • От Вадим Смоленский
      В RAD Studio, начиная с Berlin, предусмотрена возможность создавать пакеты appx для загрузки приложений в Microsoft Store. К сожалению, материалов на эту тему пока немного, толковая ссылка нашлась лишь одна:
      https://community.embarcadero.com/blogs/entry/appx-development-for-windows-10-store
      Там толково объяснено, как создать appx для приложения из одного исполнимого файла. Я попробовал, всё получилось. Но как быть, если пакет должен содержать и другие файлы? В моем случае это файл базы данных, целая папка html-файлов для хелпов, и т.п. Где я должен их указать? Логично было бы предположить, что за это ответственен манифест (Project => Options => Application => Manifest File), который можно кастомизировать. Но сколько я ни гуглил, не смог найти ничего о структуре этого манифеста применительно к файлам. Похоже, манифест здесь все-таки ни при чем.
      Раньше я всё это делал в Inno Setup. Как поступать теперь - непонятно...
    • От Вадим Смоленский
      Коллеги! Хотел бы еще раз привлечь ваше внимание к проблеме, недавнее обсуждение которой, к сожалению, заглохло. Не был бы столь настойчив, но это касается всех из нас, кто работает с Windows. Речь о дефекте платформы FMX, выражающемся в том, что щелчок по иконке в панели задач не приводит к сворачиванию приложения, как это задумано. Пользователь sargon предложил следующее решение:
      WM_SYSCOMMAND: begin if wParam = SC_MINIMIZE then PlatformWin.MinimizeApp else if wParam = SC_RESTORE then PlatformWin.RestoreApp; DefWindowProc(HWND, uMsg, wParam, LPARAM); sleep(50); // у FMX какая-то беда с потоками, иногда при нажатии по иконуе приложения в TaskBar окно не сворачивается а снова активируется, sleep уменьшает количество таких глюков Winapi.Windows.SetActiveWindow(FormToHwnd(LForm)); // после разворота активирует окно - проверил в Berlin и Tokyo 10.2.2 end; Фрагмент нужно вставить в FMX.Platform.Win в функцию function WndProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
      Я последовал этому совету, но стало только хуже: перестало работать сворачивание даже при щелчке по системной кнопке "Minimize". С щелчком по иконке тоже никаких сдвигов. Что здесь может быть не так?
  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу