1

Тема: AHK: Работа с двумя окнами

Здравствуйте!
Пытаюсь сделать скрипт. Задача - при нажатии сочетания клавиш "-25" скопировать число в Окне1 (окно браузера, число всегда находится на одном и том же месте), потом открыть Калькулятор (Окно2), вставить туда это число, вычесть из него 25 и показать результат в том же калькуляторе. Пробовала задать движения мышки по координатам с помощью MouseMove, Click и Click 2 - удалось только выделить число в Окне1 (не скопировать) и открыть Окно2. Дальше мышка начинает нажимать не на клавиши в Калькуляторе, а возвращается в окно браузера и там после этого почему-то вызывается DevTools. Что я делаю не так?..

2

Re: AHK: Работа с двумя окнами

Смотрите команду CoordMode.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

3 (изменено: marusa122, 2023-03-22 17:27:10)

Re: AHK: Работа с двумя окнами

teadrinker вообще перестал скрипт работать. Пробовала запускать Калькулятор так:

:*:-25:: ; при нажатии комбинации клавиш "-25" скрипт запускается автоматически
application :="calc.exe"
workingDir := "C:\\WINDOWS\\system32"
process_id = 0
window_title := "Калькулятор"          	
figure1 = Clipboard ; число, которое скрипт должен скопировать из уже открытого окна и вставить в открытый им калькулятор
figure2 = 25
operation := "-"

MouseMove 1266, 662
Click 2 ; выделяется число в открытом окне
tempClip := Clipboard
Send, ^{c}
copiedText := Clipboard

Run, %application%, %workingDir%, UseErrorLevel, process_id
Sleep, 5000 
Send, ^{v}

WinWaitActive, %window_title%,,2

ControlGetPos, X, Y,,, %figure1%
MouseClick, left, X, Y

Sleep, 5000 

ControlClick, %operation%, %window_title% 

ControlSend, , %figure2%, %window_title% 

Sleep, 5000 

SendEvent, = 

Sleep, 5000  

Process, Close, %process_id%

Калькулятор запускается, но выделенное ранее число в него почему-то не копируется. И еще вопрос - как добавить сюда дополнительную операцию с числом? Нужно разделить его на 100 перед тем, как вычитать 25.

4

Re: AHK: Работа с двумя окнами

marusa122, ознакомьтесь, пожалуйста, с Правилами, отредактируйте свой пост. Обратите внимание на точки в конце предложений.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

5

Re: AHK: Работа с двумя окнами

teadrinker, отредактировала.

6

Re: AHK: Работа с двумя окнами

marusa122 пишет:

workingDir := "C:\\WINDOWS\\system32"

В AHK в строках не нужно экранировать слеши.

marusa122 пишет:
figure1 = Clipboard

В AHK для операции присваивания лучше использовать знак := а не =. В данном случае вы вместо содержания буфера обмена переменной figure1 присваиваете буквальную строку "Clipboard".

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

7

Re: AHK: Работа с двумя окнами

teadrinker, отредактировала всё, что вы сказали. Теперь скрипт вставил в калькулятор число и свернул его, не выполнив с числом никаких операций.

8

Re: AHK: Работа с двумя окнами

marusa122 пишет:
ControlClick, %operation%, %window_title%

У вас название контрола, судя по значению переменной, "-". Имеете в виду знак минус? Но у кнопки нет такого заголовка. У вас какая ОС, Windows 7 ?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

9

Re: AHK: Работа с двумя окнами

teadrinker, Windows 10

10

Re: AHK: Работа с двумя окнами

А разве в Windows 10 процесс калькулятора называется calc.exe? Это в Windows 7 calc.exe. У меня на Windows 10 он CalculatorApp.exe. В любом случае ControlClick с кнопками калькулятора на Windows 10 работать не будет, AHK их не видит, как отдельные контролы. Чтобы узнать, можно ли использовать ControlClick, нужно смотреть в Window Spy в разделе «Control Under Mouse Position:», выделяется ли данный элемент управления, как отдельный контрол с точки зрения AHK.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

11 (изменено: Malcev, 2023-03-22 20:16:21)

Re: AHK: Работа с двумя окнами

teadrinker пишет:

А разве в Windows 10 процесс калькулятора называется calc.exe?

Зависит от версии.
У меня на win10 LTSC калькулятор - классическое приложение calc.exe.

12

Re: AHK: Работа с двумя окнами

