S
Serge
Послать сочетание клавматуры в другое приложение
Немного истории. Недавно мне понадобилось переписать программы для переводов в WORD и Stylus3.1. на Word и Promt6. Это только в PROMTе считают, что этот кошмар, "профессиональная среда для переводов". В отличие от Stylus, где весь интерфейс расписан в одной библиотеке, в Promt6 все намного сложнее. Попросил на сайде Promt справку по библиотекам типов, но меня отфутболили, потребовав регистрацию. Пожав плечами - какая может быть регистрация на библиотеки типов, если я рекламирую их продукт?
В общем, не стал брать библиотеки сразу в лоб, тем более не горело. Полазив по интернету обратил внимание на то, что много есть вопросов на то, что или не получается послать сообщение в другое приложение, или посылают накрутив гору кода, и не одного не нашел, как послать сообщение правильно в другое приложение "горячих клавиш", и оно обработалось.
В предшествии у меня был, небольшой опыт в этом вопросе, в основном, что-то разблокировать в инстоляции, ну, в общем, по мелочам.
Не открою никому Америку, что работа Windows базируется на сообщения между окнами и системой. Существует основной класс окна приложения и может быть подклассы окон приложения, и взаимодействуют они между собой с помощью сообщений, как стандартных, так и пользовательских.
А вот теперь, в интернете, я только вскользь, находил, а какими сообщениями они пользуются между собой? И почему - то все скопом мило умалчивают об этом.
Взяв два шпиона ws32.exe и spy.exe, немного поэкспериментировал и решил все свои проблемы. Хочу сразу заметить, что нижеприведенный код на искусство ни в коем мере не претендует - попросту это топор - веревка - палка, но все это работает и работает надежно.
Обратил внимание, на многих форумах народ жалуется, что посылают сообщения в приложение, а оно не обрабатывает его. Скорее всего, пытаются посылать сообщение в самое верхнее окно, но не факт, что оно пропустит сообщение на обработку, если оно предназначено для дочернего окна. Поэтому, чтобы подобные вещи делать необходимо, знать досконально Z порядок окон. И чтобы правильно послать сообщение необходимо поэкспериментировать со шпионами.
Суть, заключается в том, послав сообщение активному окну, т.е. нажав сочетание клавиш, в активном окне приложения. Окно определяет, что есть нажатие "горячих клавиш" и посылает в верхнее окно (необязательно в Z порядке) WM_COMMAND или WM_SYSCOMMAND, в основном, со стандартными сообщениями Windows, в качестве параметров. А вот после этого оконная процедура, как правило, она одна на приложение, реагирует на это сообщение.
Просто послать "сочетание" в другое приложение с помощью SendMessage или Post Message, вам не удаться никогда, если, только извернуться так, что удаться сымитировать работу драйвера клавиатуры. Но думаю, что не стоит ломать стену, если рядом не дыра, а "Бранденбурские ворота, " в которые вы можете въехать и измываться над любым приложением насколько у вас хватит фантазии.
Ниже привожу рабочий код из моих программ, переработка пользовательских словарей Stylus под промт, написана в черновом варианте, мне нужно было переделать один словарь, и написал ее под консоль, чтобы не тратить время, но суть там присутствует.
Идея в следующем:
1. Переделать специализированный словарь от Stylus - в пользовательский, а затем подключить его к PROMTу
2. Созданный вновь словарь надо назвать "InfoStylus", в комментариях старался поподробней описать, на тот случай если кому - то понадобиться сделать тоже самое.
3. В командной строке запишите количество статей, которое необходимо переработать в пользовательский словарь. На практике 2000 статей обрабатывает нормально.
4. Написана на Builder6
Сергей Финкевич.
Немного истории. Недавно мне понадобилось переписать программы для переводов в WORD и Stylus3.1. на Word и Promt6. Это только в PROMTе считают, что этот кошмар, "профессиональная среда для переводов". В отличие от Stylus, где весь интерфейс расписан в одной библиотеке, в Promt6 все намного сложнее. Попросил на сайде Promt справку по библиотекам типов, но меня отфутболили, потребовав регистрацию. Пожав плечами - какая может быть регистрация на библиотеки типов, если я рекламирую их продукт?
В общем, не стал брать библиотеки сразу в лоб, тем более не горело. Полазив по интернету обратил внимание на то, что много есть вопросов на то, что или не получается послать сообщение в другое приложение, или посылают накрутив гору кода, и не одного не нашел, как послать сообщение правильно в другое приложение "горячих клавиш", и оно обработалось.
В предшествии у меня был, небольшой опыт в этом вопросе, в основном, что-то разблокировать в инстоляции, ну, в общем, по мелочам.
Не открою никому Америку, что работа Windows базируется на сообщения между окнами и системой. Существует основной класс окна приложения и может быть подклассы окон приложения, и взаимодействуют они между собой с помощью сообщений, как стандартных, так и пользовательских.
А вот теперь, в интернете, я только вскользь, находил, а какими сообщениями они пользуются между собой? И почему - то все скопом мило умалчивают об этом.
Взяв два шпиона ws32.exe и spy.exe, немного поэкспериментировал и решил все свои проблемы. Хочу сразу заметить, что нижеприведенный код на искусство ни в коем мере не претендует - попросту это топор - веревка - палка, но все это работает и работает надежно.
Обратил внимание, на многих форумах народ жалуется, что посылают сообщения в приложение, а оно не обрабатывает его. Скорее всего, пытаются посылать сообщение в самое верхнее окно, но не факт, что оно пропустит сообщение на обработку, если оно предназначено для дочернего окна. Поэтому, чтобы подобные вещи делать необходимо, знать досконально Z порядок окон. И чтобы правильно послать сообщение необходимо поэкспериментировать со шпионами.
Суть, заключается в том, послав сообщение активному окну, т.е. нажав сочетание клавиш, в активном окне приложения. Окно определяет, что есть нажатие "горячих клавиш" и посылает в верхнее окно (необязательно в Z порядке) WM_COMMAND или WM_SYSCOMMAND, в основном, со стандартными сообщениями Windows, в качестве параметров. А вот после этого оконная процедура, как правило, она одна на приложение, реагирует на это сообщение.
Просто послать "сочетание" в другое приложение с помощью SendMessage или Post Message, вам не удаться никогда, если, только извернуться так, что удаться сымитировать работу драйвера клавиатуры. Но думаю, что не стоит ломать стену, если рядом не дыра, а "Бранденбурские ворота, " в которые вы можете въехать и измываться над любым приложением насколько у вас хватит фантазии.
Ниже привожу рабочий код из моих программ, переработка пользовательских словарей Stylus под промт, написана в черновом варианте, мне нужно было переделать один словарь, и написал ее под консоль, чтобы не тратить время, но суть там присутствует.
Идея в следующем:
1. Переделать специализированный словарь от Stylus - в пользовательский, а затем подключить его к PROMTу
2. Созданный вновь словарь надо назвать "InfoStylus", в комментариях старался поподробней описать, на тот случай если кому - то понадобиться сделать тоже самое.
3. В командной строке запишите количество статей, которое необходимо переработать в пользовательский словарь. На практике 2000 статей обрабатывает нормально.
4. Написана на Builder6
Код:
#include <vcl.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
//дескриптор исходного словаря
HWND hWnd0 = NULL;
// дескриптор словаря слива статей
HWND hWnd1 = NULL;
// верхнее окно hWnd0
HWND hWnd01 = NULL;
// верхнее окно hWnd1
HWND hWnd11 = NULL;
//главное окно
HWND PAPA = NULL;
char dcv[80];
int i;
bool t6;
bool CALLBACK EnumWindowsProc(HWND, LPARAM);
bool CALLBACK EnumWinChild (HWND, LPARAM);
int main(int argc, char* argv[])
{ //определение PAPA
EnumWindows((WNDENUMPROC)EnumWindowsProc, 0);
if (t6 == 1)
{
//да, закройте все окна в Stylus, программа особенно не тестировалась, а нижняя функция нужна на случай, если забыли это сделать
EnumChildWindows(GetTopWindow(GetTopWindow(PAPA)), (WNDENUMPROC)EnumWinChild, 0);
//Определение дескрипторов словарей
EnumChildWindows(GetNextWindow(GetTopWindow(GetTopWindow(PAPA)), GW_HWNDNEXT), (WNDENUMPROC)EnumWinChild, 0);
//определение дескриптора старшего окна исходного словаря
hWnd01 = GetParent(GetParent(hWnd0));
//определение дескриптора старшего окна слива словаря
hWnd11 = GetParent(GetParent(hWnd1));
//записать в командную строку количество статей, которые хотите скопировать
//практически перегонял словарь, то выявились некоторые заморочки:
//пользовательский словарь должен быть < 300 kb,
// сразу ставить не более 2000 статей, иначе у Stylus съезжает крыша
for (i == 0; i < atoi(argv[1]); i++)
{
// переход в окно словаря слива
PostMessage (hWnd11, WM_SYSCOMMAND, SC_NEXTWINDOW, 0);
/*задержка, все-таки PostMessage функция неуправляемая, а динамический приоритет у этой программы на данный момент самый высокий из выполняющихся программ, приходится делать задержку или менять приоритет, что труднее. Цифры взяты с потолка, они не факт*/
Sleep(10);
//в определениях можно найти определение e122, что-то WM_COPY, легче так вставить, не проверял
PostMessage(PAPA, WM_COMMAND, 0x1e122, 0);
Sleep(10);
// постановка сообщения в окно "Стрелка вниз"
//кстати послать нажатие клавиши можно, но не получиться просто так послать сочетание
//чем вызвано не знаю, скорее всего, какими-то временными интервалами в оконной процедуре
// если кто может подсказать напишите "finkeviche@yandex.ru"
PostMessage (hWnd0, WM_KEYDOWN, 0x28, 0);
PostMessage (hWnd0, WM_KEYUP, 0x28, 0);
Sleep(10);
// переход в словарь слива (помните, что все остальные окна должны быть закрыты)
//словарь должен быть назван "InfoStylus.udc"(потом можно его переименовать и вообще, что хотите, то и сделать и конвертировать его в PROMT6)
//и имя его должно тоже быть "InfoStylus", иначе не определится дескриптор
PostMessage (hWnd01, WM_SYSCOMMAND, SC_NEXTWINDOW, 0);
Sleep(10);
// вставить из буфера обмена статью в словарь слива
//в определениях можно найти определение "e125", что-то вроде ID_EDIT_PASTE,
//а 1 в старшем слове определяет, что используется клавиатура
//ну и все такое, кому интересно
PostMessage(PAPA, WM_COMMAND, 0x1e125, 0);
Sleep(100);
}
}
return 1;
}
//---------------------------------------------------------------------------
bool CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
GetClassName(hWnd, dcv, 60);
int ptr;
ptr = strcmp(dcv, "TStylusWindow");
if (ptr == 0)
{
PAPA = hWnd;
t6 = true;
return false;
}
t6 = false;
return true;
};
bool CALLBACK EnumWinChild (HWND hWnd, LPARAM lp)
{
int ptr;
GetClassName(hWnd, dcv, 60);
ptr = strcmp(dcv, "SysListView32");
GetWindowText(hWnd, dcv, 70);
if (ptr == 0 && strcmp(dcv, "List1") == 0)
{
GetWindowText(GetParent(GetParent(hWnd)), dcv, 80);
if (strncmpi(dcv, "InfoStylus:", 11) == 0)
{
hWnd1 = hWnd;
}
else
{
hWnd0 = hWnd;
}
}
return true;
}
//=============================================
А ниже приведен код на VBA, чтобы заставить PROMT6 переводить в Wordе. Необходимость этого в том, что если задействовать стандартные средства переводчика и предоставленный интерфейс в редакторе(Word), не получатся слитное выполнение общей программы, т.к. в памяти от переводчика болтается сервисная программа и ей фиолетово, что делается в редакторе, и связки не получатся. Пробовал отлавливать потоки на количество, но в сервисной программе, кажется, идет подсчет ссылок и отказывается переводить вообще. В сервисном модуле постоянно присутствует 4 потока, и она отшивает все, что не зарегистрировано PROMTом.
Программа переводит выделенный текст и возвращает перевод из переводчика и оставляет его выделенным
'StylusMacros
'объявления на уровне модуля
Dim Doc As Object
Dim Promt As Object
Dim rfgt55 As Integer
Option Explicit
'так удобно передавать в другие модули
'параметры мои и особенно не обращайте внимание на них
Sub StylusFlaish_t(SrtingProgramm$, yj As Long, dc As String)
Select Case SrtingProgramm$
Case "TransSelection"
TransSelection (yj)
Case "TransDocument"
TransDocument
Case "PromViz"
PromViz
Case "PromNoViz"
PromNoViz
End Select
Private Sub TransSelection(yj As Long)
Dim myTask As Task
Dim Ran As Object
If Doc Is Nothing Then
'параметра можно откапать из реестра
Set Promt = CreateObject("Promt.Application")
' делать PROMT видимым, если 0, то скрытым
Promt.Visible = 1
Set Doc = Promt.PromtDocuments.Add(1, "PROMT.Document")
Else
'переход в окно английского текста на всякий случай
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
'H111 код по определению WM_COMMAND, а 8007 переход в окно исходного текста
'ну, 1 в старшем слове, я уже объяснял
myTask.SendWindowMessage &H111, &H18007, 0
Exit For
End If
Next myTask
'см. справку по библиотекам типов (описание пользование макросами), которая поступает с PROMT
'разработчики все - таки разжалобились
Doc.Text = ""
End If
Set Ran = Selection.Range
Ran.Copy
Doc.Paste
'вообще-то из-за этой функции и пришлось использовать COM интерфейс переводчика
Doc.Translate
'переход в окно перевода
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H18008, 0
End If
Next myTask
'все выделить
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H1E12A, 0
End If
Next myTask
'скопировать в буфер
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H1E122, 0
Exit For
End If
Next myTask
Ran.Paste
Ran.Select
'на случай если необходимо сохранить исходный текст, yj =1
'то переход в окно исходного текста
If yj = 1 Then
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H18007, 0
Exit For
End If
Next myTask
'все выделить
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H1E12A, 0
End If
Next myTask
'скопировать в буфер обмена
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H1E122, 0
Exit For
End If
Next myTask
End If
'PROMT накапливает буфер обмена, вот его и очищаем
'может и сам очищает, но не проверял
rfgt55 = rfgt55 + 1
If rfgt55 > 5 Then
For Each myTask In Tasks
If InStr(myTask.Name, "PROMT") > 0 Then
myTask.SendWindowMessage &H111, &H8513, 0
Exit For
End If
Next myTask
rfgt55 = 0
End If
Set Ran = Nothing
End Sub
' если запустили PROMT в скрытом виде, то так можно его увидеть
Private Sub PromViz()
Promt.Visible = 1
End Sub
'а так скрыть, но не советую
Private Sub PromNoViz()
Promt.Visible = 0
End Sub
//======================================================
/*Ниже приведен реализ выше написанного кода, но в C++
Все - таки Word дремуче работает с памятью и лишний раз дергать его за
усы не стоит.
Могут быть накладки, в том смысле, что среди 30 файлов
найти все объявления и описания, мог и пропустить, но кому надо, тот поймет*/
RETPromt.cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#include <Vcl\utilcls>
#pragma hdrstop
#include "SekFill.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
extern "C" __declspec(dllexport) void __stdcall ManipPROMT()
{
try{
if (VarIsEmpty(Stek->RetOLE()))
{
Stek->SetOLE1();
Stek->SetOLE();
EnumWindows((WNDENUMPROC)EnumWindowsProcA, 0);
}
else
{
//в отличие от ДОС выриатиа можно использовать SendMessage, т.к. сообщения поступаю все в верхнее окно
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x18007, 0);
Stek->RetOLE().OlePropertySet("Text", "");
}
Stek->RetOLE().OleProcedure("Paste");
Stek->RetOLE().OleProcedure("Translate");
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x18008, 0);
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x1E12A, 0);
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x1E122, 0);
Stek->y = Stek->y + 1;
if (Stek->y > 4)
{
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x8513, 0);
Stek->y = 0;
}
}
catch(const Exception &E)
{
ShowMessage(AnsiString(E.ClassName())+ E.Message);
}
return;
}
//функция на тот случай если надо сохранить исходный текст в буфере
extern "C" __declspec(dllexport) void __stdcall REtClipboard()
{
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x18007, 0);
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x1E12A, 0);
SendMessage(Stek->REtHWND(), (UINT)0x111, (WPARAM)0x1E122, 0);
}
//------------------------------------------------------------------------------------------------
SekFill.h
//---------------------------------------------------------------------------
#ifndef SekFillH
#define SekFillH
#include <vcl.h>
class TsekFill
{
private:
//закрытые т.к. стоит CpuIdlePro для XP и часто класс открывает буфер и закрывает его
//переводчик иногда глючил
//объяснений нет, но сейчас перестал, а так наверно не обязательно закрывать
//вариант для документа PROMT
Variant V;
//вариант для самого приложения PROMT
Variant V1;
//хэндл для основного окна
HWND hwnd;
public:
//счетчик буфера переводчика
int y;
TSekFill();
//возвращает интерфейс документа PROMT
Variant RetOLE();
//назначает интерфейс документа на вариант
void SetOLE();
//назначает интерфейс приложения на вариант
void SetOLE1();
//возвращает хэндл главного окна
HWND REtHWND();
//назначает хэндл на основное окно
void SetHWHD(HWND&);
virtual ~TSekFill();
};
extern TSekFill* Stek;
//функция повторного вызова для определения главного окна
bool CALLBACK EnumWindowsProcA(HWND, LPARAM);
//---------------------------------------------------------------------------
#endif
//=================================================
SekFill.cpp
//#include <vcl.h>
#include <windows.h>
//#include <sysvari.h>
#include <Clipbrd.hpp>
#include <Vcl\utilcls>
#pragma hdrstop
#include "SekFill.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
TSekFill::TSekFill()
{
y = 0;
}
//---------------------------------------------------------------------
TSekFill::~TSekFill()
{
V = Unassigned;
V1 = Unassigned;
}
//---------------------------------------------------------------
Variant TSekFill::RetOLE()
{
return V;
}
void TSekFill::SetOLE()
{
V = V1.OlePropertyGet("PromtDocuments");
V1.OlePropertySet("Visible", true);
V = V.OleFunction("Add", 1, "PROMT.Document");
}
void TSekFill::SetOLE1()
//коряво, но это часть класса и он распределяется в динамической памяти,
//держит в памяти много параметров на время сессии Word и при любом сбое
// выгружается, записывая параметры и стек посещения документов в файл,
// а при загрузке восстанавливает все данные
{
if (VarIsEmpty(V1))
{
V1 = OleVariant::CreateObject("Promt.Application");
}
else
{
V1 = Unassigned;
V1 = OleVariant::CreateObject("Promt.Application");
}
}
HWND TSekFill::REtHWND()
{
return hwnd;
}
void TSekFill::SetHWHD(HWND& hWnd)
{
hwnd = hWnd;
}
bool CALLBACK EnumWindowsProcA(HWND hWnd, LPARAM lp)
{
char asd[60];
//проверяется имя класса окна, и название окна
GetClassName(hWnd, asd, 60);
int ptr = strncmpi(asd, "Afx:400000:", 11);
GetWindowText(hWnd, asd, 60);
int ptrz = strncmpi(asd, "PROMT - [", 9);
if (ptr == 0 && ptrz == 0)
{
Stek->SetHWHD(hWnd);
return false;
}
return true;
}
Сергей Финкевич.