Drag&Drop на главной панели инструментов

Обсуждение вопросов автоматизации Total Commander с помощью сторонних программ и скриптовых технологий.

Модераторы: Avada, CaptainFlint, Nik, d-view, Моторокер

sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):Попробовал как с включеннной кнопкой истории, так и выключенной.
Я ещё и без заголовка файловой панели пробовал.
Можно использовать и 2926 (Цепочки навигации: вкл/выкл) и 2910 (Командная строка: вкл/выкл) и т.п.
Orion9 писал(а):В usercmd.ini нужна команда
У меня команда заработала, когда вместо %A0 записал просто %A.
Orion9 писал(а):Вроде удобно.
Да, хорошо!
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):Можно использовать и 2926 (Цепочки навигации: вкл/выкл) и 2910 (Командная строка: вкл/выкл) и т.п.
Ну, пока и так работает :D Целый час вчера гонял. Нельзя назвать это стресс-тестом панели, конечно, но уже что-то.

И кстати, ошибок никаких не словил, это радует! Думаю, сейчас ошибки могут быть только на стадии поиска и загрузки панели (первые три функции - HBarShowHint, HBarGetFile, HBarLoad). Плохо, что окно панели не хранит путь к панели, это создает трудности. Т.ч. придется пересматривать эти функции и делать проверку на конфликт имен, что-то вроде

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

Func HBarConflict()
    Local bars = List(), text, num = 0
    bars.Text = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    For i = 0 To bars.Count - 1
        If StrPos(bars[i], gHBarName, -1) > 0 Then
           num += 1
           text &= bars[i] & auCRLF
        Endif   
    Next
    If num < 2 Then text = ""
    Return text    
EndFunc
Но здесь не учитывается путь к панели из wincmd.ini и путь активной панели. От последнего, думаю, надо избавляться, чтобы он не создавал путаницу. Но это всё-равно не поможет, если OPENBAR будет вызван из самого ТС с произвольным путем к панели.

Самое неприятное, что когда OPENBAR открывает файл, дескриптор панели не меняется. Т.е. если я открою DEFAULT.BAR из каталога D:\Temp и если у меня открыта главная панель как %COMMANDER_PATH%\DEFAULT.BAR, то нет никакой возможности узнать, какая из этих двух панелей открыта в данный момент только по имени файла.

Ладно, подумаю над этим чуть позже. Сейчас, видимо, придется взять небольшую паузу, но если будут какие-то ошибки, постараюсь потихоньку исправлять. Главное иметь в виду, что открытие панели сейчас самое уязвимое место, и что остальной функционал еще недостаточно тестирован, т.ч. могут быть ошибки и косяки.
sa писал(а):У меня команда заработала, когда вместо %A0 записал просто %A.
Спасибо, подправил. Убрал заодно снятие оконной процедуры при открытии через OPENBAR. Видимо, в нем была проблема. Еще Loopback писал, что вешается-то она легко, а вот снимается с ошибками. У меня она, похоже, вообше не снималась или снималась через раз

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

DllCall("SetWindowLong" & (auX64 ? "PtrW" : "W"), _
         "hwnd", hBarWnd, "int", -4, "long_ptr", gHBarWndProc, "ptr")
Добавил еще пункт "Индекс удаления 1", чтобы можно было сразу очистить панель и проверку в HBarSaveAs, хотя там лучше сделать через копипование файла, но это потом.

Последний тестовый модуль.

[cut="Bars.aucfg"]

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

Pragma IncludeOnce

# наведение на главную панель
ControlSetHint /F /D:50 28 "HBarShowHint"

# правый клик с удержанием Ctrl
ControlSetMouseAction /R /K:C 28 "HBarShowMenu"

# правый клик с удержанием Shift
ControlSetMouseAction /R /K:S 28 "HBarShowOpenMenu"

# Win + Backspace
# отмена последнего действия 
SetHotkeyAction /K:W /H:BS "HBarUndo"

Global gDragIndex, gDropIndex, gSmartIndex
Global gHBarButtons = 0, gHBarDivs = 0, gHBarSmart = 1

Global gHBarName, gHBarFile, gHBarFileTs, gHBarReady = 0
Global gHBarTxt, gHBarCP = "ANSI", gHBarWincmd = 0, gHBarDbg = 0
Global gHBarHint = 0, gHBarVerb, gHBarCancel, gHBarCancelLoad, gHBarLoadError = 1
Global gHBarList = List(), gHBarMap = List(), gHBarBars = list(), gHBarUndo, gHBarUndoTs, gHBarUndoLoad

Global gHBarWndProc, hBarWnd = 0
Global gHBarWP = Callback("HBarWndProc", "hwnd;uint;wparam;lparam")

Func HBarWndProc(hWnd, uMsg, wParam, lParam)
    Static IsDrag = 0, _
           hTotal = DllCall("GetModuleHandleW", "Ptr", 0, "handle"), _
           MK_SHIFT        = 0x0004, _      
           MK_CONTROL      = 0x0008, _
           MK_LBUTTON      = 0x0001, _
           WM_MOUSEMOVE    = 0x0200, _
           WM_LBUTTONDOWN  = 0x0201, _
           WM_LBUTTONUP    = 0x0202, _
           MoveButton      = MK_CONTROL, _
           CopyButton      = MK_SHIFT
    Static drag_system = DllCall("LoadCursor", "Ptr", 0, "Ptr", 32646), _
           stop_system = DllCall("LoadCursor", "Ptr", 0, "Ptr", 32648), _
           icon_cursor = DllCall("LoadCursor", "Ptr", hTotal, "Ptr", 13), _           
           kick_cursor = DllCall("LoadCursor", "Ptr", hTotal, "Ptr", 26), _
           copy_cursor = DllCall("LoadCursor", "Ptr", hTotal, "Ptr", 25), _
           drag_cursor = DllCall("LoadCursor", "Ptr", hTotal, "Ptr", 22), _
           stop_cursor = DllCall("LoadCursor", "Ptr", hTotal, "Ptr", 21)
    Static SM_CXDRAG = 68, SM_CYDRAG = 69, _
           dragX = GetSystemMetrics(SM_CXDRAG), dragY = GetSystemMetrics(SM_CYDRAG), _
           startX, startY
    Local x, y, pos
    Switch uMsg
       Case WM_LBUTTONDOWN
          If IsDrag = 0 And (BitAND(wParam, MoveButton) Or BitAND(wParam, CopyButton)) Then
             Static buf = Buffer(8)	# POINT
             buf.Zero()
             x = BitAND(lParam, 0xFFFF)
             y = BitAND(BitShift(lParam,16), 0xFFFF)
             startX = x
             startY = y
             buf.SetNum(0, "int", x, "int", y)
             DllCall("ClientToScreen", "hwnd", hWnd, "ptr", buf.ptr)
             pos = buf.GetNum(0, "int64")             
             #x = buf.GetNum(0)
             #y = buf.GetNum(4)
             #If DllCall("DragDetect", "hwnd", hWnd, "int64", MakeInt(y, x, 2)) Then
             If DllCall("DragDetect", "hwnd", hWnd, "int64", pos) Then
                Local InfoMsg
                If gHBarLoadError Or gHBarList.Count = 0 Then
                   InfoMsg = "Панель не загружена"              
                ElseIf FileGetTime(gHBarFile) <> gHBarFileTs Then
                   InfoMsg = "Файл панели изменился"
                Endif
                If InfoMsg <> "" Then
                   ShowDragHint(InfoMsg)
                   lParam = -1000
                   Return DllCall("CallWindowProcW", _
                         "ptr", gHBarWndProc, "hwnd", hWnd, "uint", uMsg, "wparam", wParam, "lparam", lParam)
                EndIf         
                If BitAND(wParam, MoveButton) And BitAND(wParam, CopyButton) Then
                   IsDrag = MoveButton
                Else
                   IsDrag = wParam
                EndIf
                WinSetStyle(0x80000, 3, hWnd)
                If BitAND(wParam, MoveButton) Then
                   DllCall("SetLayeredWindowAttributes", "hwnd", hWnd, "ptr", 0, "byte", 128, "dword", 2)
                Else   
                   DllCall("SetLayeredWindowAttributes", "hwnd", hWnd, "ptr", 0, "byte", 216, "dword", 2)
                EndIf   
                x = buf.GetNum(0)
                y = buf.GetNum(4)                           
                gDragIndex = HBarGetIdx(1, x, y)
             Else
                IsDrag = 0
                DllCall("CallWindowProcW", _
                   "ptr", gHBarWndProc, "hwnd", hWnd, "uint", uMsg, "wparam", wParam, "lparam", lParam) 
                DllCall("CallWindowProcW", _
                   "ptr", gHBarWndProc, "hwnd", hWnd, "uint", WM_LBUTTONUP, "wparam", wParam, "lparam", lParam)
                Return 0
             EndIf
          EndIf    
       Case WM_LBUTTONUP
          If IsDrag > 1 Then
             Local nDragMode = IsDrag
             DllCall("ReleaseCapture")
             IsDrag = 0
             If WinFromPoint() = hWnd Then
                MouseGetPos("x","y")
                gSmartIndex = 0
                gDropIndex = HBarGetIdx(2, x, y)
                If gDragIndex <> gDropIndex Then
                   If gHBarHint Then ShowDragHint("Index " & gDragIndex & " To Index: " & gDropIndex)
                   If BitAND(nDragMode, MoveButton) Then                      
                      HBarDragDrop(gDragIndex, gDropIndex)
                   Else
                      If BitAND(wParam, MoveButton) Then
                         HBarDragIcon(gDragIndex, gDropIndex)
                      Else
                         HBarDragCopy(gDragIndex, gDropIndex)
                      EndIf
                   EndIf   
                EndIf
             Else
                y = BitAND(BitShift(lParam,16), 0xFFFF)
                If BitAND(wParam, MoveButton) And lParam > 0 And y > 200 Then HBarDragDelete(gDragIndex)
             EndIf
             WinSetStyle(0x80000, 5, hWnd)
             #Return 0
             #lParam = MakeInt(y, x + 200, 2)
             lParam = -1000
          EndIf
       Case WM_MOUSEMOVE
          If IsDrag > 1 And BitAND(wParam, MK_LBUTTON) Then
             If WinFromPoint() = hWnd Then
                If BitAND(IsDrag, CopyButton) Then
                   DllCall("SetCursor", "Ptr", BitAND(wParam, MoveButton) ? icon_cursor : copy_cursor)
                Else   
                   DllCall("SetCursor", "Ptr", drag_cursor)
                EndIf 
                #DllCall("SetCursor", "Ptr", BitAND(IsDrag, MoveButton) ? drag_cursor : copy_cursor)
             Else
                y = BitAND(BitShift(lParam,16), 0xFFFF)
                #WinSetText(lParam & " y=" & y)
                If BitAND(wParam, MoveButton) And lParam > 0 And y > 200 Then
                   DllCall("SetCursor", "Ptr", kick_cursor)
                Else   
                   DllCall("SetCursor", "Ptr", stop_cursor)
                EndIf    
             EndIf
          EndIf   
    EndSwitch
    Return DllCall("CallWindowProcW", _
                   "ptr", gHBarWndProc, "hwnd", hWnd, "uint", uMsg, "wparam", wParam, "lparam", lParam)
