Страница 1 из 1

Добавлено: 25 Октябрь 2004, 19:41
Aragorn
Хочу поделиться. Жду здоровой критики, советов, замечаний...

Решаем проблему переключения в WinXP и введение в сабклассинг.

часть 1 (не лезет вся, чевой-то :))

Общеизвестный факт того, что в приложениях, написанных на любой версии Clarion,
и работающих в среде WindowsXP, не переключается раскладка клавиатуры с русской
на английскую и наоборот, давно выводит из себя пользователей и заставляет
разработчиков искать различные способы переключения клавиатуры. Среди уже
известных способов наибольшей популярностью пользуются следующие четыре:
- удаление из памяти модуля ctfmon.exe;
- установка альтернативного переключателя клавиатуры (Ninja или Punto Switcher);
- использование шаблона Юрия Философова entrypoint.tpl;
- и, наконец, было подмечено, что, если выполнять переключение клавиатуры не
обычным ctrl+shift/alt+shift, а ctrl+shift shift/alt+shift shift (то есть
нажимая дважды shift, не отпуская ctrl(alt)), то переключение происходит. Что
и было некоторыми разработчиками взято даже на эксплуатацию.
Причина непереключения клавиатуры кроется в висящем в памяти приложении ctfmon.exe,
появляющемся там после установки на компьютер Microsoft Office любой версии.
Что же это за программа? Вот что примерно написано на сайте Microsoft:
"Ctfmon.exe активизирует процессор текстового ввода (TIP) компонента "альтернативный ввод данных" и языковую панель Microsoft Office. Ctfmon.exe производит мониторинг активных окон и предоставляет поддержку клавиатуры перевода, распознавания речи и рукописных символов и других технологий альтернативного ввода данных. Ctfmon.exe остаётся в памяти даже после того, как все приложения Microsoft Office закрыты."
Проблема, которую необходимо решить для того, чтобы выполнялось нормальное
переключение раскладки клавиатуры, состоит в следующем. При нажатии комбинации клавиш, отвечающих за переключение клавиатуры, в процедуру-обработчик сообщений элемента окна поступают ДВА сообщения одновременно о том, что нужно переключить клавиатуру. Что и происходит: туда и обратно. В итоге имеем то, что было до нажатия клавиш.
Что же делать? Подсказку я нашёл в шаблоне Юрия Философова.
Для того, чтобы переключение произошло, необходимо запретить выполнение второй команды на переключение. Следовательно, нужно перехватывать передаваемые в процедуру-обработчик сообщения, отлавливать сообщения о переключении и передавать их в процедуру через одно.
Вот примерно для таких задач есть такая штука, как сабклассинг. Долгое время я читал советы о том, что нужно "просабклассить то-то и то-то и отлавливать то-то и то-то". И вот я знаю, что имелось в виду (надеюсь)!
Чтобы перехватывать сообщения, передаваемые в процедуру-обработчик сообщений элемента окна, нужно завести в программе процедуру, имеющую такой же интерфейс, что и процедура-обработчик:

Код: Выделить всё

 SubClassFunc Procedure(UShort,Short,UShort,Long),Long,Pacal
 ...
 SubClassFunc Procedure(hWnd, wMsg, wParam, lParam)
    Code
    ...
Первый параметр - окно, принимающее сообщения;
второй параметр - идентификатор сообщения;
третий и четвёртый параметры - дополнительная информация, зависящая от сообщения.

Теперь нам надо "обмануть" программу, заставив посылать сообщения от компонентов окна в нашу процедуру. Для этого в Clarion есть два свойства элемента: Prop:WndProc и Prop:ClientWndProc.
Свойство Prop:WndProc позволяет установить или считать адрес процедуры-обработчика сообщений окна или определённого элемента управления; свойство Prop:ClientWndProc - установить или считать адрес процедуры-обработчика клиентской части окна (т.е. всего окна, за исключением заголовка м строки статуса).

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

Код: Выделить всё

 CallWindowProc Procedure(Long,Unsigned,Signed,Unsigned,Long),Long,Pascal
Здесь первый параметр - адрес той самой процедуры-обработчика, которую мы просабклассили. Его
мы должны запомнить:

Код: Выделить всё

   SavedProc Long
   ...
   SavedProc = <окно или элемент>{Prop:WindProc}