teadrinker, Странно, у меня на calc.exe открылся Калькулятор. Посмотрела - не выделяется как отдельный контрол. И как тогда заставить скрипт работать в Windows 10?..

13

Re: AHK: Работа с двумя окнами

Malcev пишет:

Зависит от версии

Ну, в принципе не важно, у меня он тоже по calc.exe запускается.

marusa122 пишет:

И как тогда заставить скрипт работать в Windows 10?

Заставить работать можно, но тут будет посложнее. Смотрите на англоязычном форуме примеры с UIAutomation. Там есть библиотека, адаптированная под тех, кто не знает всех тонкостей, автор Descolada. Если для вас это окажется чересчур заморочно, отложите до тех времён, когда станете опытным пользователем, а пока начните с чего-то попроще. Можно применить другой подход — поставить в соответствие каждой кнопке координаты её центра и кликать по координатам.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

14

Re: AHK: Работа с двумя окнами

teadrinker, а я с этого "попроще" и начинала. Итог - в первом посте этой ветки. Вместо того, чтобы нажимать на кнопки в Калькуляторе, скрипт нажимал непонятно что в браузере и вызывал панель разработчика.

15

Re: AHK: Работа с двумя окнами

Так я в приведённом вами варианте так и не увидел использования CoordMode.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

16 (изменено: marusa122, 2023-03-22 22:36:04)

Re: AHK: Работа с двумя окнами

teadrinker, вот

:*:-25::
CoordMode, Mouse, Screen
MouseMove, 1266, 663 ; перемещается к числу в окне браузера
Click 2 ; выделяет число
tempClip := Clipboard
Send, ^{c}
copiedText := Clipboard
Clipboard := tempClip
Run calc.exe ; открывает Калькулятор
Sleep, 5000
Send, %copiedText% ; вставляет число
Click 320, 403 ; поделить
Send 100 ; на 100
Click 319, 615 ; равно
Click 320, 519 ; минус
Send 25 
Click 319, 615 ; равно
return

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

17

Re: AHK: Работа с двумя окнами

Точки верните на своё место, пожалуйста.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

18 (изменено: marusa122, 2023-03-22 23:12:18)

Re: AHK: Работа с двумя окнами

Вернула, извините.

19

Re: AHK: Работа с двумя окнами

Вы не очень внимательны, да? В программировании это нежелательное качество.

marusa122 пишет:

Вернула, извините

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

20

Re: AHK: Работа с двумя окнами

teadrinker, да какой из меня программист... Вернула точку.

21

Re: AHK: Работа с двумя окнами

marusa122 пишет:

На этапе "вставляет число" скрипт ничего не вставляет.

После Send, ^{c} (где фигурные скобки не нужны), выделенный текст не сразу попадает в буфер обмена, соответственно, у вас в переменной прошлое его значение. Нужно перед посылом Ctrl + C очистить буфер обмена, присвоив ему пустое значение, а после посыла ждать появления информации с помощью ClipWait.

marusa122 пишет:

а может ли AutoHotkey считать сам, без "костылей" в виде Калькулятора?

Сам не может, но есть различные библиотеки. Я помню, была такая Monster.ahk, которая умела парсить строку с математическим выражением и выдавать результат, гуглите.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

22

Re: AHK: Работа с двумя окнами

marusa122 пишет:

Вопрос: а может ли AutoHotkey считать сам, без "костылей" в виде Калькулятора?

Может. Составляйте формулы из переменных.


a:=3
b:=5
c:=a*(b-25)
msgbox,% "a=" a "`nb=" b "`nc=" c
Win 10 x64
AHK v1.1.33.02
                       Справка тебе в помощь.

23

Re: AHK: Работа с двумя окнами

А зачем формулы составлять?

js := new JsRT.Edge
MsgBox % js.Eval("3*(5-25)")