EndFunc

Func HBarShowHint()
    # панель в режиме drag'n'drop
    If WinHasStyle(0x80000, 1, hBarWnd) Then Return  
    # состояние CapsLock и Shift
    If BitAND(DllCall("GetKeyState", "int", 0x14, "short"), 1) = 0 Then
       If Not IsPressed(0x10) Then Return
    EndIf
    # дескриптор горизонтальной панели 
    Local hWnd = RequestInfo(28)
    If hWnd = 0 Then 
       Return "Окно панели не найдено " & hWnd
    EndIf
    # глобальные переменные дескриптора и оконной процедуры
    If hBarWnd <> hWnd Then
       hBarWnd = hWnd
       gHBarWndProc = DllCall("SetWindowLong" & (auX64 ? "PtrW" : "W"), _
                             "hwnd", hBarWnd, "int", -4, "long_ptr", gHBarWP.Ptr, "ptr")
       Return                    
    EndIf
    # поиск файла панели
    If gHBarName <> WinGetText(hBarWnd) Then
       gHBarUndo = false
       gHBarUndoLoad = ""
       gHBarCancel = false
       gHBarCancelLoad = ""       
       gHBarFile = HBarGetFile()
       If Not FileExist(gHBarFile) Then
          gHBarName = ""
          Return "Файл панели не найден " & gHBarFile
       EndIf
       # метка времени
       gHBarFileTs = FileGetTime(gHBarFile)
       # загрузка панели       
       HBarLoad()
       If gHBarLoadError Then Return HBarGetHint()
    ElseIf FileGetTime(gHBarFile) <> gHBarFileTs Then
       gHBarUndo = false
       gHBarUndoLoad = ""
       If Not FileExist(gHBarFile) Then
          gHBarName = ""
          Return "Файл панели не найден " & gHBarFile
       EndIf   
       gHBarFileTs = FileGetTime(gHBarFile)
       HBarLoad()
       If gHBarLoadError Then Return HBarGetHint()       
    EndIf
    # индекс кнопки под курсором
    gDragIndex = HBarGetIdx()
    If Not gDragIndex And Not gHBarDbg Then Return "Не удалось определить индекс кнопки"
    Return HBarGetHint()  
EndFunc

Func HBarGetFile()
    Local bar, txt
    # информация о файле панели в wincmd.ini
    IniRead /R bar %COMMANDER_INI% "Buttonbar" "Buttonbar" "%COMMANDER_PATH%\DEFAULT.BAR"
    # раскрытие переменной окружения
    bar = Set(bar) 
    # в конфигурации указано только имя файла
    If FileGetDir(bar) = "" Then bar = COMMANDER_PATH & "" & bar
    gHBarWincmd = bar
    # чтение текста окна панели
    txt = WinGetText(hBarWnd)
    gHBarName = txt
    # на панель загружен временный .bar файл
    If Not StrPos(bar, txt) Then       
       bar = COMMANDER_PATH & "\Bars" & txt
    EndIf
    If Not FileExist(bar) Then
       bar = GetCurrentPath() & "" & txt
    EndIf
    # проверка существования файла панели
    If Not FileExist(bar) Then
       bar = COMMANDER_PATH & "" & txt
    EndIf      
    Return bar
EndFunc

Func HBarConflict()
    Local bars = List(), text, num = 0
    bars.Text = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    For i = 0 To bars.Count - 1
        If StrPos(bars[i], gHBarName, -1) > 0 Then
           num += 1
           text &= bars[i] & auCRLF
        Endif   
    Next
    If num < 2 Then text = ""
    Return text    
EndFunc

Func HBarLoad()
    gHBarLoadError = 1
    Local bar = FileRead(gHBarFile, 128, "RAW")
    Local bom = "FFFE"
    Local utf = "5B0042007500740074006F006E006200610072005D000D000A00"
    If ERROR = 1 Then
       gHBarCP = "Unknown"       
       Return
    EndIf   
    If StrLeft(bar, 4) = "FFFE" Then 
       gHBarCP = "UTF-16 BOM"
    Else
       IF StrPos(bar, utf) Then
          gHBarCP = "UTF-16 без BOM"
       Else
          gHBarCP = "ANSI"
       EndIf
    EndIf
    # загрузка файла в массив
    gHBarList.Count = 0
    gHBarList.LoadFromFile(gHBarFile, gHBarCP)    
    # количество кнопок в файле
    Local nCount = 0
    IniRead /R nCount  %gHBarFile% "Buttonbar" "Buttoncount" 0
    # обнуление счетчиков
    gHBarDivs = 0
    gHBarButtons = 0
    # локальные переменные
    Local j, key, val, found, oButton = Map(), oCmd = Map()
    # поиск кнопок и команд
    For j = 0 To gHBarList.Count - 1        
        found = 0    
        If StrLeft(gHBarList[j], 6) = "button" Then  
           found = 1
        ElseIf StrLeft(gHBarList[j], 3) = "cmd" Then  
           found = 2
        EndIf
        If found > 0 Then
           key = StrLower(StrPart(gHBarList[j], "=", 1))
           val = StrLen(StrPart(gHBarList[j], "=", 2))
           If found = 1 Then
              oButton.Set(key, val)              
           Else
              oCmd.Set(key, val)          
           EndIf
        EndIf
    Next
    gHBarMap.Count = 0    
    # подсчет кнопок и разделителей
    For j = 1 To nCount     
        key = "button" & j
        val = oButton.Get(key, 0)
        If val > 0 Then
           # количество кнопок                 
           gHBarButtons += 1
           gHBarMap.Add(1)
        Else
           # количество разделителей  
           gHBarDivs += 1
           If oCmd.Get("cmd" & j, 0) = 2 Then
              gHBarMap.Add(-2)           
           Else 
              gHBarMap.Add(0)
           EndIf   
        EndIf
    Next
    Free(oButton, oCmd)
    gHBarLoadError = gHBarMap.Count > 0 ? 0 : 1
    # карта панели
    gHBarTxt = auCRLF & auCRLF & "Loaded" & ": " & gHBarFile & " > " & gHBarCP & " "
    If gHBarLoadError Then gHBarTxt &= " -- ERROR LOADING"
    gHBarTxt &= auCRLF
    For j = 0 To gHBarMap.Count - 1
        gHBarTxt &= gHBarMap[j] & "  "
        If j > 0 and Mod(j, 50) = 0 Then gHBarTxt &= auCRLF
    Next
    gHBarTxt &= auCRLF & "Wincmd.ini: " & gHBarWincmd
    #OutputDebugString("HBarLoad=" & gHBarMap.Count)
EndFunc