Следовательно, порядок действий таков:
перед тем, как произойдёт какое-либо событие, мы должны:
1) запомнить адрес процедуры-обработчика для окна или его элемента:

Код: Выделить всё

   SavedProc = <окно или элемент>{Prop:WindProc}
2) вместо адреса процедуры-обработчика подсунуть программе адрес нашей процедуры.

Код: Выделить всё

   <окно или элемент>{Prop:WindProc} = Address(SubClassFunc)
В нашем обработчике:
3) выполнить нужные нам действия, проанализировав второй передаваемый параметр (идентификатор сообщения);
4) вернуть управление в настоящий обработчик:

Код: Выделить всё

      Return(CallwindowProc(SavedProc, hWnd, wMsg, wParam, lParam)
5) желательно потом вернуть упаравление в настоящий обработчик совсем:

Код: Выделить всё

      <окно или элемент>{Prop:WindProc} = SavedProc
В Help Клариона есть подробные примеры того, как это работает и для чего это можно использовать.

Написал: Aragorn(147)

Добавлено: 25 Октябрь 2004, 19:42
Aragorn
А вот и имплементация...
Часть 2

В связи со всем этим я переделал шаблон:

Код: Выделить всё

#!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#At(%GlobalData)
#If (%IsWXPSwitch)                                       !это для включения/отключения шаблона
XP:Run       ULong                                       !Признак ОС WindowsXP
XP:CtlNo     Long                                        !Номер элемента управления
XP:WndProc   Ulong                                       !адрес истинного обработчика
XP:WndStatus Byte                                        !состояние переключателя клавиатуры
#EndIf    
#EndAt
#At (%GlobalMap)
#If (%IsWXPSwitch)                                       !это для включения/отключения шаблона
   #If (%CallWindowProc Or %GetVersionProc)              !если требуется описать CallWindowProc или
       Module('Windows API')                             !функцию получения версии Windows
   #EndIf
   #If (%CallWindowProc)                                 !Требуется описание CallWindowProc
          CallWindowProc(Long,UnSigned,UnSigned,UnSigned,Long),Long,Raw,Pascal,Name('CallWindowProcA')
   #EndIf
   #If (%GetVersionProc)                                 !Требуется описание GetVersion
          GetVersion(),Ulong,Pascal,Raw
   #EndIf
   #If (%CallWindowProc Or %GetVersionProc)
       End
   #EndIf
   XpKbdHandler(Unsigned,Unsigned,Unsigned,Long),Long,Pascal !А это наш перехватчик
#EndIf
#EndAt
#!------------------------------------------------------------------------------------------------------------
#At(%ProgramProcedures)
#If (%IsWXPSwitch)                                       !это для включения/отключения шаблона
XPKbdHandler Function(Loc:hWnd,Loc:usMsg,Loc:WParam,Loc:LParam)
       Code
          Case Loc:usMsg
           of 0050H                                      !запрос на замену языка
              If XP:WndStatus=False                      !принцип триггера
                 XP:WndStatus=True
                 Return(0)                               !повторное переключение - нафиг!
              Else XP:WndStatus=False End
          End
          Return(CallWindowProc(XP:WndProc,Loc:hWnd,Loc:usMsg,Loc:WParam,Loc:LParam))
#EndIf
#EndAt
#!------------------------------------------------------------------------------------------------------------
#At(%WindowEventHandling,'OpenWindow'),Priority(3500)
#If (%IsWXPSwitch)                                       !это для включения/отключения шаблона
  XP:WndStatus=False                                     !первичная установка
  XP:Run=Band(GetVersion(),0FFFFh)                       !Получаем версию XP
  If Band(XP:Run,0FFh)>=5 And Band(XP:Run,0FF00h)=100H Then XP:Run=True
  Else                                                        XP:Run=False End
#EndIf
#EndAt
#!------------------------------------------------------------------------------------------------------------
#AT(%AcceptLoopBeforeEventHandling)                      !Перед циклом обработки событий
#If (%IsWXPSwitch)                                       !это для включения/отключения шаблона
   If Xp:Run                                             !Если это XP
      Case Event()                                       !проверяем события
       Of Event:LoseFocus Orof Event:Suspend             !покинули элемент управления - 
          Xp:CtlNo{Prop:WndProc}=XP:WndProc              !Восстановили истинный обработчик
       Of Event:GainFocus Orof Event:Resume Orof Event:Selected !попали в новый - 
          Xp:CtlNo{Prop:WndProc}=XP:WndProc              !восстановили обработчик предыдущего
          XP:WndProc=Selected(){Prop:WndProc}            !Запомнили адрес обработчика
          Selected(){Prop:WndProc}=Address(XpKBDHandler) !подставили свой
          XP:CtlNo=Selected()                            !запомнили номер элемента управления
      End
   End