/*
 *  ActiveScript for AutoHotkey v1.1
 *
 *  Provides an interface to Active Scripting languages like VBScript and JScript,
 *  without relying on Microsoft's ScriptControl, which is not available to 64-bit
 *  programs.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class ActiveScript extends ActiveScript._base
{
    __New(Language)
    {
        if this._script := ComObjCreate(Language, ActiveScript.IID)
            this._scriptParse := ComObjQuery(this._script, ActiveScript.IID_Parse)
        if !this._scriptParse
            throw Exception("Invalid language", -1, Language)
        this._site := new ActiveScriptSite(this)
        this._SetScriptSite(this._site.ptr)
        this._InitNew()
        this._objects := {}
        this.Error := ""
        this._dsp := this._GetScriptDispatch()  ; Must be done last.
        try
            if this.ScriptEngine() = "JScript"
                this.SetJScript58()
    }

    SetJScript58()
    {
        static IID_IActiveScriptProperty := "{4954E0D0-FBC7-11D1-8410-006008C3FBFC}"
        if !prop := ComObjQuery(this._script, IID_IActiveScriptProperty)
            return false
        VarSetCapacity(var, 24, 0), NumPut(2, NumPut(3, var, "short") + 6)
        hr := DllCall(NumGet(NumGet(prop+0)+4*A_PtrSize), "ptr", prop, "uint", 0x4000
            , "ptr", 0, "ptr", &var), ObjRelease(prop)
        return hr >= 0
    }
    
    Eval(Code)
    {
        pvar := NumGet(ComObjValue(arr:=ComObjArray(0xC,1)) + 8+A_PtrSize)
        this._ParseScriptText(Code, 0x20, pvar)  ; SCRIPTTEXT_ISEXPRESSION := 0x20
        return arr[0]
    }
    
    Exec(Code)
    {
        this._ParseScriptText(Code, 0x42, 0)  ; SCRIPTTEXT_ISVISIBLE := 2, SCRIPTTEXT_ISPERSISTENT := 0x40
        this._SetScriptState(2)  ; SCRIPTSTATE_CONNECTED := 2
    }
    
    AddObject(Name, DispObj, AddMembers := false)
    {
        static a, supports_dispatch ; Test for built-in IDispatch support.
            := a := ((a:=ComObjArray(0xC,1))[0]:=[42]) && a[0][1]=42
        if IsObject(DispObj) && !(supports_dispatch || ComObjType(DispObj))
            throw Exception("Adding a non-COM object requires AutoHotkey v1.1.17+", -1)
        this._objects[Name] := DispObj
        this._AddNamedItem(Name, AddMembers ? 8 : 2)  ; SCRIPTITEM_ISVISIBLE := 2, SCRIPTITEM_GLOBALMEMBERS := 8
    }
    
    _GetObjectUnk(Name)
    {
        return !IsObject(dsp := this._objects[Name]) ? dsp  ; Pointer
            : ComObjValue(dsp) ? ComObjValue(dsp)  ; ComObject
            : &dsp  ; AutoHotkey object
    }
    
    class _base
    {
        __Call(Method, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Method](Params*)
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Get(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Property, Params*]
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Set(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
            {
                Value := Params.Pop()
                try
                    return (this._dsp)[Property, Params*] := Value
                catch e
                    throw Exception(e.Message, -1, e.Extra)
            }
        }
    }
    
    _SetScriptSite(Site)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+3*A_PtrSize), "ptr", p, "ptr", Site)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptSite")
    }
    
    _SetScriptState(State)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+5*A_PtrSize), "ptr", p, "int", State)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptState")
    }
    
    _AddNamedItem(Name, Flags)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+8*A_PtrSize), "ptr", p, "wstr", Name, "uint", Flags)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::AddNamedItem")
    }
    
    _GetScriptDispatch()
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+10*A_PtrSize), "ptr", p, "ptr", 0, "ptr*", pdsp)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::GetScriptDispatch")
        return ComObject(9, pdsp, 1)
    }
    
    _InitNew()
    {
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+3*A_PtrSize), "ptr", p)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::InitNew")
    }
    
    _ParseScriptText(Code, Flags, pvarResult)
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+5*A_PtrSize), "ptr", p
            , "wstr", Code, "ptr", 0, "ptr", 0, "ptr", 0, "uptr", 0, "uint", 1
            , "uint", Flags, "ptr", pvarResult, "ptr", 0)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::ParseScriptText")
    }
    
    _HRFail(hr, what)
    {
        if e := this.Error
        {
            this.Error := ""
            throw Exception("`nError code:`t" this._HRFormat(e.HRESULT)
                . "`nSource:`t`t" e.Source "`nDescription:`t" e.Description
                . "`nLine:`t`t" e.Line "`nColumn:`t`t" e.Column
                . "`nLine text:`t`t" e.LineText, -3)
        }
        throw Exception(what " failed with code " this._HRFormat(hr), -2)
    }
    
    _HRFormat(hr)
    {
        return Format("0x{1:X}", hr & 0xFFFFFFFF)
    }
    
    _OnScriptError(err) ; IActiveScriptError err
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        DllCall(NumGet(NumGet(err+0)+3*A_PtrSize), "ptr", err, "ptr", &excp) ; GetExceptionInfo
        DllCall(NumGet(NumGet(err+0)+4*A_PtrSize), "ptr", err, "uint*", srcctx, "uint*", srcline, "int*", srccol) ; GetSourcePosition
        DllCall(NumGet(NumGet(err+0)+5*A_PtrSize), "ptr", err, "ptr*", pbstrcode) ; GetSourceLineText
        code := StrGet(pbstrcode, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstrcode)
        if fn := NumGet(excp, 6 * A_PtrSize) ; pfnDeferredFillIn
            DllCall(fn, "ptr", &excp)
        wcode := NumGet(excp, 0, "ushort")
        hr := wcode ? 0x80040200 + wcode : NumGet(excp, 7 * A_PtrSize, "uint")
        this.Error := {HRESULT: hr, Line: srcline, Column: srccol, LineText: code}
        static Infos := "Source,Description,HelpFile"
        Loop Parse, % Infos, `,
            if pbstr := NumGet(excp, A_Index * A_PtrSize)
                this.Error[A_LoopField] := StrGet(pbstr, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstr)
        return 0x80004001 ; E_NOTIMPL (let Exec/Eval get a fail result)
    }
    
    __Delete()
    {
        if this._script
        {
            DllCall(NumGet(NumGet((p:=this._script)+0)+7*A_PtrSize), "ptr", p)  ; Close
            ObjRelease(this._script)
        }
        if this._scriptParse
            ObjRelease(this._scriptParse)
    }
    
    static IID := "{BB1A2AE1-A4F9-11cf-8F20-00805F2CD064}"
    static IID_Parse := A_PtrSize=8 ? "{C7EF7658-E1EE-480E-97EA-D52CB4D76D17}" : "{BB1A2AE2-A4F9-11cf-8F20-00805F2CD064}"
}

class ActiveScriptSite
{
    __New(Script)
    {
        ObjSetCapacity(this, "_site", 3 * A_PtrSize)
        NumPut(&Script
        , NumPut(ActiveScriptSite._vftable("_vft_w", "31122", 0x100)
        , NumPut(ActiveScriptSite._vftable("_vft", "31125232211", 0)
            , this.ptr := ObjGetAddress(this, "_site"))))
    }
    
    _vftable(Name, PrmCounts, EIBase)
    {
        if p := ObjGetAddress(this, Name)
            return p
        ObjSetCapacity(this, Name, StrLen(PrmCounts) * A_PtrSize)
        p := ObjGetAddress(this, Name)
        Loop Parse, % PrmCounts
        {
            cb := RegisterCallback("_ActiveScriptSite", "F", A_LoopField, A_Index + EIBase)
            NumPut(cb, p + (A_Index-1) * A_PtrSize)
        }
        return p
    }
}

_ActiveScriptSite(this, a1:=0, a2:=0, a3:=0, a4:=0, a5:=0)
{
    Method := A_EventInfo & 0xFF
    if A_EventInfo >= 0x100  ; IActiveScriptSiteWindow
    {
        if Method = 4  ; GetWindow
        {
            NumPut(0, a1+0) ; *phwnd := 0
            return 0 ; S_OK
        }
        if Method = 5  ; EnableModeless
        {
            return 0 ; S_OK
        }
        this -= A_PtrSize     ; Cast to IActiveScriptSite
    }
    ;else: IActiveScriptSite
    if Method = 1  ; QueryInterface
    {
        iid := _AS_GUIDToString(a1)
        if (iid = "{00000000-0000-0000-C000-000000000046}"  ; IUnknown
         || iid = "{DB01A1E3-A42B-11cf-8F20-00805F2CD064}") ; IActiveScriptSite
        {
            NumPut(this, a2+0)
            return 0 ; S_OK
        }
        if (iid = "{D10F6761-83E9-11cf-8F20-00805F2CD064}") ; IActiveScriptSiteWindow
        {
            NumPut(this + A_PtrSize, a2+0)
            return 0 ; S_OK
        }
        NumPut(0, a2+0)
        return 0x80004002 ; E_NOINTERFACE
    }
    if Method = 5  ; GetItemInfo
    {
        a1 := StrGet(a1, "UTF-16")
        , (a3 && NumPut(0, a3+0))  ; *ppiunkItem := NULL
        , (a4 && NumPut(0, a4+0))  ; *ppti := NULL
        if (a2 & 1) ; SCRIPTINFO_IUNKNOWN
        {
            if !(unk := Object(NumGet(this + A_PtrSize*2))._GetObjectUnk(a1))
                return 0x8002802B ; TYPE_E_ELEMENTNOTFOUND
            ObjAddRef(unk), NumPut(unk, a3+0)
        }
        return 0 ; S_OK
    }
    if Method = 9  ; OnScriptError
        return Object(NumGet(this + A_PtrSize*2))._OnScriptError(a1)
    
    ; AddRef and Release don't do anything because we want to avoid circular references.
    ; The site and IActiveScript are both released when the AHK script releases its last
    ; reference to the ActiveScript object.
    
    ; All of the other methods don't require implementations.
    return 0x80004001 ; E_NOTIMPL
}

_AS_GUIDToString(pGUID)
{
    VarSetCapacity(String, 38*2)
    DllCall("ole32\StringFromGUID2", "ptr", pGUID, "str", String, "int", 39)
    return String
}


/*
 *  JsRT for AutoHotkey v1.1
 *
 *  Utilizes the JavaScript engine that comes with IE11.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class JsRT extends ActiveScript._base
{
    __New()
    {
        throw Exception("This class is abstract. Use JsRT.IE or JSRT.Edge instead.", -1)
    }
    
    class IE extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "jscript9", "ptr")
                throw Exception("Failed to load jscript9.dll", -1)
            if DllCall("jscript9\JsCreateRuntime", "int", 0, "int", -1
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("jscript9\JsCreateContext", "ptr", runtime, "ptr", 0, "ptr*", context)
            this._Initialize("jscript9", runtime, context)
        }
    }
    
    class Edge extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "chakra", "ptr")
                throw Exception("Failed to load chakra.dll", -1)
            if DllCall("chakra\JsCreateRuntime", "int", 0
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("chakra\JsCreateContext", "ptr", runtime, "ptr*", context)
            this._Initialize("chakra", runtime, context)
        }
        
        ProjectWinRTNamespace(namespace)
        {
            return DllCall("chakra\JsProjectWinRTNamespace", "wstr", namespace)
        }
    }
    
    _Initialize(dll, runtime, context)
    {
        this._dll := dll
        this._runtime := runtime
        this._context := context
        DllCall(dll "\JsSetCurrentContext", "ptr", context)
        DllCall(dll "\JsGetGlobalObject", "ptr*", globalObject)
        this._dsp := this._JsToVt(globalObject)
    }
    
    __Delete()
    {
        this._dsp := ""
        if dll := this._dll
        {
            DllCall(dll "\JsSetCurrentContext", "ptr", 0)
            DllCall(dll "\JsDisposeRuntime", "ptr", this._runtime)
        }
        DllCall("FreeLibrary", "ptr", this._hmod)
    }
    
    _JsToVt(valref)
    {
        VarSetCapacity(variant, 24, 0)
        DllCall(this._dll "\JsValueToVariant", "ptr", valref, "ptr", &variant)
        ref := ComObject(0x400C, &variant), val := ref[], ref[] := 0
        return val
    }
    
    _ToJs(val)
    {
        VarSetCapacity(variant, 24, 0)
        ref := ComObject(0x400C, &variant) ; VT_BYREF|VT_VARIANT
        ref[] := val
        DllCall(this._dll "\JsVariantToValue", "ptr", &variant, "ptr*", valref)
        ref[] := 0
        return valref
    }
    
    _JsEval(code)
    {
        e := DllCall(this._dll "\JsRunScript", "wstr", code, "uptr", 0, "wstr", "source.js"
            , "ptr*", result)
        if e
        {
            if DllCall(this._dll "\JsGetAndClearException", "ptr*", excp) = 0
                throw this._JsToVt(excp)
            throw Exception("JsRT error", -2, format("0x{:X}", e))
        }
        return result
    }
    
    Exec(code)
    {
        this._JsEval(code)
    }
    
    Eval(code)
    {
        return this._JsToVt(this._JsEval(code))
    }
    
    AddObject(name, obj, addMembers := false)
    {
        if addMembers
            throw Exception("AddMembers=true is not supported", -1)
        this._dsp[name] := obj
    }
}

24

Re: AHK: Работа с двумя окнами

MsgBox, % Calc("3*(5-25)")

Calc(expr) {
   try res := GetJS().eval(expr)
   catch {
      MsgBox, Incorrect expression
      Return
   }
   Return res
}

GetJS() {
   static Doc, JS
   if !Doc {
      Doc := ComObjCreate("htmlfile")
      Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := Doc.parentWindow
      ( Doc.documentMode < 9 && JS.execScript() )
   }
   Return JS
}

Так не проще?
Но тот Monster.ahk работал без создания дополнительных объектов, просто парсингом.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

25

Re: AHK: Работа с двумя окнами

Не, не проще:

MsgBox, % Calc("3**3")

Calc(expr) {
   try res := GetJS().eval(expr)
   catch {
      MsgBox, Incorrect expression
      Return
   }
   Return res
}

GetJS() {
   static Doc, JS
   if !Doc {
      Doc := ComObjCreate("htmlfile")
      Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := Doc.parentWindow
      ( Doc.documentMode < 9 && JS.execScript() )
   }
   Return JS
}

26

Re: AHK: Работа с двумя окнами

А в реальности часто в степени приходится возводить? Там через Math.pow().
Вот monster.ahk:

MsgBox % Eval("3**3")

Eval(x) {                              ; non-recursive PRE/POST PROCESSING: I/O forms, numbers, ops, ";"
   Local FORM, FormF, FormI, i, W, y, y1, y2, y3, y4
   FormI := A_FormatInteger, FormF := A_FormatFloat

   SetFormat Integer, D                ; decimal intermediate results!
   RegExMatch(x, "\$(b|h|x|)(\d*[eEgG]?)", y)
   FORM := y1, W := y2                 ; HeX, Bin, .{digits} output format
   SetFormat FLOAT, 0.16e              ; Full intermediate float precision
   StringReplace x, x, %y%             ; remove $..
   Loop
      If RegExMatch(x, "i)(.*)(0x[a-f\d]*)(.*)", y)
         x := y1 . y2+0 . y3           ; convert hex numbers to decimal
      Else Break
   Loop
      If RegExMatch(x, "(.*)'([01]*)(.*)", y)
         x := y1 . FromBin(y2) . y3    ; convert binary numbers to decimal: sign = first bit
      Else Break
   x := RegExReplace(x,"(^|[^.\d])(\d+)(e|E)","$1$2.$3") ; add missing '.' before E (1e3 -> 1.e3)
                                       ; literal scientific numbers between ‘ and ’ chars
   x := RegExReplace(x,"(\d*\.\d*|\d)([eE][+-]?\d+)","‘$1$2’")

   StringReplace x, x,`%, \, All       ; %  -> \ (= MOD)
   StringReplace x, x, **,@, All       ; ** -> @ for easier process
   StringReplace x, x, +, ±, All       ; ± is addition
   x := RegExReplace(x,"(‘[^’]*)±","$1+") ; ...not inside literal numbers
   StringReplace x, x, -, ¬, All       ; ¬ is subtraction
   x := RegExReplace(x,"(‘[^’]*)¬","$1-") ; ...not inside literal numbers

   Loop Parse, x, `;
      y := Eval1(A_LoopField)          ; work on pre-processed sub expressions
                                       ; return result of last sub-expression (numeric)
   If FORM = b                         ; convert output to binary
      y := W ? ToBinW(Round(y),W) : ToBin(Round(y))
   Else If (FORM="h" or FORM="x") {
      SetFormat Integer, Hex           ; convert output to hex
      y := Round(y) + 0
   }
   Else {
      W := W="" ? "0.6g" : "0." . W    ; Set output form, Default = 6 decimal places
      SetFormat FLOAT, %W%
      y += 0.0
   }
   SetFormat Integer, %FormI%          ; restore original formats
   SetFormat FLOAT,   %FormF%
   Return y
}

Eval1(x) {                             ; recursive PREPROCESSING of :=, vars, (..) [decimal, no ";"]
   Local i, y, y1, y2, y3
                                       ; save function definition: f(x) := expr
   If RegExMatch(x, "(\S*?)\((.*?)\)\s*:=\s*(.*)", y) {
      f%y1%__X := y2, f%y1%__F := y3
      Return
   }
                                       ; execute leftmost ":=" operator of a := b := ...
   If RegExMatch(x, "(\S*?)\s*:=\s*(.*)", y) {
      y := "x" . y1                    ; user vars internally start with x to avoid name conflicts
      Return %y% := Eval1(y2)
   }
                                       ; here: no variable to the left of last ":="
   x := RegExReplace(x,"([\)’.\w]\s+|[\)’])([a-z_A-Z]+)","$1«$2»")  ; op -> «op»

   x := RegExReplace(x,"\s+")          ; remove spaces, tabs, newlines

   x := RegExReplace(x,"([a-z_A-Z]\w*)\(","'$1'(") ; func( -> 'func'( to avoid atan|tan conflicts

   x := RegExReplace(x,"([a-z_A-Z]\w*)([^\w'»’]|$)","%x$1%$2") ; VAR -> %xVAR%
   x := RegExReplace(x,"(‘[^’]*)%x[eE]%","$1e") ; in numbers %xe% -> e
   x := RegExReplace(x,"‘|’")          ; no more need for number markers
   Transform x, Deref, %x%             ; dereference all right-hand-side %var%-s

   Loop {                              ; find last innermost (..)
      If RegExMatch(x, "(.*)\(([^\(\)]*)\)(.*)", y)
         x := y1 . Eval@(y2) . y3      ; replace (x) with value of x
      Else Break
   }
   Return Eval@(x)
}

Eval@(x) {                             ; EVALUATE PRE-PROCESSED EXPRESSIONS [decimal, NO space, vars, (..), ";", ":="]
   Local i, y, y1, y2, y3, y4

   If x is number                      ; no more operators left
      Return x
                                       ; execute rightmost ?,: operator
   RegExMatch(x, "(.*)(\?|:)(.*)", y)
   IfEqual y2,?,  Return Eval@(y1) ? Eval@(y3) : ""
   IfEqual y2,:,  Return ((y := Eval@(y1)) = "" ? Eval@(y3) : y)

   StringGetPos i, x, ||, R            ; execute rightmost || operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) || Eval@(SubStr(x,3+i))
   StringGetPos i, x, &&, R            ; execute rightmost && operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) && Eval@(SubStr(x,3+i))
                                       ; execute rightmost =, <> operator
   RegExMatch(x, "(.*)(?<![\<\>])(\<\>|=)(.*)", y)
   IfEqual y2,=,  Return Eval@(y1) =  Eval@(y3)
   IfEqual y2,<>, Return Eval@(y1) <> Eval@(y3)
                                       ; execute rightmost <,>,<=,>= operator
   RegExMatch(x, "(.*)(?<![\<\>])(\<=?|\>=?)(?![\<\>])(.*)", y)
   IfEqual y2,<,  Return Eval@(y1) <  Eval@(y3)
   IfEqual y2,>,  Return Eval@(y1) >  Eval@(y3)
   IfEqual y2,<=, Return Eval@(y1) <= Eval@(y3)
   IfEqual y2,>=, Return Eval@(y1) >= Eval@(y3)
                                       ; execute rightmost user operator (low precedence)
   RegExMatch(x, "i)(.*)«(.*?)»(.*)", y)
   If IsFunc(y2)
      Return %y2%(Eval@(y1),Eval@(y3)) ; predefined relational ops

   StringGetPos i, x, |, R             ; execute rightmost | operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) | Eval@(SubStr(x,2+i))
   StringGetPos i, x, ^, R             ; execute rightmost ^ operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ^ Eval@(SubStr(x,2+i))
   StringGetPos i, x, &, R             ; execute rightmost & operator
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) & Eval@(SubStr(x,2+i))
                                       ; execute rightmost <<, >> operator
   RegExMatch(x, "(.*)(\<\<|\>\>)(.*)", y)
   IfEqual y2,<<, Return Eval@(y1) << Eval@(y3)
   IfEqual y2,>>, Return Eval@(y1) >> Eval@(y3)
                                       ; execute rightmost +- (not unary) operator
   RegExMatch(x, "(.*[^!\~±¬\@\*/\\])(±|¬)(.*)", y) ; lower precedence ops already handled
   IfEqual y2,±,  Return Eval@(y1) + Eval@(y3)
   IfEqual y2,¬,  Return Eval@(y1) - Eval@(y3)
                                       ; execute rightmost */% operator
   RegExMatch(x, "(.*)(\*|/|\\)(.*)", y)
   IfEqual y2,*,  Return Eval@(y1) * Eval@(y3)
   IfEqual y2,/,  Return Eval@(y1) / Eval@(y3)
   IfEqual y2,\,  Return Mod(Eval@(y1),Eval@(y3))
                                       ; execute rightmost power
   StringGetPos i, x, @, R
   IfGreaterOrEqual i,0, Return Eval@(SubStr(x,1,i)) ** Eval@(SubStr(x,2+i))
                                       ; execute rightmost function, unary operator
   If !RegExMatch(x,"(.*)(!|±|¬|~|'(.*)')(.*)", y)
      Return x                         ; no more function (y1 <> "" only at multiple unaries: --+-)
   IfEqual y2,!,Return Eval@(y1 . !y4) ; unary !
   IfEqual y2,±,Return Eval@(y1 .  y4) ; unary +
   IfEqual y2,¬,Return Eval@(y1 . -y4) ; unary - (they behave like functions)
   IfEqual y2,~,Return Eval@(y1 . ~y4) ; unary ~
   If IsFunc(y3)
      Return Eval@(y1 . %y3%(y4))      ; built-in and predefined functions(y4)
   Return Eval@(y1 . Eval1(RegExReplace(f%y3%__F, f%y3%__X, y4))) ; LAST: user defined functions
}