Func HBarGetIdx(MousePos = false, PosX = 0, PosY = 0)
    # дескриптор монитора окна ТС
    Local hMon = DllCall("MonitorFromWindow", "hwnd", AUTORUN_TCHANDLE, "dword", 2)
    # буфер для структуры MONITORINFOEX
    Local buf = Buffer(104)
    buf.Zero()
    buf.SetNum(0, "dword", 104)
    DllCall("GetMonitorInfoW", "ptr", hMon, "ptr", buf.ptr)
    # имя монитора
    Local sMon = buf.GetStr(40)
    # информация о масштабировании экрана
    Local DC = DllCall("GetDC", "int", 0)
    Local nDPI = DllCall("GetDeviceCaps", "handle", DC, "int", 88) # LOGPIXELSX
    Local nScale = DllCall("MulDiv", "int", 100, "int", nDPI, "int", 96)
    DllCall("ReleaseDC", "int", 0, "handle", DC)     
    # локальные переменные
    Local x, y, w, h, mx, my, idx, row, rows, rowh, dpi, b, extra
    # координаты и размеры окна панели
    WinGetPos("x", "y", "w", "h", hBarWnd) 
    # координаты указателя мыши
    If MousePos Then
       mx = PosX
       my = PosY
    Else
       MouseGetPos("mx", "my")
    EndIf   
    # чтение информации о высоте кнопок
    IniRead /R b %COMMANDER_INI% "Buttonbar" "Buttonheight"
    IniRead /R dpi %COMMANDER_INI% "Buttonbar" "DefaultDpi" %'nDPI'
    Local b_ini = b, b_calc
    # масштабирование экрана
    If dpi <> nDPI Then
       IniRead /R d %COMMANDER_INI% "Buttonbar" %'"Buttonheight" & nDPI' 0
       If d <> 0 Then 
          b = d
       Else   
          b = Round(b / (dpi / nDPI), 0)
          b_calc = "*"
       EndIf
    EndIf   
    # количество рядов кнопок на панели
    rows = Floor(h/b)
    If rows = 0 Then Return ShowDragHint("Error number of rows can't be zero")
    # количество дополнительных пикселей
    extra = Mod(h,b)
    # ширина разделителя    
    Local div = Floor(b/3)
    # высота одного ряда
    rowh = b + 3
    # номер ряда под указателем
    row = Ceil((my - y)/rowh)
    # абстрактные координаты по оси х
    Local tx = (mx - x + 1) + ((row - 1) * w), cx = 0, ex = 0, idx_padding = 0
    # индекс кнопки в ряду    
    If gHBarDivs = 0 Then
       idx = (Floor((mx - x)/b) + 1) + (row-1)*(Floor(w/b))
       If idx > gHBarButtons Then idx = gHBarButtons
    Else
       j = 1
       idx = 0       
       For i = 0 To gHBarMap.Count - 1
           idx_padding = 0
           If gHBarMap[i] = -2 Then              
              If i = 0 Then cx += w    # первая кнопка разрыв
              j += 1
              cx += ex           
              If i > 0 And gHBarMap[i-1] = -2 Then
                 cx += w - ex
              EndIf
              If i = gHBarMap.Count - 1 Then cx += w    # последняя кнопка разрыв
              If cx >= tx Then
                 idx = i + 1
                 Break
              EndIf
              Continue
           EndIf
           cx += (gHBarMap[i] = 0 ? div : b)
           ex = j*w - cx
           If i + 1 <= gHBarMap.Count - 1 Then
              next_button = gHBarMap[i+1]
              next_width = (next_button = -2 ? 0 : (next_button = 0 ? div : b))
              If ex < next_width Then
                 cx += ex
                 idx_padding += ex
                 j += 1
              EndIf
           Else
              cx += ex
              idx_padding = ex
           EndIf   
           If cx >= tx Then
              idx = i + 1
              Break
           EndIf   
       Next
    EndIf
    If idx = 0 Then Return idx
    
    Local SysDbg = 0    
    If SysDbg Then 
       OutputDebugString("idx=" & idx & " gHBarMap=" & gHBarMap.Count)
    EndIf   
    
    idx_button = gHBarMap[idx-1]
    idx_width = (idx_button = -2 ? 0 : (idx_button = 0 ? div : b))
    idx_pixel = tx - (cx - idx_padding - idx_width)
    idx_float = idx_width > 0 ? idx_pixel / idx_width : 0
    # дробный индекс    
    gSmartIndex = idx + Round(idx_float, 3)
    If Floor(gSmartIndex) > idx Then gSmartIndex = idx + 0.999
    # дополнительная информация
    gHBarVerb = "mouse=" & mx & ", " & my & auCRLF & _
                "bar=" & x & ", " & y & ", " & w & ", " & h & auCRLF &  _
                "map=" & tx & ", " & cx & auCRLF & _
                "row=" & row & " of " & rows & auCRLF & _
                "height=" & rowh & " (" & extra & ")" & " b=" & b & b_calc & " (" & b_ini & ")" & auCRLF & _
                "buttons=" & gHBarButtons & " + " & gHBarDivs & " divs" & auCRLF & _ 
                "width=" & b & ", " & div & auCRLF & _
                "mon=" & HMon & " dpi=" & nDPI & " scale=" & nScale & "%" & auCRLF & auCRLF & _ 
                "smart=" & gSmartIndex & auCRLF & _
                "button=" & idx_button & " width=" & idx_width & " pixel=" & idx_pixel
    If SysDbg Then
       OutputDebugString("" & auCRLF & (Not MousePos ? "Hint" : "Proc " & MousePos) & auCRLF & gHBarVerb & auCRLF & "idx=" & idx)
    EndIf
    If MousePos <> 2 Then gSmartIndex = 0
    Return idx
EndFunc

Func HBarGetHint()
    # чтение информации о кнопке из .bar фйла
    Local sMenu, sButton, sCmd, sParam, sPath, sIcon, text
    IniRead sMenu   %gHBarFile% "Buttonbar" %'"menu"   & gDragIndex' ""
    IniRead sButton %gHBarFile% "Buttonbar" %'"button" & gDragIndex' ""
    IniRead sCmd    %gHBarFile% "Buttonbar" %'"cmd"    & gDragIndex' ""
    IniRead sParam  %gHBarFile% "Buttonbar" %'"param"  & gDragIndex' ""
    IniRead sPath   %gHBarFile% "Buttonbar" %'"path"   & gDragIndex' ""
    IniRead sIcon   %gHBarFile% "Buttonbar" %'"iconic" & gDragIndex' ""
    # удаление подсказки тотала
    Local hTip = WinFind(0, auX64 ? "HintWindow" : "TToolTip")
    If hTip > 0 Then DllCall("DestroyWindow", "handle", hTip)
    # возврат текста     
    text = "index=" & gDragIndex & " of " & gHBarButtons+gHBarDivs & auCRLF & _           
           "menu=" & sMenu & auCRLF & _
           "button=" & sButton & auCRLF & _
           "cmd=" & sCmd & auCRLF & _
           "param=" & sParam & auCRLF & _
           "path=" & sPath  & auCRLF & _
           "iconic=" & sIcon & (gHBarDbg ? auCRLF & auCRLF & gHBarVerb & gHBarTxt : "")
    Return text
EndFunc

Func HBarDragDrop(DragIdx, DropIdx)
    Static aItems = List("button","cmd","param","path","menu","iconic")
    Local T1, T2, bTL = 0
    Local i, j, k, txt, key, new
    
    # режим отладки
    If bTL Then T1 = GetUptime()

    # сохранение файла панели     
    HBarSaveState(txt)
    
    # временная замена индекса DragIdx = 9999
    For j = 0 To aItems.Count - 1
       key = aItems[j] & DragIdx & "="
       new = aItems[j] & 9999 & "="
       txt = StrReplace(txt, key, new)          
    Next
    
    If bTL Then   
       T2 = Round(GetUptime() - T1, 0) / 1000
       OutputDebugString("Время операции 1: " &  StrFormat("%.3f", T2) & " sec")
       OutputDebugString("Drag & Drop: " &  DragIdx & " -> " & DropIdx)       
    EndIf
    
    # умный сброс по границам кнопки
    If gHBarSmart Then
       If gSmartIndex > DropIdx + 0.499 Then
          If DragIdx >= DropIdx Then
             DropIdx += 1
          EndIf
       Else
          If DragIdx < DropIdx Then
             DropIdx -= 1
          EndIf
       EndIf
    EndIf   
    
    # сдвиг индексов 
    If DragIdx < DropIdx Then
       If gHBarMap[DropIdx-1] = -2 Then DropIdx -= 1
       For i = DragIdx + 1 To DropIdx
            For k = 0 To aItems.Count - 1
               key = aItems[k] & i & "="
               new = aItems[k] & i - 1 & "="       
               txt = StrReplace(txt, key, new)
               #If bTL Then OutputDebugString("DragIdx < " & key & " -> " & new)               
            Next
        Next
     Else
       For i = DragIdx - 1 TO DropIdx Step -1
            For k = 0 To aItems.Count - 1
               key = aItems[k] & i & "="
               new = aItems[k] & i + 1 & "="       
               txt = StrReplace(txt, key, new)
               #If bTL Then OutputDebugString("DragIdx > " & key & " -> " & new)               
            Next
        Next
     EndIf
     
    If bTL Then
       T2 = Round(GetUptime() - T1, 0) / 1000
       OutputDebugString("Время операции 2: " &  StrFormat("%.3f", T2) & " sec")
    EndIf 
    
    # обратная замена индекса DragIdx -> DropIdx 
    For j = 0 To aItems.Count - 1
       key = aItems[j] & 9999 & "="
       new = aItems[j] & DropIdx & "="
       txt = StrReplace(txt, key, new)         
    Next
    
    # сохранение панели
    gHBarList.Text = txt
    gHBarList.SaveToFile(gHBarFile, gHBarCP)
    
    # обновление панели
    HBarUpdateReload()
    
    If bTL Then   
       T2 = Round(GetUptime() - T1, 0) / 1000
       OutputDebugString("Время операции 3: " &  StrFormat("%.3f", T2) & " sec")
    EndIf
EndFunc

Func HBarReadIndex(Index)
    Local key, val, txt
    Static aItems = List("button","cmd","param","path","menu","iconic")
    # сохранение индекса
     For j = 0 To aItems.Count - 1
        key = aItems[j] & Index
        val = IniRead(gHBarFile, "Buttonbar", key) 
        If val <> '' Then 
           If StrLeft(val, 1) ='"' And StrRight(val, 1) ='"' Then val = '"' & val & '"'
           txt &= key & '=' & val & auCRLF
       EndIf  
     Next 
     Return StrTrim(txt)
EndFunc

Func HBarDragCopy(DragIdx, DropIdx)
    Local key, val, ini, new, txt
    Static aItems = List("button","cmd","param","path","menu","iconic")
    
    # сохранение кнопки
    For j = 0 To aItems.Count - 1
       key = aItems[j] & DragIdx
       val = IniRead(gHBarFile, "Buttonbar", key) 
       If val <> '' Then 
          If StrLeft(val, 1) ='"' And StrRight(val, 1) ='"' Then val = '"' & val & '"'
          ini &= key & '=' & val & auCRLF
       EndIf   
    Next 
    ini = StrTrim(ini)
    If ini = '' Then Return ShowDragHint("Нет данных о кнопке " & DragIdx)
    
    # сохранение файла панели    
    HBarSaveState(txt)
    
    # умный сброс по границам кнопки
    If gHBarSmart Then
       If gSmartIndex > DropIdx + 0.499 Then
          DropIdx += 1 
       EndIf
    EndIf

    # сдвиг индексов    
    If DropIdx <= gHBarMap.Count Then
       # замена ключей со сдвигом +1
       For i = gHBarMap.Count To DropIdx Step -1
           For j = 0 To aItems.Count - 1
               key = aItems[j] & i & "="
               new = aItems[j] & i + 1 & "="
               txt = StrReplace(txt, key, new)
           Next
       Next
    EndIf
    
    # добавление сохраненной кнопки
    For j = 0 To aItems.Count - 1
        key = aItems[j] & DragIdx & "="
        new = aItems[j] & DropIdx & "="
        ini = StrReplace(ini, key, new)
    Next
    txt &= auCRLF & ini
    
    # сохранение панели
    gHBarList.Text = txt
    gHBarList.SaveToFile(gHBarFile, gHBarCP)
    IniWrite(gHBarFile, "Buttonbar", "Buttoncount", gHBarMap.Count+1)
    
    # обновление панели    
    HBarUpdateReload()
EndFunc

Func HBarSaveState(ByRef Text)
    Text = gHBarList.Text
    gHBarUndoLoad = Text
    If Not gHBarCancel Then
       gHBarCancel = true
       gHBarCancelLoad = Text
    EndIf      
EndFunc