#EndIf
#EndAt
#!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Всё? почти. Теперь представим себе большой проект. Много библиотек + один исполняемы модуль. Чтобы не
вставлять всё это хозяйство в Global Extensions каждого, сделаем-ка вот что. Это только для ABC!!!

1. Откроем ABCHAIN.TPL.
2. Вставим текст шаблона перед концом шаблона #APPLICATION.
3. Чтобы управлять всем этим, сделаем красиво:
В #SHEET добавим новую закладку:

Код: Выделить всё

  #Tab('&Клавиатура в WinXP')
    #Display
    #Display('Разработка шаблона - Aragorn')
    #Display('В шаблоне использованы принципы работы шаблона')
    #Display('Юрия Философова (Entrypoint)')
    #Display
    #Display
    #Prompt('Включить контроль переключения клавиатуры',Check),%IsWXPSwitch,Default(1),At(6)
    #Display('       в Windows XP')
    #Display
    #Enable(%IsWXPSwitch)
       #Boxed('Параметры')
       #Prompt('Описать CallWindowProc',Check),%CallWindowProc,Default(1),At(8)
       #Prompt('Описать GetVersion',Check),%GetVersionProc,Default(1),At(8)
       #EndBoxed
    #EndEnable
  #EndTab
О! теперь красиво... Правда, придётся перерегистрить ABC, а все модули - перекомпилить.
Написал: Aragorn(147)

Добавлено: 25 Октябрь 2004, 20:07
Гость
Привет !

Только хочется добавить, что всю эту "красоту" нужно очень хорошо протестировать ... и молиться, что бы после очередной заплатки Window все не заклинило - что для серийных программ плохо. А развитие Windows - непредсказуемо, что показал XP SP2.

Шаблоны Юрия Философова entrypoint.tpl - очень хороши, и большое спасибо, что он их сделал и дал возможность ими пользоваться, но например - если сделать в поле вызов справочника по щелчку правой кнопки мыши (через AlertKey) - то первый раз все OK !, а на второй вызов - программа зависает намертво. Проверено. Увы !

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

А вообще, конечно, странно все происходит. Я понимаю, что ту же проблему можно наблюдать и Microsoft Office (поле в желтом окне для ввода строки поиска по справке), но может быть кто-нибудь из коллег, кто имееет выход на SoftVelocity проинформирует их, что не у всех один ряд букв на клавиатуре ? ;) Ведь проблема не только с русским языком ...

С уважением, ТАТА

Добавлено: 26 Октябрь 2004, 18:43
Aragorn
Граждане, а ведь и правда местами не работает... Потихоньку шаблон превращается... превращается... в элегантные шорты... тьфу, в шаблон... entrypoint... нда... дал маху... не протестировал подробно... дааааааа....:(:(:(:(:(
Написал: Aragorn(147)

Добавлено: 26 Октябрь 2004, 19:38
Гость
Aragorn
Вообще-то увернность в том, что сообытия LoseFocus/GainFocus, Suspend/Resume, Selected/Accepted - штука очень вредная. Часто имеем ситуации, когда обработчк ACCEPT-цикла не генерирует какое-то событие из пары - при условии что событие по логике должно быть. И когда ты по таким событиям вешаешь-снимаешь оконные обработчки - это 100% гарантия того, что рано или поздно прога банально и пошло повиснет.

Добавлено: 26 Октябрь 2004, 20:44
Гость
Aragorn
уточнение - нужно читать:
Вообще-то увернность в том, что сообытия LoseFocus/GainFocus, Suspend/Resume, Selected/Accepted парные - штука очень вредная.
Далее по тексту предидущей мессаги :)

Добавлено: 27 Октябрь 2004, 13:15
Aragorn
Дааа... пример: если Form из Browse вызвать сначала через RBM, нажать отмену, а потом Form вызвать по кнопке, и нажать отмену - каюк... Всё приложение закрывается... Причина - вот это самое парное событие не вызвалось...
Написал: Aragorn(147)