ToBin(n) {      ; Binary representation of n. 1st bit is SIGN: -8 -> 1000, -1 -> 1, 0 -> 0, 8 -> 01000
   Return n=0||n=-1 ? -n : ToBin(n>>1) . n&1
}
ToBinW(n,W=8) { ; LS W-bits of Binary representation of n
   Loop %W%     ; Recursive (slower): Return W=1 ? n&1 : ToBinW(n>>1,W-1) . n&1
      b := n&1 . b, n >>= 1
   Return b
}
FromBin(bits) { ; Number converted from the binary "bits" string, 1st bit is SIGN
   n = 0
   Loop Parse, bits
      n += n + A_LoopField
   Return n - (SubStr(bits,1,1)<<StrLen(bits))
}

Sgn(x) {
   Return (x>0)-(x<0)
}

MIN(a,b) {
   Return a<b ? a : b
}
MAX(a,b) {
   Return a<b ? b : a
}
GCD(a,b) {      ; Euclidean GCD
   Return b=0 ? Abs(a) : GCD(b, mod(a,b))
}
Choose(n,k) {   ; Binomial coefficient
   p := 1, i := 0, k := k < n-k ? k : n-k
   Loop %k%                   ; Recursive (slower): Return k = 0 ? 1 : Choose(n-1,k-1)*n//k
      p *= (n-i)/(k-i), i+=1  ; FOR INTEGERS: p *= n-i, p //= ++i
   Return Round(p)
}