Func HBarUpdateReload(Reopen = 0)
    # временя последнего изменения
    gHBarUndoTs = FileGetTime(gHBarFile)
    gHBarFileTs = FileGetTime(gHBarFile)
    If gHBarUndoTs Then 
       gHBarUndo = true
    Else
       gHBarUndo = false    
       gHBarUndoLoad = ""   
    EndIf
    
    # загрузка файла панели    
    HBarLoad()
    
    # обновление панели
    If Reopen Then
       SendMessage(AUTORUN_TCHANDLE, 11, 0, 0)
       SendCommand(2901, 0, 1)
       SendCommand(2901, 0, 1)     # cm_VisButtonBar
       Sleep(10)
       SendMessage(AUTORUN_TCHANDLE, 11, 1, 0)
   Else    
       SendCommand(2945)     # cm_ReloadBarIcons
       SendCommand(2919)
       SendCommand(2919)
   EndIf
   #WinRedraw(1)   
   #WinRedraw(0)
EndFunc

Func ShowDragHint(DragHint)
    Local dark = IniRead(COMMANDER_INI, "Configuration", "DarkMode", 0)
    SetHintParam("ShowHint", "Font", 13, "Tahoma")
    SetHintParam("ShowHint", "BackColor", 0x0F0F0F)
    SetHintParam("ShowHint", "Text", 0xFFFFFF)
    SetHintParam("ShowHint", "DarkBackColor", 0xFF0000)
    SetHintParam("ShowHint", "DarkText", 0xFFFFFF)    
    ShowHint(auCRLF & "    " & DragHint & "    " & auCRLF, 0, 0, 1000, 1)
    WinAlign(LAST_HINT_WINDOW)
    If Not dark Then
       WinSetStyle(0x80000, 3, LAST_HINT_WINDOW)
       DllCall("SetLayeredWindowAttributes", _
                "hwnd", LAST_HINT_WINDOW, "ptr", 0, "byte", 192, "dword", 2)
    EndIf                                         
    Sleep(100)
    SetHintParam("ShowHint", "Reload")  
EndFunc

Func HBarShowMenu()
    Local hWnd = RequestInfo(28)
    If hWnd = 0 Then 
       ShowDragHint("Окно панели не найдено")
       Return
    EndIf
    If gHBarName <> WinGetText(hWnd) Then
       ShowDragHint("Shift + наведение загрузка панели")
       Return
    EndIf
    ShowPopupMenu /D /F /I:0 "HBarMenu"
EndFunc

Func HBarMenu()
    Local txt
    Local bak1 = FileChangeExt(gHBarFile, "bak1")
    Local bak2 = FileChangeExt(gHBarFile, "bak2")
    txt &= 'MENUITEM "'  & gHBarName & '", em_aucmd /D' & auCRLF
    txt &= 'MENUITEM SEPARATOR' & auCRLF
    txt &= 'MENUITEM "Загружена как ANSI", em_aucmd /D ' & (gHBarCP = "ANSI" ? "/C" : "") & ' -1' & auCRLF
    txt &= 'MENUITEM "Загружена как UTF-16", em_aucmd /D ' & (StrPos(gHBarCP, "UTF-16") ? "/C" : "") & ' -1' & auCRLF
    txt &= 'MENUITEM SEPARATOR' & auCRLF    
    txt &= 'MENUITEM "Smart Drag&&Drop", em_aucmd ' & (gHBarSmart ?  "/C" : "") & ' -1 HBarSmart' & auCRLF     
    txt &= 'MENUITEM "Отладочная информация", em_aucmd ' & (gHBarDbg ?  "/C" : "") & ' -1 HBarDebugInfo' & auCRLF
    txt &= 'MENUITEM SEPARATOR' & auCRLF      
    txt &= 'MENUITEM "Сохранить...", em_aucmd -1 HBarSaveAs' & auCRLF      
    txt &= 'POPUP "Загрузить панель"' & auCRLF
    txt &= HBarAddBarMenu() & auCRLF      
    txt &= 'END_POPUP' & auCRLF
    txt &= 'MENUITEM SEPARATOR' & auCRLF
    txt &= 'MENUITEM "Добавить разрыв", em_aucmd -1 HBarAddButton -2' & auCRLF
    txt &= 'MENUITEM "Добавить разделитель", em_aucmd -1 HBarAddButton 1' & auCRLF    
    txt &= 'POPUP "Удалить кнопки за разрывом"' & auCRLF
    txt &= HBarBreakMenu() & auCRLF      
    txt &= 'END_POPUP' & auCRLF               
    txt &= 'MENUITEM SEPARATOR' & auCRLF
    txt &= 'MENUITEM "Отменить действие", em_aucmd ' & (gHBarUndo ?  "" : "/D") & ' -1 HBarUndo' & auCRLF
    txt &= 'MENUITEM "Отменить последние действия", em_aucmd ' & (gHBarCancel ?  "" : "/D") & ' -1 HBarCancel' & auCRLF
    txt &= 'MENUITEM SEPARATOR' & auCRLF
    txt &= 'MENUITEM "Резервная копия 1", em_aucmd ' & (FileExist(bak1) ? "" : "/D") & ' -1 HBarLoadBak 1' & auCRLF
    txt &= 'MENUITEM "Резервная копия 2", em_aucmd ' & (FileExist(bak2) ? "" : "/D") & ' -1 HBarLoadBak 2' & auCRLF    
    txt &= 'MENUITEM SEPARATOR' & auCRLF
    txt &= 'MENUITEM "Сделать резервную копию 1", em_aucmd -1 HBarSaveBak 1' & auCRLF
    txt &= 'MENUITEM "Сделать резервную копию 2", em_aucmd -1 HBarSaveBak 2' & auCRLF
    Return txt
EndFunc

Func HBarDebugInfo()
    gHBarDbg = Not gHBarDbg
    If gHBarDbg Then
       ShowDragHint("Отладочная информация включена")
    Else
       ShowDragHint("Отладочная информация отключена")    
    EndIf
EndFunc

Func HBarSmart()
    gHBarSmart = Not gHBarSmart
    If gHBarSmart Then
       ShowDragHint("Smart Drag&Drop включен")
    Else
       ShowDragHint("Smart Drag&Drop отключен")    
    EndIf
EndFunc

Func HBarSetCodePage(nCodePage)
    Local sCP = (nCodePage = 1 ? "ANSI" : "UTF-16")
    Local bLoadQ = gHBarCP <> sCP
    gHBarCP = sCP
    If bLoadQ Then
       MsgBox("Открыть панель в кодировке " & gHBarCP & "?", "Autorun", 3+64+0)
       If EXTENDED = 6 Then 
          HBarLoad()
          ShowDragHint("Панель открыта как " & gHBarCP)
          SendCommand(2945)  
       EndIf   
    EndIf
EndFunc

Func HBarSaveBak(nBak)
     Local bak = FileChangeExt(gHBarFile, "bak" & nBak)
     If FileExist(bak) Then
        MsgBox("Файл существует" & auCRLF & auCRLF & _
                bak & auCRLF & auCRLF & "Перезаписать?", "Autorun", 3+32+0)
        If EXTENDED <> 6 Then Return
     EndIf
     FileCopy(gHBarFile, bak, 1)
     ShowDragHint("Файл сохранен " & bak)
EndFunc

Func HBarLoadBak(nBak)
     Local bak = FileChangeExt(gHBarFile, "bak" & nBak)
     If Not FileExist(bak) Then
        MsgBox("Файл не существует " & bak, "Autorun")
        Return
     EndIf
     If Not FileExist(gHBarFile) Then
        MsgBox("Файл не существует " & gHBarFile, "Autorun")
        Return
     EndIf     
     MsgBox("Файл " & gHBarFile & " будет перезаписан файлом " & _
                bak & auCRLF & auCRLF & "Продолжить?", "Autorun", 3+32+0)
     If EXTENDED <> 6 Then Return
     FileCopy(bak, gHBarFile, 1)     
     HBarUpdateReload()
     ShowDragHint("Файл загружен " & bak)
EndFunc

Func HBarUndo()
    If Not gHBarUndo Then Return
    If FileGetTime(gHBarFile) <> gHBarUndoTs Then
       MsgBox("Файл " & gHBarFile & " изменился на диске" & auCRLF & auCRLF & _
              "Отмена действия невозможна", "Autorun", 64)
       Return       
    EndIf
    FileWrite(gHBarFile, gHBarUndoLoad, gHBarCP)    
    If ERROR = 1 Then
       MsgBox("Ошибка записи " & gHBarFile, "Autorun", 64)
    Else
       gHBarUndo = false
       gHBarUndoLoad = ""
       gHBarFileTs = FileGetTime(gHBarFile)
       HBarLoad()
       ShowDragHint("Действие отменено")
       SendCommand(2945)
       SendCommand(2919)
       SendCommand(2919)       
       #WinRedraw(0)
    EndIf   
EndFunc

Func HBarCancel()
    If Not gHBarCancel Then Return
    If gHBarCancelLoad = "" Then Return MsgBox("Пустое значение CancelLoad")
    MsgBox("Файл будет перезаписан" & auCRLF & auCRLF & _
            gHBarFile & auCRLF & auCRLF & "Продолжить?", "Autorun", 3+32+0)
    If EXTENDED <> 6 Then Return
    FileWrite(gHBarFile, gHBarCancelLoad, gHBarCP)    
    If ERROR = 1 Then
       MsgBox("Ошибка записи " & gHBarFile, "Autorun", 64)
    Else
       gHBarCancel = false
       gHBarCancelLoad = ""
       gHBarFileTs = FileGetTime(gHBarFile)           
       HBarLoad()
       ShowDragHint("Первоначальная панель загружена")
       SendCommand(2945)
       SendCommand(2919)
       SendCommand(2919)       
       #WinRedraw(0)
    EndIf   
EndFunc

Func HBarShowOpenMenu()
    ShowPopupMenu /D /F /I:0 "HBarOpenBarMenu"
EndFunc

Func HBarOpenBarMenu()
    Local sBars = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    Local text
    gHBarBars.Count = 0
    gHBarBars.Text = sBars
    For i = 0 To gHBarBars.Count - 1
        text = text & 'MENUITEM "' & gHBarBars[i] & '", em_aucmd -1 HBarOpenBar ' & i & auCRLF 
    Next
    Return text
EndFunc

Func HBarOpenBar(FileIndex)
    Local bar = gHBarBars[FileIndex]
    c = IniRead(bar, "Buttonbar", "Buttoncount", 0)
    If c = 0 Then 
       MsgBox("Файл не содержит данных о кнопках" & auCRLF & auCRLF & bar, "Autorun", 0+48)
       Return
    EndIf
    If gHBarWndProc > 0 Then       
        gHBarLoadError = 1               
        #hBarWnd = 0 
    EndIf    
    CommandExec("em_open_bar_path", bar)
EndFunc    
    
Func HBarAddBarMenu()
    Local sBars = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    #sBars = StrTrim(sBars)
    Local text
    gHBarBars.Count = 0
    gHBarBars.Text = sBars
    For i = 0 To gHBarBars.Count - 1
        text = text & 'MENUITEM "' & gHBarBars[i] & '", em_aucmd -1 HBarMergeOne ' & i & auCRLF 
    Next
    Return text
EndFunc

Func HBarMergeOne(FileIndex)
    Local bar = gHBarBars[FileIndex]
    c = IniRead(bar, "Buttonbar", "Buttoncount", 0)
    If c = 0 Then 
       MsgBox("Файл не содержит данных о кнопках" & auCRLF & auCRLF & bar, "Autorun", 0+48)
       Return
    EndIf
    If IsPressed(0x11) Then 
       CommandExec("em_open_bar_path", bar)
       Return
    Endif
    If FileGetTime(gHBarFile) <> gHBarFileTs Then
       MsgBox("Операция невозможна. Файл на диске изменился." & auCRLF & FileGetTime(gHBarFile) & "<>" & gHBarFileTs)
       Return
    Endif
    Local text =  FileRead(bar), count = gHBarMap.Count + 1
    Local rext = 'button' & count & '=' & auCRLF & _
                 'cmd' & count & '=-2' & auCRLF & 'iconic' & count & '=0' & auCRLF 
    Local i = 0
    Local rex = RegExp('(?i)(button|cmd|param|path|menu|iconic)(\d+?)=(.*?)(\R|\Z)', text)
    If rex.Exec() Then 
        Do 
           #If StrLower(rex.Match[1]) = 'button' Then i += 1
           If StrCompare(rex.Match[1], 'button') = 0 Then i += 1
           rext &= rex.Match[1] & rex.Match[2]+count & '=' & rex.Match[3] & auCRLF
        Until not rex.ExecNext()
    EndIf     
    Free(rex)
    If i < 1 Then 
       Return MsgBox("Не удалось найти ни одной кнопки" & auCRLF & auCRLF & _
                     "Buttoncount=" & c & " RegExp=" & i & auCRLF & & bar, "Autorun", 16)
    ElseIf c <> i Then
       MsgBox("Количество кнопок не совпадает" & auCRLF & auCRLF & _
              "Buttoncount=" & c & " RegExp=" & i & auCRLF & & bar, "Autorun", 48)
    EndIf 
    # сохранение панели
    Local txt
    HBarSaveState(txt)    
    txt &= auCRLF & rext
    # обновление данных
    gHBarList.Text = txt
    gHBarList.SaveToFile(gHBarFile, gHBarCP)
    IniWrite(gHBarFile, "Buttonbar", "Buttoncount", count+c)
    # обновление панели
    HBarUpdateReload()
    ShowDragHint("Добавлено " & i+1 & " кнопок")
EndFunc

Func HBarBreakMenu()
    Local text, br = List()
    For i = 0 To gHBarMap.Count - 1
        If gHBarMap[i] = -2 Then
           br.Add(i+1)
           text &= 'MENUITEM "Индекс разрыва: ' & i+1 & '", em_aucmd -1 HBarBreakErase ' & i+1 & ' ' & i+1 & auCRLF
        EndIf   
    Next
    If text = "" Then Return 'MENUITEM "Нет разрывов", em_aucmd /D -1'
    If br.Count > 1 Then
       Local r1 = br[0], r2 = 0
       text &= 'MENUITEM SEPARATOR' & auCRLF         
       For i = 1 To br.Count - 1
           r2 = br[i]
           If r2 > 0 Then
              text &= 'MENUITEM "Удалить кнопки между ' & r1 & ' - ' & r2 & '", em_aucmd -1 HBarBreakErase ' & r1 & ' ' & r2 & auCRLF
           EndIf
           r1 = r2
           r2 = 0
       Next
    EndIf
    If gHBarMap.Count > 1 Then
       text &= 'MENUITEM SEPARATOR' & auCRLF    
       text &= 'MENUITEM "Индекс удаления 1", em_aucmd -1 HBarBreakErase ' & 2 & ' ' & 2 & auCRLF
    EndIf
    Return text
EndFunc

Func HBarBreakErase(BreakIndex, StopIndex)
    Local Count = 0, txt, _
          Range = StopIndex - BreakIndex
          
    If FileGetTime(gHBarFile) <> gHBarFileTs Then
       MsgBox("Операция невозможна. Файл на диске изменился." & auCRLF & FileGetTime(gHBarFile) & "<>" & gHBarFileTs)
       Return
    Endif
    
    If BreakIndex < 2 Then
       MsgBox("Индекс разрыва не может быть меньше 2")
       Return
    Endif    
    
    Local text =  gHBarList.Text, rext = ""
    Local rex = RegExp('(?i)(button|cmd|param|path|menu|iconic)(\d+?)=(.*?)(\R|\Z)', text)    
    If rex.Exec() Then 
        Do 
           If BreakIndex = StopIndex Then
              If rex.Match[2] < BreakIndex Then
                 Count += 1
                 rext &= rex.Match[1] & rex.Match[2] & '=' & rex.Match[3] & auCRLF
              EndIf
           Else
              If rex.Match[2] < BreakIndex Then
                 Count += 1              
                 rext &= rex.Match[1] & rex.Match[2] & '=' & rex.Match[3] & auCRLF
              ElseIf rex.Match[2] >= StopIndex Then
                 Count += 1              
                 rext &= rex.Match[1] & rex.Match[2]-Range & '=' & rex.Match[3] & auCRLF                 
              EndIf
           EndIf
        Until not rex.ExecNext()
    EndIf
    Free(rex)    
    If rext = "" Then Return MsgBox("Ошибка удаления. Buttoncount=0, BreakIndex=" & BreakIndex)
    
    # сохранение панели    
    HBarSaveState(txt)
    
    # обновление файла
    Local NewCount = 0
    If BreakIndex = StopIndex Then
       NewCount = BreakIndex - 1
    Else 
       NewCount = gHBarMap.Count - Range
    EndIf
    ShowDragHint("Удалено " & gHBarMap.Count - NewCount & " кнопок")    
    gHBarList.Text = "[Buttonbar]" & auCRLF & "Buttoncount=" & NewCount & auCRLF & rext
    gHBarList.SaveToFile(gHBarFile, gHBarCP)
    
    # обновление панели    
    HBarUpdateReload()
EndFunc

#{  
    поиск индекса по ключу и значению:
    
    bar = COMMANDER_PATH & "\Bars\Main.bar"
    
    idx = FindButtonIdx(bar, "cmd", "61007")
    idx = FindButtonIdx(bar, "menu", "Auto_bars")   
    idx = FindButtonIdx(bar, "button", "WCMICONS.dll,72") 
    
    MsgBox(FindButtonIdx(bar, "param", '/ef "tcm(60011,1)"'))
#}
Func FindButtonIdx(ButtonBar, ButtonKey, ButtonVal)
    Local txt, pos, len, sub, idx = 0
    If Not FileExist(ButtonBar) Then
       ShowDragHint("Панель не найдена " & ButtonBar)
       Return 0   
    EndIf   
    len = StrLen(ButtonKey)
    ProcessExecGetOutput txt %COMSPEC% '/c type "%ButtonBar%"'
    pos = StrPos(txt, "=" & ButtonVal)
    If pos = 0 Then Return 0
    For i = 0 To 3
       sub = StrMid(txt, pos-len-i, len)
       If sub = ButtonKey Then
          idx = StrMid(txt, pos-i, i)
          If IsInt(idx) Then Break
       Endif
    Next
    # перепроверка индекса
    If idx > 0 Then
       IniRead txt %ButtonBar% "Buttonbar" %"ButtonKey & idx"
       If StrPos(txt, ButtonVal) > 0 Then Return idx
    EndIf
    Return 0
EndFunc

Func HBarSaveAs()
    If FileGetTime(gHBarFile) <> gHBarFileTs Then
       MsgBox("Операция невозможна. Файл на диске изменился." & auCRLF & FileGetTime(gHBarFile) & "<>" & gHBarFileTs)
       Return
    Endif 
    # буфер для имени файла
    Local file = Buffer(1024)
    file.Zero()
    file.SetStr(gHBarFile)

    # буфер для фильтра расширений
    Local fstr = "Bar files (*.bar)" & Chr(0) & "*.bar" & Chr(0) & Chr(0)
    Local flen = StrLen(fstr) * 2
    Local filt = Buffer(flen)
    filt.Zero()
    filt.SetStr(fstr)
    
    # буфер для структуры OPENFILENAMEW    
    Local ofn = Buffer(auX64 ? 152 : 76)
    ofn.Zero()
    ofn.SetNum(0, "UInt", ofn.size)    # lStructSize
    If auX64 Then
       ofn.SetNum(8,  "Ptr",   AUTORUN_TCHANDLE)   # hwndOwner
       ofn.SetNum(24, "Ptr",   filt.ptr)           # lpstrFilter
       ofn.SetNum(48, "Ptr",   file.ptr)           # lpstrFile
       ofn.SetNum(56, "Dword", file.size)          # nMaxFile
       ofn.SetNum(96, "Dword", 0x00000802)         # 0x00000002 | 0x00000800
    Else
       ofn.SetNum(4,  "Ptr",   AUTORUN_TCHANDLE)   # hwndOwner
       ofn.SetNum(12, "Ptr",   filt.ptr)           # lpstrFilter
       ofn.SetNum(28, "Ptr",   file.ptr)           # lpstrFile
       ofn.SetNum(32, "Dword", file.size)          # nMaxFile
       ofn.SetNum(52, "Dword", 0x00000802)         # 0x00000002 | 0x00000800       
    EndIf
    
    If DllCall("comdlg32\GetSaveFileNameW", "Ptr", ofn.ptr) Then
       Local new = file.GetStr()
       If new <> gHBarFile Then 
          gHBarList.SaveToFile(new, gHBarCP)
       Else
          MsgBox("Файл не может быть сохранен сам в себя","Autorun", 48)
       EndIf   
    EndIf
    
    Free(file, filt, ofn)
EndFunc

Func HBarDragIcon(DragIdx, DropIdx)
    Local txt
    
    # проверка индекса получателя
    If gHBarMap[DropIdx-1] <> 1 Then Return ShowDragHint("Получатель не может быть разделителем")
    
    # сохранение значка
    Local val = IniRead(gHBarFile, "Buttonbar", "button" & DragIdx) 
    If val = '' Then 
       Return ShowDragHint("Нет данных о значке button" & DragIdx)
    Else 
       If StrLeft(val, 1) ='"' And StrRight(val, 1) ='"' Then val = '"' & val & '"'  
    EndIf
    
    # сохранение панели    
    HBarSaveState(txt)
    
    # обновление значка
    IniWrite(gHBarFile, "Buttonbar", "button" & DropIdx, val)
    
    # обновление панели    
    HBarUpdateReload()
EndFunc

Func HBarDragDelete(DragIdx)
    Local txt
    Static aItems = List("button","cmd","param","path","menu","iconic")
    
    # сохранение панели    
    HBarSaveState(txt)
    
    # замена индекса удаляемой кнопки
    Local key, new
    For j = 0 To aItems.Count - 1
        key = aItems[j] & DragIdx & "="
        new = aItems[j] & 8888 & "="
        txt = StrReplace(txt, key, new)
    Next
    
    # замена ключей со сдвигом -1
    For i = DragIdx + 1 To gHBarMap.Count
        For k = 0 To aItems.Count - 1
            key = aItems[k] & i & "="
            new = aItems[k] & i - 1 & "="
            txt = StrReplace(txt, key, new)
        Next
    Next
    
    # сохранение панели
    gHBarList.Text = txt
    gHBarList.SaveToFile(gHBarFile, gHBarCP)
    
    # удаление ключей кнопки из файла
    For j = 0 To aItems.Count - 1
        key = aItems[j] & 8888
        IniDelete(gHBarFile, "Buttonbar", key)
    Next
    
    # обновление общего количества кнопок    
    IniWrite(gHBarFile, "Buttonbar", "Buttoncount", gHBarMap.Count-1)
    
    # обновление панели
    HBarUpdateReload()        
EndFunc

Func HBarAddButton(IndexType)
    Local txt
    If FileGetTime(gHBarFile) <> gHBarFileTs Then
       MsgBox("Операция невозможна. Файл на диске изменился." & auCRLF & FileGetTime(gHBarFile) & "<>" & gHBarFileTs)
       Return
    Endif    
    # сохранение панели
    HBarSaveState(txt)
    
    # добавление новых ключей
    IniWrite(gHBarFile, "Buttonbar", "button" & gHBarMap.Count+1)
    IniWrite(gHBarFile, "Buttonbar", "iconic" & gHBarMap.Count+1, 0)
    If IndexType = -2 Then
       IniWrite(gHBarFile, "Buttonbar", "cmd" & gHBarMap.Count+1, -2)
    EndIf
    
    # обновление общего количества кнопок
    IniWrite(gHBarFile, "Buttonbar", "Buttoncount", gHBarMap.Count+1)
    
    # обновление панели
    HBarUpdateReload()
    ShowDragHint("Новый индекс " & gHBarMap.Count)
EndFunc
[/cut]

Если кто-то хочет попробовать не подключая к своему ТС, можно взять готовую сборку и заменить модуль Bars.aucfg в каталоге Scripts
Ссылка 1
Ссылка 1
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9
Хотел всё-таки разобраться, в чём проблема удаления группы кнопок путём последовательного указания мышью (наведением, пусть с тем же модификатором Shift) двух крайних кнопок диапазона: ведь индексы этих кнопок определяются надёжно, а остальное кажется уже делом техники…
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а): в чём проблема удаления группы кнопок путём последовательного указания мышью
Да вроде нет больших проблем. Я просто подумал, что надо как-то визуально или графически делать выбор или через поля ввода, а с этим в Autorun сложности. Через DllCall многое можно порешать: и окна можно создать, и функции gdi вызывать, только зачем так заморачиваться :)

Благодаря вашей идее с копированием, поставленную задачу можно решить буквально за несколько секунд. Если на панели уже есть разрывы, достаточно просто скопировать один разрыв в начало диапазона, а второй разрыв в конец. После этого удалить кнопки между этими разрывами через меню. Если на панели нет разрывов, то нужно добавить один разрыв через меню и повторить описанные действия. Даже последний пункт меню, который я добавил для удаления всех кнопок после индекса 1, можно было не делать, т.к. задача тоже решается через копирование разрыва (за первый индекс) с последующим удлением всего за разрывом :D

Но если делать последовательное указание диапазона через запоминание индекса, то сделать можно, и это не должно быть сложно. Например:
[cut]

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

Global gHBarMark1 = 0, gHBarMark2 = 0

Func HBarMarkToDelete()
    Local InfoMsg
    If gHBarLoadError Or gHBarList.Count = 0 Then
       InfoMsg = "Панель не загружена"              
    ElseIf FileGetTime(gHBarFile) <> gHBarFileTs Then
       InfoMsg = "Файл панели изменился"
    Endif
    If InfoMsg <> "" Then
       ShowDragHint(InfoMsg)
       Return
    Endif
    If Not gHBarMark1 Then
       gHBarMark1 = HBarGetIdx()
       If gHBarMark1 < 1 Then Return
       MsgBox("Индекс: " & gHBarMark1 & auCRLF & auCRLF  & _       
              "Задать начало диапазона?", "Autorun", 3+64+0)
       If EXTENDED <> 6 Then gHBarMark1 = 0
       Return
    Endif
    If Not gHBarMark2 Then
       gHBarMark2 = HBarGetIdx()
       If gHBarMark2 <= gHBarMark1 Then 
          gHBarMark2 = 0
          Return
       EndIf   
       MsgBox("Индекс: " & gHBarMark2 & auCRLF & auCRLF  & _       
              "Задать конец диапазона?", "Autorun", 3+64+0)
       If EXTENDED <> 6 Then 
          gHBarMark2 = 0
          Return
       EndIf   
    Endif
    MsgBox("Диапазон: " & gHBarMark1 & "-"& gHBarMark2 & auCRLF & auCRLF  & _       
           "Удалить группу кнопок?", "Autorun", 3+64+0)
    If EXTENDED <> 6 Then 
       ShowDragHint("Диапазон сброшен")
    Else
       # удаление
       HBarBreakErase(gHBarMark1, gHBarMark2)
    EndIf
    gHBarMark1 = 0
    gHBarMark2 = 0
EndFunc
[/cut]
В самый конец функции HBarLoad нужно добавить сброс маркеров диапазона, чтобы при смене панели или состояния панели указанный наполовину диапазон сбрасывался.

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

gHBarMark1 = 0
gHBarMark2 = 0
Функцию можно повесить и на среднюю кнопку мыши, но сейчас она у меня вызывается через Alt

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

# правый клик с удержанием Alt
ControlSetMouseAction /R /K:A 28 "HBarMarkToDelete"
Навскидку всё работает, но могут быть, конечно, неучтенные моменты. Если вам понравится, можно добавить к функционалу :D
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):неучтенные моменты
Удобнее не ограничивать задание диапазона началом и концом, чтобы можно было, к примеру, начать с большего индекса, а закончить меньшим. Если оба раза указана одна кнопка, то её и удалять. Сейчас, например, при указании диапазона 67-68, удаляется только 67.

P.S.
Ну и сам интерфейс удаления сейчас такой, как будто пользователь сам помогает скрипту удалять кнопки (почти как с установкой разрывов), а не наоборот.
В идеале хотелось, чтобы была одна операция, а не две, не связанные друг с другом. Что-то нажать, получить запрос на указание первой граничной кнопки, зависнуть над ней мышью (если надо, то ещё что-то нажать дополнительно), следом получить запрос на указание второй граничной кнопки, зависнуть над ней мышью (если надо, то ещё что-то нажать дополнительно) и, наконец, подтвердить/отменить удаление.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):начать с большего индекса, а закончить меньшим
Это можно сделать через функции Min и Max. Вроде работает.
sa писал(а):Если оба раза указана одна кнопка, то её и удалять
А зачем? Одну кнопку можно удалить и так.

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

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

text &= 'MENUITEM "Индекс удаления 1", em_aucmd -1 HBarBreakErase ' & 2 & ' ' & 2 & auCRLF
где передается диапазон 2-2. Поэтому для удаления одной кнопки придется делать дополнительную функцию.

Но можно ведь и не делать. Одну кнопку можно удалить простым дропом за панель, поэтому пока оставил как было, т.е. нельзя задать диапазон равный одной кнопке.
sa писал(а):Сейчас, например, при указании диапазона 67-68, удаляется только 67.
Исправил. Там просто +1 нужно было добавить к параметру.
sa писал(а):Ну и сам интерфейс удаления сейчас такой, как будто пользователь сам помогает скрипту удалять кнопки (почти как с установкой разрывов), а не наоборот.
Да нормальный интерфейс, мне даже нравится :) Конечно, было бы лучше, если бы первая кнопка как-то отмечалась, чтобы не держать ее в уме, но и так, вроде бы, удобно: навёл на кнопку, получил подсказку, убедился, что это нужный индекс, нажал следом Alt+ПКЛ для выбора диапазона, и всё.

В справке к Autohotkey есть пример на DllCall для закрашивания области экрана.
[cut]

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

Rect := Buffer(16)  ; Set capacity to hold four 4-byte integers.
NumPut( "Int", 0                  ; left
      , "Int", 0                  ; top
      , "Int", A_ScreenWidth//2   ; right
      , "Int", A_ScreenHeight//2  ; bottom
      , Rect)
hDC := DllCall("GetDC", "Ptr", 0, "Ptr")  ; Pass zero to get the desktop's device context.
hBrush := DllCall("CreateSolidBrush", "UInt", 0x0000FF, "Ptr")  ; Create a red brush (0x0000FF is in BGR format).
DllCall("FillRect", "Ptr", hDC, "Ptr", Rect, "Ptr", hBrush)  ; Fill the specified rectangle using the brush above.
DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)  ; Clean-up.
DllCall("DeleteObject", "Ptr", hBrush)  ; Clean-up.
[/cut]
Я перевел его в Autorun с небольшой особенностью и добавил к функции.
[cut]

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

Global gHBarMark1 = 0, gHBarMark2 = 0

Func HBarMarkToDelete()
    Local InfoMsg, sMenu
    If gHBarLoadError Or gHBarList.Count = 0 Then
       InfoMsg = "Панель не загружена"              
    ElseIf FileGetTime(gHBarFile) <> gHBarFileTs Then
       InfoMsg = "Файл панели изменился"
    Endif
    If InfoMsg <> "" Then
       ShowDragHint(InfoMsg)
       Return
    Endif
    HBarCreateColor()    
    If Not gHBarMark1 Then
       gHBarMark1 = HBarGetIdx()
       If gHBarMark1 < 1 Then
          gHBarMark1 = 0
          Return
       EndIf
       IniRead sMenu %gHBarFile% "Buttonbar" %'"menu" & gHBarMark1' "<>"
       MsgBox("Индекс: " & gHBarMark1 & auCRLF & sMenu & auCRLF & auCRLF  & _       
              "Задать начало диапазона?", "Autorun", 3+64+0)
       If EXTENDED <> 6 Then gHBarMark1 = 0
       Return
    Endif
    If Not gHBarMark2 Then
       gHBarMark2 = HBarGetIdx()
       If gHBarMark2 = gHBarMark1 Then 
          gHBarMark2 = 0
          Return
       EndIf
       IniRead sMenu %gHBarFile% "Buttonbar" %'"menu" & gHBarMark2' "<>"       
       MsgBox("Индекс: " & gHBarMark2 & auCRLF & sMenu & auCRLF & auCRLF  & _       
              "Задать конец диапазона?", "Autorun", 3+64+0)
       If EXTENDED <> 6 Then 
          gHBarMark2 = 0
          Return
       EndIf   
    Endif
    Local nHBarMark1 = Min(gHBarMark1, gHBarMark2)
    Local nHBarMark2 = Max(gHBarMark1, gHBarMark2)    
    MsgBox("Диапазон: " & nHBarMark1 & "-"& nHBarMark2 & auCRLF & auCRLF  & _       
           "Удалить группу кнопок?", "Autorun", 3+64+0)
    If EXTENDED <> 6 Then 
       ShowDragHint("Диапазон сброшен")
    Else
       # удаление
       HBarBreakErase(nHBarMark1, nHBarMark2+1)
    EndIf
    gHBarMark1 = 0
    gHBarMark2 = 0
EndFunc

Func HBarCreateColor()
    MouseGetPos("x","y")
    Local buf = Buffer(16)    # RECT
    buf.Zero()
    buf.SetNum(0, "Int", x-5, _
                  "Int", y-5, _
                  "Int", x+5, _
                  "Int", y+5)    
    Local hDC  = DllCall("GetDC", "ptr", 0, "ptr")
    #Local hDC = DllCall("GetWindowDC", "ptr", hBarWnd, "ptr")  
    Local hBrush = DllCall("CreateSolidBrush", "UInt", 0x0000FF, "Ptr")
    DllCall("FillRect", "Ptr", hDC, "Ptr", buf.ptr, "Ptr", hBrush)
    DllCall("DeleteObject", "Ptr", hBrush)
    DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)    
    Free(buf)
    Sleep(100)
EndFunc
[/cut]
Сейчас намного лучше стало. Если бы маркер держался на экране, то было бы еще лучше, но заморачиваться не хочется. Ведь размер панели может измениться в любой момент, а это значит, что придется пересчитывать координаты маркера. Как по мне, оно не стоит того. И сейчас вполне удобно :D
sa писал(а):В идеале хотелось, чтобы была одна операция, а не две, не связанные друг с другом.
Надо подумать над этим. Но вы попробуйте обновленный вариант: он удобен по-своему, к нему просто привыкнуть надо.
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):нельзя задать диапазон равный одной кнопке
Ничего страшного! А то получится, что при удалении можно обойтись и без Drag&Drop :D.
Orion9 писал(а):Да нормальный интерфейс, мне даже нравится
...
Сейчас намного лучше стало.
Я ещё и подтверждения убрал (сначала все три, потом на удаление всё-таки зачем-то вернул - хотя и так имеется отмена последнего действия). Вместо первых двух стало достаточно красных квадратиков. Теперь можно при нажатом Alt щёлкнуть правой клавишей мыши по двум произвольным кнопкам панели и получить запрос на удаление – чем не одна операция?!
В окошке подтверждения удаления дополнительно указал количество кнопок в группе (Удалить группу кнопок: 12шт?)
Спасибо!
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):В окошке подтверждения удаления дополнительно указал количество кнопок в группе (Удалить группу кнопок: 12шт?)
Хорошая идея. Тоже добавил. Только сообщение немножко другое.
sa писал(а):Я ещё и подтверждения убрал
Да, теперь я понял задумку — сверхскоростное удаление! Удобно получилось :D Но, имхо, последнее сообщение я бы все же оставил, а то мало ли. К тому же, так можно сбросить диапазон, если вдруг понял, что граница неправильно выбрана. Хотя и откаты есть.

Я добавил параметр AskFor, для скрытия/показа диалога. В будущем можно будет опцию сделать, если понадобится. Но по-моему, без двух лишних сообщений работает прекрасно :D
sa писал(а):чем не одна операция?!
Согласен. Прикольно получилось и удобно.

[cut]

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

Global gHBarMark1 = 0, gHBarMark2 = 0

Func HBarMarkToDelete()
    Local InfoMsg, sMenu, AskFor = 0
    If gHBarLoadError Or gHBarList.Count = 0 Then
       InfoMsg = "Панель не загружена"              
    ElseIf FileGetTime(gHBarFile) <> gHBarFileTs Then
       InfoMsg = "Файл панели изменился"
    Endif
    If InfoMsg <> "" Then
       ShowDragHint(InfoMsg)
       Return
    Endif
    HBarCreateColor()    
    If Not gHBarMark1 Then
       gHBarMark1 = HBarGetIdx()
       If gHBarMark1 < 1 Then
          gHBarMark1 = 0
          Return
       EndIf
       If AskFor Then
          IniRead sMenu %gHBarFile% "Buttonbar" %'"menu" & gHBarMark1' "<>"
          MsgBox("Индекс: " & gHBarMark1 & auCRLF & sMenu & auCRLF & auCRLF  & _       
                 "Задать начало диапазона?", "Autorun", 3+64+0)
          If EXTENDED <> 6 Then gHBarMark1 = 0
       EndIf
       Return
    Endif
    If Not gHBarMark2 Then
       gHBarMark2 = HBarGetIdx()
       If gHBarMark2 = gHBarMark1 Then 
          gHBarMark2 = 0
          Return
       EndIf
       If AskFor Then       
          IniRead sMenu %gHBarFile% "Buttonbar" %'"menu" & gHBarMark2' "<>"       
          MsgBox("Индекс: " & gHBarMark2 & auCRLF & sMenu & auCRLF & auCRLF  & _       
                 "Задать конец диапазона?", "Autorun", 3+64+0)
          If EXTENDED <> 6 Then 
             gHBarMark2 = 0
             Return
          EndIf
       EndIf
    Endif
    Local nHBarMark1 = Min(gHBarMark1, gHBarMark2)
    Local nHBarMark2 = Max(gHBarMark1, gHBarMark2)    
    MsgBox("Диапазон: " & nHBarMark1 & "-"& nHBarMark2 & auCRLF & auCRLF  & _       
           "Удалить группу из " & nHBarMark2 - nHBarMark1 + 1 & " кнопок?", "Autorun", 3+64+0)
    If EXTENDED <> 6 Then 
       ShowDragHint("Диапазон сброшен")
    Else
       # удаление
       HBarBreakErase(nHBarMark1, nHBarMark2+1)
    EndIf
    gHBarMark1 = 0
    gHBarMark2 = 0
EndFunc
[/cut]
[cut]Изображение[/cut]
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):Только сообщение немножко другое.
Удалить группу из 21 кнопок?
Не очень звучит…
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):Не очень звучит…
Да, возможно. С формулировками у меня всегда были проблемы. Но это мелочь, всегда можно поправить :)

Сейчас, как я писал выше, придется взять небольшую паузу, но функционал рано считать законченным. Возможно, к Drag&Drop уже добавить нечего, но с кнопками и панелями еще можно придумать интересные операции.

Важный момент. Каждый раз напоминал себе об этом написать и каждый раз забывал, возможно, многие знают, но стоит еще раз упомянуть. Операции Drag&Drop приводят к замене ключей внутри файла панели. Если заменить первую кнопку на последнюю, то физически последней она не станет, поменяется только её индекс. Чем больше таких операций, тем больше путаницы в файле панели. Чтобы привести всё в порядок, нужно просто открыть диалог изменения панели и нажать ОК. Тотал сам отсортирует все ключи и выкинет лишний мусор, если он там есть.

В общем, переодически нужно это делать, чтобы держать панель в хорошем состоянии :D
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):нужно просто открыть диалог изменения панели и нажать ОК
У меня для этого в меню запуска заведена команда с использованием TCFS2:

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

/ef "tcm(498,1) eval(settext_wait(,findwnd(`TBUTTONCHANGEDLG`))) sendmsg(findwnd(`TButton`,,findwnd(`TBUTTONCHANGEDLG`),3),$F5) sendmsg(findwnd(`Button`,,findwnd(`TBUTTONCHANGEDLG`),6),$F5)"
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):команда с использованием TCFS2
Можно такое на Autorun повторить. Есть шаблон :)

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

Local b, i, h = 0
PostMessage(AUTORUN_TCHANDLE, 0x433, 519)
# ожидание окна 
For i = 1 To 75
    h = WinFind(0, "TDlgCustomColors")
    If h > 0 Then Break   
    Sleep(20)
Next
If h > 0 Then   
   b = WinFind(h, (AUTORUN_TCARCH = 32 ? "TButton" : "Button"), (AUTORUN_TCARCH = 32 ? 3 : 2))
   SendMessage(b, 0xF5, 0, 0)
Else
   ShowHint("Окно не найдено") 
   Return  
Endif
sa
Сообщения: 2758
Зарегистрирован: Ср апр 29, 2009 18:56

Сообщение sa »

Orion9 писал(а):Есть шаблон
У меня шаблон стал нормально отрабатывать сохранение панели после замены SendMessage на PostMessage.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

sa писал(а):У меня шаблон стал нормально отрабатывать сохранение панели после замены SendMessage на PostMessage.
Да, наверное, так и есть. Шаблон был плохо протестирован, хотя он работал.

В справке говорится, что BM_CLICK может иногда не срабатывать, если окно неактивно (не успело стать активным)

[cut]Remarks

If the button is in a dialog box and the dialog box is not active, the BM_CLICK message might fail. To ensure success in this situation, call the SetActiveWindow function to activate the dialog box before sending the BM_CLICK message to the button.[/cut]
Но болван, в целом, рекомендует использовать PostMessage:

[cut]Для отправки сообщения BM_CLICK кнопке лучше использовать PostMessage, а не SendMessage. [1]

Главная причина заключается в том, что BM_CLICK заставляет кнопку визуально переключаться (анимировать нажатие) и генерировать событие WM_COMMAND с кодом BN_CLICKED для своего родительского окна. Если использовать SendMessage, ваша программа будет полностью заблокирована до тех пор, пока родительское окно чужого приложения не завершит всю цепочку обработки этого клика.

Сравнение PostMessage и SendMessage для BM_CLICK

PostMessage (Рекомендуется)
Принцип работы: Бросает сообщение в очередь и сразу возвращает управление.
Многопоточность: Безопасно. Не блокирует ваш поток, даже если цель зависла.
Поведение UI: Кнопка корректно и плавно отрисовывает анимацию клика.
Синхронизация: Выполняется асинхронно (в порядке очереди Windows).

SendMessage (Не рекомендуется)
Принцип работы: Вызывает оконную процедуру напрямую и ждет завершения.
Многопоточность: Риск deadlock (взаимной блокировки), если целевое окно не отвечает.
Поведение UI: Анимация может "залипнуть" или проскочить слишком быстро.
Синхронизация: Выполняется синхронно (немедленно).

Исключение из правил

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

Пример правильного вызова (C++)

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

// Находим хэндл кнопки (например, кнопка "ОК")
HWND hButton = FindWindowEx(hParentWnd, NULL, L"Button", L"OK");
if (hButton != NULL) {
    // Отправляем асинхронный клик — безопасно и надежно
    PostMessage(hButton, BM_CLICK, 0, 0);
}
[/cut]
Кстати, что делать, если PostMessage не помог (текст неформатирован)

[cut]При автоматизации чужого окна (инжекте клика) отправка сообщения BM_CLICK через PostMessage — это классический и самый скрытный подход. Однако в современных версиях Windows сторонние приложения часто защищаются от таких действий.
Если целевое окно не реагирует на BM_CLICK, используйте проверенные альтернативные методы, описанные ниже.
## Метод 1: Прямая отправка WM_COMMAND родительскому окну
Иногда чужая кнопка игнорирует сообщения, отправленные напрямую ей. Вместо этого можно сымитировать поведение самой кнопки и отправить сообщение WM_COMMAND напрямую её родительскому окну.
Для этого вам нужно знать ID кнопки (Control ID), который можно узнать через утилиты Spy++ или GetDlgCtrlID.

HWND hParent = FindWindow(NULL, L"Заголовок Чужого Окна");HWND hButton = FindWindowEx(hParent, NULL, L"Button", L"ОК");
if (hButton && hParent) {
WORD buttonId = GetDlgCtrlID(hButton);
// Собираем wParam: BN_CLICKED (0) в старшем слове, ID в младшем
WPARAM wParam = MAKEWPARAM(buttonId, BN_CLICKED);

// Отправляем родителю, как будто кнопка сама сообщила о клике
PostMessage(hParent, WM_COMMAND, wParam, (LPARAM)hButton);
}

## Метод 2: Симуляция клика мыши через WM_LBUTTONDOWN / UP
Если элемент является кастомной кнопкой (не класса "Button"), он не поймет BM_CLICK. В таком случае отправьте последовательность нажатия и отпускания левой кнопки мыши по локальным координатам элемента.

// Координаты клика (например, центр кнопки X=50, Y=15)// Упаковываем координаты в LPARAM (Y в старшее слово, X в младшее)LPARAM lParam = MAKELPARAM(50, 15);
// Нажимаем и отпускаем
PostMessage(hButton, WM_LBUTTONDOWN, MK_LBUTTON, lParam);
PostMessage(hButton, WM_LBUTTONUP, 0, lParam);

## Метод 3: Глобальный ввод через SendInput (Самый надежный)
Если приложение защищено (например, игра или программа с протектором) и полностью игнорирует сообщения PostMessage, Windows блокирует программный ввод на уровне оконных очередей. Поможет только SendInput, который имитирует физическое движение мыши на аппаратном уровне.
Минус: Этот метод требует, чтобы чужое окно было активно и находилось на переднем плане.

// 1. Активируем окно и выводим на передний план
SetForegroundWindow(hParent);
// 2. Получаем координаты центра кнопки на экранеRECT rect;
GetWindowRect(hButton, &rect);int x = rect.left + (rect.right - rect.left) / 2;int y = rect.top + (rect.bottom - rect.top) / 2;
// 3. Перемещаем курсор физически
SetCursorPos(x, y);
// 4. Генерируем аппаратный кликINPUT inputs[2] = {};

inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;

inputs[1].type = INPUT_MOUSE;
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;

SendInput(2, inputs, sizeof(INPUT));

## Почему инжект может не работать? (Чек-лист отладки)

1. Разные уровни прав (UIPI): Если чужая программа запущена от имени Администратора, а ваша утилита автоматизации — от обычного пользователя, Windows заблокирует любые PostMessage/SendMessage. Запустите вашу программу от Администратора.
2. Защита UI Automation: Некоторые современные приложения (WPF, Electron, UWP) не используют стандартные Win32-кнопки. Для них BM_CLICK бесполезен. Для таких окон лучше использовать стандарт Microsoft UI Automation (интерфейс InvokePattern).[/cut]

В прошлый раз не успел сделать, но в меню открытия панелей явно не помешает отдельный пункт для возврата на главную панель из wincmd.ini. Это затрагивает только одну функцию.

[cut]

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

Func HBarOpenBarMenu()
    Local sText, sMain
    Local sBars = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    
    # ключ wincmd.ini   
    IniRead /R sMain %COMMANDER_INI% "Buttonbar" "Buttonbar" "%COMMANDER_PATH%\DEFAULT.BAR"
    
    # раскрытие окружения
    sMain = Set(sMain)
    
    # путь в ключе не указан
    If FileGetDir(sMain) = "" Then sMain = COMMANDER_PATH & "" & sMain

    # пункты меню 
    gHBarBars.Count = 0
    gHBarBars.Text = sMain & auCRLF & sBars
    
    For i = 0 To gHBarBars.Count - 1
        If i = 1 Then sText &= 'MENUITEM SEPARATOR' & auCRLF
        sText &= 'MENUITEM "' & gHBarBars[i] & '", em_aucmd -1 HBarOpenBar ' & i & auCRLF 
    Next    
    Return sText
EndFunc
[/cut]
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

Планирую добавить к меню открытия панелей пункт для открытия и загрузки иконок из .dll и .icl файлов. Пока сделал только диалог, но он довольно универсальный, может кому-нибудь пригодится.

[cut]

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

Func OpenFileDialog(FileName, Filter)
    Local sFile
    
    # буфер для имени файла
    Local file = Buffer(1024)
    file.Zero()
    file.SetStr(FileName)

    # буфер для фильтра расширений
    Local mask = Buffer(StrLen(Filter) * 2)
    mask.Zero()
    mask.SetStr(Filter)
    
    # буфер для структуры OPENFILENAMEW    
    Local ofn = Buffer(auX64 ? 152 : 76)
    ofn.Zero()
    ofn.SetNum(0, "UInt", ofn.size)                          # lStructSize
       
    ofn.SetNum(auX64 ? 8  : 4,  "Ptr",   AUTORUN_TCHANDLE)   # hwndOwner
    ofn.SetNum(auX64 ? 24 : 12, "Ptr",   mask.ptr)           # lpstrFilter
    ofn.SetNum(auX64 ? 48 : 28, "Ptr",   file.ptr)           # lpstrFile
    ofn.SetNum(auX64 ? 56 : 32, "Dword", file.size)          # nMaxFile
    ofn.SetNum(auX64 ? 96 : 52, "Dword", 0x00000802)         # 0x00000002 | 0x00000800
    
    If DllCall("comdlg32\GetOpenFileNameW", "Ptr", ofn.ptr) Then
       sFile = file.GetStr()
    EndIf
    
    Free(file, mask, ofn)
    Return sFile
EndFunc
[/cut]
Пример использования

[cut]

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

Func HBarOpenIcons()
    Local flt = "Icons (*.dll *.icl *.exe)" & Chr(0) & "*.dll;*.icl;*.exe" & Chr(0) & Chr(0)
    Local file = OpenFileDialog(COMMANDER_PATH & "\WCMICONS.DLL", flt)
    If file <> "" Then
       nIcons = DllCall("shell32\ExtractIconExW", "wstr", file, "int", -1, "ptr", 0, "ptr", 0, "uint", 0, "uint")
       MsgBox(file & auCRLF & auCRLF & "Иконок: " & nIcons & " шт.")
    EndIf
EndFunc

Func HBarOpenBarMenu()
    Local sText, sMain
    Local sBars = FileFind(COMMANDER_PATH, "*.bar", -1, 1)
    
    # ключ wincmd.ini   
    IniRead /R sMain %COMMANDER_INI% "Buttonbar" "Buttonbar" "%COMMANDER_PATH%\DEFAULT.BAR"
    
    # раскрытие окружения
    sMain = Set(sMain)
    
    # путь в ключе не указан
    If FileGetDir(sMain) = "" Then sMain = COMMANDER_PATH & "\" & sMain

    # пункты меню 
    gHBarBars.Count = 0
    gHBarBars.Text = sMain & auCRLF & sBars
    
    For i = 0 To gHBarBars.Count - 1
        If i = 1 Then sText &= 'MENUITEM SEPARATOR' & auCRLF
        sText &= 'MENUITEM "' & gHBarBars[i] & '", em_aucmd -1 HBarOpenBar ' & i & auCRLF 
    Next
    sText = 'MENUITEM "Открыть...", em_aucmd -1 HBarOpenIcons' & auCRLF & sText
    Return sText
EndFunc
[/cut]
Ответить