Fib(n) {        ; n-th Fibonacci number (n < 0 OK, iterative to avoid globals)
   a := 0, b := 1
   Loop % abs(n)-1
      c := b, b += a, a := c
   Return n=0 ? 0 : n>0 || n&1 ? b : -b
}
fac(n) {        ; n!
   Return n<2 ? 1 : n*fac(n-1)
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

27 (изменено: marusa122, 2023-03-24 00:41:10)

Re: AHK: Работа с двумя окнами

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

:*:-25::
MouseMove, 1266, 663 ; перемещает мышку на координаты нужного числа (всегда одни и те же)
Click 2 ; выделяет это число
ClipSave=%ClipboardAll%
Clipboard= ;
Send, ^{c} ; копирует выделенный текст
ClipWait, 2
Result:=Round(Clipboard-25,2) ; делает вычисления и округляет число
MsgBox, 0, Итог, %Result%, ; показывает MsgBox с результатом
Clipboard=%ClipSave%
Return

Здесь даже не пришлось добавлять деление на 100. При работе с Калькулятором число наподобие 999.00 копировалось и вставлялось как 99900, а в данном случае скрипт правильно воспринимает точку как разделитель.

28

Re: AHK: Работа с двумя окнами

marusa122, я последний раз напомню про точку. Мне начинает казаться, что вы издеваетесь.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

29 (изменено: __Михаил__, 2023-04-11 22:26:32)

Re: AHK: Работа с двумя окнами

teadrinker
Советую добавить этот код в коллекцию (если его там ещё почему-то нет). Через поиск не обнаружил. Проверил на Win XP, 7, 10 и везде работает.

Win11x64, AHK v1.1.37.01 (Unicode 64-bit), AHK v2.0.17| AHK-Wiki | Переменные и выражения | RegEx101

30

Re: AHK: Работа с двумя окнами

Добавил.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder