Что нового

Как корректно произвести считывание и восстановление координат и размеров окна

pvnn

Осваивающий
Сообщения
305
Репутация
32
При закрытии программы считываю позицию и размеры окна функцией WinGetPos в ini-файл.
При старте программы, при создании окна считываю параметры формы из ini-файла.
Все просто, но после каждого перезапуска значения Width и Height постоянно увеличиваются. Например так:
Width:456->462->468...итд - каждый раз увеличивается на 6
Height:275->300->325...итд - каждый раз увеличивается на 25
Что я не так делаю? Как это можно исправить?
Спасибо

Код:
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

$IniSetUp=@ScriptDir&'\conf.ini'


; Ini
 $guiwidth=IniRead($IniSetUp,'GUI','guiwidth',450)
 $guiheight=IniRead($IniSetUp,'GUI','guiheight',250)
 $guileft=IniRead($IniSetUp,'GUI','guileft',240)
 $guitop=IniRead($IniSetUp,'GUI','guitop',230)





$Form1 = GUICreate("Form1", $guiwidth, $guiheight, $guileft, $guitop)
GUISetState(@SW_SHOW)

While 1
	$nMsg = GUIGetMsg()
	Switch $nMsg
		Case $GUI_EVENT_CLOSE
			WriteGuiPos()
			Exit
	EndSwitch
WEnd

; Записать размеры окна и контролов в ini-файл для последующего восстановления
Func WriteGuiPos()
	$aGUIPos=WinGetPos($Form1)   ; Получает позицию и размеры указанного окна
	IniWrite($IniSetUp,'GUI','guiwidth',$aGUIPos[2])  	 
	IniWrite($IniSetUp,'GUI','guiheight',$aGUIPos[3])	 
	IniWrite($IniSetUp,'GUI','guileft',$aGUIPos[0])
	IniWrite($IniSetUp,'GUI','guitop',$aGUIPos[1])
	Return
EndFunc
 

InnI

AutoIT Гуру
Сообщения
4,912
Репутация
1,429
Дело в том, что в GuiCreate() используются размеры и координаты клиентской области окна, к которой добавляются заголовок и рамка. И вот эти общие размеры и координаты возвращает WinGetPos(). Поэтому при сохранении/загрузке нужно учитывать эту разницу.
Код:
_WinAPI_GetSystemMetrics()
 
Автор
P

pvnn

Осваивающий
Сообщения
305
Репутация
32
InnI Что-то не совсем понятно как применять _WinAPI_GetSystemMetrics() с окнами.
Можно на примере показать
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
pvnn,
А так?
Код:
#include <GUIConstantsEx.au3>

Global $sIniSetUp = @ScriptDir & '\conf.ini', $aDefGuiPos[4] = [450, 250, 240, 230]

$iGuiwidth = IniRead($sIniSetUp, 'GUI', 'guiwidth', $aDefGuiPos[0])
$iGuiheight = IniRead($sIniSetUp, 'GUI', 'guiheight', $aDefGuiPos[1])
$iGuileft = IniRead($sIniSetUp, 'GUI', 'guileft', $aDefGuiPos[2])
$iGuitop = IniRead($sIniSetUp, 'GUI', 'guitop', $aDefGuiPos[3])

$hGui = GUICreate('Form1', $iGuiwidth, $iGuiheight, $iGuileft, $iGuitop)
GUISetState()

While 1
	Switch GUIGetMsg()
		Case $GUI_EVENT_CLOSE
			WriteGuiPos($hGui, $sIniSetUp)
			Exit
	EndSwitch
WEnd

Func WriteGuiPos($h_Gui, $s_IniFile)
	Local $a_Client = WinGetClientSize($h_Gui)
	Local $a_GUIPos = WinGetPos($h_Gui)

	If $a_Client[0] And $a_Client[1] Then
		IniWrite($s_IniFile, 'GUI', 'guiwidth', $a_Client[0])
		IniWrite($s_IniFile, 'GUI', 'guiheight', $a_Client[1])
	Else
		IniWrite($s_IniFile, 'GUI', 'guiwidth', $aDefGuiPos[0])
		IniWrite($s_IniFile, 'GUI', 'guiheight', $aDefGuiPos[1])
	EndIf
	If $a_GUIPos[0] < 0 Or $a_GUIPos[1] < 0 Then
		IniWrite($s_IniFile, 'GUI', 'guileft', $aDefGuiPos[2])
		IniWrite($s_IniFile, 'GUI', 'guitop', $aDefGuiPos[3])
	Else
		IniWrite($s_IniFile, 'GUI', 'guileft', $a_GUIPos[0])
		IniWrite($s_IniFile, 'GUI', 'guitop', $a_GUIPos[1])
	EndIf
EndFunc   ;==>WriteGuiPos
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Автор
P

pvnn

Осваивающий
Сообщения
305
Репутация
32
madmasles спасибо большое за пример
Creator спасибо за ссылку
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Я поменял код с учетом замечания CreatoR`a.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
pvnn
Сохранение позиции окна - учитывает и разрешение экрана, если оно изменится, то окно появится в любом случае в видимой области прилегая к той части экрана, где оно должно было быть. При закрытии окна свёрнутым сохраняются последние позиции. Учитывается рабочая область экрана, то есть окно не перекрывает панель задач.

Через 5 минут обновил. Я существующий баг исправил. Если панель задач слева или сверху, то был сдвиг окна при каждом запуске. Теперь исправил чтобы было идеально.
 
Автор
P

pvnn

Осваивающий
Сообщения
305
Репутация
32
madmasles посмотрел твое решение, как самое простое.
К сожалению если на форме есть меню то высота снова "скачет"
Другие решения пока не смотрел...

Код:
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

Global $sIniSetUp = @ScriptDir & '\conf.ini', $aDefGuiPos[4] = [450, 250, 240, 230]

$iGuiwidth = IniRead($sIniSetUp, 'GUI', 'guiwidth', $aDefGuiPos[0])
$iGuiheight = IniRead($sIniSetUp, 'GUI', 'guiheight', $aDefGuiPos[1])
$iGuileft = IniRead($sIniSetUp, 'GUI', 'guileft', $aDefGuiPos[2])
$iGuitop = IniRead($sIniSetUp, 'GUI', 'guitop', $aDefGuiPos[3])

$hGui = GUICreate('Form1', $iGuiwidth, $iGuiheight, $iGuileft, $iGuitop)
$MenuItem1 = GUICtrlCreateMenu("File")
GUISetState()

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            WriteGuiPos($hGui, $sIniSetUp)
            Exit
    EndSwitch
WEnd

Func WriteGuiPos($h_Gui, $s_IniFile)
    Local $a_Client = WinGetClientSize($h_Gui)
    Local $a_GUIPos = WinGetPos($h_Gui)

    If $a_Client[0] And $a_Client[1] Then
        IniWrite($s_IniFile, 'GUI', 'guiwidth', $a_Client[0])
        IniWrite($s_IniFile, 'GUI', 'guiheight', $a_Client[1])
    Else
        IniWrite($s_IniFile, 'GUI', 'guiwidth', $aDefGuiPos[0])
        IniWrite($s_IniFile, 'GUI', 'guiheight', $aDefGuiPos[1])
    EndIf
    If $a_GUIPos[0] < 0 Or $a_GUIPos[1] < 0 Then
        IniWrite($s_IniFile, 'GUI', 'guileft', $aDefGuiPos[2])
        IniWrite($s_IniFile, 'GUI', 'guitop', $aDefGuiPos[3])
    Else
        IniWrite($s_IniFile, 'GUI', 'guileft', $a_GUIPos[0])
        IniWrite($s_IniFile, 'GUI', 'guitop', $a_GUIPos[1])
    EndIf
EndFunc   ;==>WriteGuiPos
 

InnI

AutoIT Гуру
Сообщения
4,912
Репутация
1,429
pvnn [?]
если на форме есть меню
После GUISetState() сосчитайте разницу
Код:
$aClient = WinGetClientSize($hGui)
$delta = $iGuiheight - $aClient[1]
и при сохранении добавьте эту разницу
Код:
IniWrite($s_IniFile, 'GUI', 'guiheight', $a_Client[1] + $delta)
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
pvnn
Вы будете постоянно наступать на очередные грабли перечисленные в моём сообщении.
1. Проверьте закрытие окна свёрнутым с панели задач. Какой размер окна он возвращает, если окна нет? "-32000". Следующий запуск окна уже не найти, потому что оно за экраном. Значит перед сохранением надо вызвать WinGetState, чтобы проверить состояние окна, свёрнуто или нет. И что делать если свёрнуто, не ужели не сохранять? Пользователь будет в недоумении.
2. Представьте что утилита на флешке и вы переносите с одного компьютера на другой? В одном монитор 27 дюймов, в во втором 19. Переместите окно на мониторе 27 дюймов к правому краю, закройте окно и попробуйте запустить на мониторе 19 дюймов. Окно будет, только до него не добраться. А есть ещё нетбуки с меньшим разрешением. При запуске проверяйте размер экрана и если разница между горизонтальной координатой и шириной окна больше чем разрешение экрана по ширине, то окно будет не видимым совсем.
3. Координаты хранить лучше в массиве, как это делают все функции WinGetPos, WinGetClientSize.
4. Эти метрики позволят избавится от сдвига на некоторое число пискселов, которые зависят от темы, определяющей ширину рамки окна.
Код:
_WinAPI_GetSystemMetrics(32) ; ширина вертикальной границы окна
_WinAPI_GetSystemMetrics(4) ; высота заголовка
_WinAPI_GetSystemMetrics(33) ; высота горизонтальной границы окна
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AZJIO [?]
Вы будете постоянно наступать на очередные грабли перечисленные в моём сообщении.
Я всё таки настаиваю чтобы это не осталось без внимания, на будущее как минимум.

Пункты 2 и 4 обновлю, а вот с третьим ещё можно поспорить, т.к очень часто координаты и размер главного окна используются в других целях, а хранение в массиве только путает.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Можно попробовать сделать это с помощью API. Примерно так.
Код:
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

Opt('MustDeclareVars', 1)

Global $hGui

If Not _StartPosGUICreate($hGui, 'Test', $WS_OVERLAPPEDWINDOW) Then GUISetState()

OnAutoItExitRegister('_SavePosGui')

While 1
	Switch GUIGetMsg()
		Case $GUI_EVENT_CLOSE
			Exit
	EndSwitch
WEnd

Func _StartPosGUICreate(ByRef $h_Wnd, $s_Title, $i_Style = -1, $i_ExStyle = -1, $h_Parent = 0)
	Local $ai_Pos[4][2] = [['GuiLeft', -1],['GuiTop', -1],['GuiRight', 450],['GuiBottom', 250]], _
			$s_Ini = @ScriptDir & '\conf.ini', $t_WinPlace, $p_WinPlace, $a_Res

	$h_Wnd = GUICreate($s_Title, $ai_Pos[2][1], $ai_Pos[3][1], $ai_Pos[0][1], $ai_Pos[1][1], $i_Style, $i_ExStyle, $h_Parent)
	For $i = 0 To 3
		$ai_Pos[$i][1] = IniRead($s_Ini, 'GUI', $ai_Pos[$i][0], 'Error')
		If $ai_Pos[$i][1] = 'Error' Then Return False
		$ai_Pos[$i][1] = Int($ai_Pos[$i][1])
	Next
	;Здесь надо делать проверку на корректность данных?
	; _WinAPI_GetWindowPlacement:
	$t_WinPlace = DllStructCreate('UINT length; UINT flags; UINT showCmd; int ptMinPosition[2]; int ptMaxPosition[2]; int rcNormalPosition[4]');$tagWINDOWPLACEMENT
	DllStructSetData($t_WinPlace, 'length', DllStructGetSize($t_WinPlace))
	$p_WinPlace = DllStructGetPtr($t_WinPlace)
	$a_Res = DllCall('user32.dll', 'bool', 'GetWindowPlacement', 'hwnd', $h_Wnd, 'ptr', $p_WinPlace)
	If @error Or Not $a_Res[0] Then Return False
	DllStructSetData($t_WinPlace, 'showCmd', @SW_SHOW)
	For $i = 1 To 4
		DllStructSetData($t_WinPlace, 'rcNormalPosition', $ai_Pos[$i - 1][1], $i)
	Next
	;_WinAPI_SetWindowPlacement:
	$a_Res = DllCall('user32.dll', 'bool', 'SetWindowPlacement', 'hwnd', $h_Wnd, 'ptr', $p_WinPlace)
	If @error Or Not $a_Res[0] Then Return False
	Return True
EndFunc   ;==>_StartPosGUICreate

Func _SavePosGui()
	Local $ai_Pos[4][2] = [['GuiLeft', 'Error'],['GuiTop', 'Error'],['GuiRight', 'Error'],['GuiBottom', 'Error']], _
			$s_Ini = @ScriptDir & '\conf.ini', $t_WinPlace, $a_Res
	; _WinAPI_GetWindowPlacement:
	$t_WinPlace = DllStructCreate('UINT length; UINT flags; UINT showCmd; int ptMinPosition[2]; int ptMaxPosition[2]; int rcNormalPosition[4]');$tagWINDOWPLACEMENT
	DllStructSetData($t_WinPlace, 'length', DllStructGetSize($t_WinPlace))
	Do
		$a_Res = DllCall('user32.dll', 'bool', 'GetWindowPlacement', 'hwnd', $hGui, 'ptr', DllStructGetPtr($t_WinPlace))
		If @error Or Not $a_Res[0] Then ExitLoop
		For $i = 1 To 4
			$ai_Pos[$i - 1][1] = DllStructGetData($t_WinPlace, 'rcNormalPosition', $i)
		Next
	Until 1
	For $i = 0 To 3
		IniWrite($s_Ini, 'GUI', $ai_Pos[$i][0], $ai_Pos[$i][1])
	Next
EndFunc   ;==>_SavePosGui
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Вот пример как раз без всяких API (AutoIt-нативно), учитываются многие нюансы, но конечно не все, но думаю этого вполне достаточно для большинства случаев:

Код:
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

;Отключаем обработку событии $GUI_EVENT_MINIMIZE, $GUI_EVENT_RESTORE и $GUI_EVENT_MAXIMIZE
;Это нам придётся делать самим, т.к нам нужно получать размеры окна ДО его сворачивания
Opt('GUIEventOptions', 1)
OnAutoItExitRegister('_OnExit')

Global $sConfig_File                = @ScriptDir & '\' & StringTrimRight(@ScriptName, 4) & '_Config.ini'

Global $aGUI_Last_Pos

Global $iDef_GUILeft                = -1
Global $iDef_GUITop                 = -1
Global $iDef_GUIWidth               = 700
Global $iDef_GUIHeight              = 550

Global $iGUI_Left                   = IniRead($sConfig_File, 'Window Settings', 'Left', $iDef_GUILeft)
Global $iGUI_Top                    = IniRead($sConfig_File, 'Window Settings', 'Top', $iDef_GUITop)
Global $iGUI_Width                  = IniRead($sConfig_File, 'Window Settings', 'Width', $iDef_GUIWidth)
Global $iGUI_Height                 = IniRead($sConfig_File, 'Window Settings', 'Height', $iDef_GUIHeight)

_GUIFixPos()

$hMain_GUI = GUICreate('Remember GUI Size Demo', $iGUI_Width, $iGUI_Height, $iGUI_Left, $iGUI_Top, BitOR($GUI_SS_DEFAULT_GUI, $WS_SIZEBOX))
GUISetState(@SW_SHOW, $hMain_GUI)

While 1
    $nMsg = GUIGetMsg()
    
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
        Case $GUI_EVENT_MINIMIZE
            ;Получаем размеры окна ДО его сворачивания
            $aGUI_Last_Pos = _WinGetPos($hMain_GUI)
            
            ;Теперь можно и свернуть окно :), делаем вручную, т.к в начале мы отключили обработку этого события
            GUISetState(@SW_MINIMIZE, $hMain_GUI)
        Case $GUI_EVENT_RESTORE
            ;Восстанавливаем окно
            GUISetState(@SW_RESTORE, $hMain_GUI)
        Case $GUI_EVENT_MAXIMIZE
            ;Разворачиваем окно
            GUISetState(@SW_MAXIMIZE, $hMain_GUI)
    EndSwitch
WEnd

Func _GUIFixPos()
	If $iGUI_Left = -1 Or $iGUI_Left = '' Or $iGUI_Left < -$iGUI_Width Or $iGUI_Left > (@DesktopWidth - 50) Then
		$iGUI_Left = $iDef_GUILeft
	EndIf
	
	If $iGUI_Top = -1 Or $iGUI_Top = '' Or $iGUI_Top < -$iGUI_Height Or $iGUI_Top > (@DesktopHeight - 50) Then
		$iGUI_Top = $iDef_GUITop
	EndIf
	
	If $iGUI_Width > (@DesktopWidth - 50) Then
		$iGUI_Width = $iDef_GUIWidth
	EndIf
	
	If $iGUI_Height > (@DesktopHeight - 50) Then
		$iGUI_Height = $iDef_GUIHeight
	EndIf
EndFunc

Func _WinGetPos($hWin)
	Local $aWin_Pos = WinGetPos($hWin)
	If @error Then Return SetError(1)
	
	Local $aClient_Size = WinGetClientSize($hWin)
	If @error Then Return SetError(2)
	
	$aWin_Pos[2] = $aClient_Size[0]
	$aWin_Pos[3] = $aClient_Size[1]
	
	Return $aWin_Pos
EndFunc

Func _OnExit()
    If Not IsArray($aGUI_Last_Pos) Then
        $aGUI_Last_Pos = _WinGetPos($hMain_GUI)
    EndIf
    
    If UBound($aGUI_Last_Pos) = 4 Then
        IniWrite($sConfig_File, 'Window Settings', 'Left', $aGUI_Last_Pos[0])
        IniWrite($sConfig_File, 'Window Settings', 'Top', $aGUI_Last_Pos[1])
        IniWrite($sConfig_File, 'Window Settings', 'Width', $aGUI_Last_Pos[2])
        IniWrite($sConfig_File, 'Window Settings', 'Height', $aGUI_Last_Pos[3])
    EndIf
EndFunc
 
Автор
P

pvnn

Осваивающий
Сообщения
305
Репутация
32
Тема решена

InnI спасибо за сосчитайте разницу - помогло
Решения CreatoR и AZJIO использую в доработанном скрипте, спасибо!
Спасибо madmasles за вариант решения с помощью API

Вообще не ожидал, что так быстро все ответят...
Еще раз Всем Огромное спасибо!
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
CreatoR [?]
а вот с третьим ещё можно поспорить, т.к очень часто координаты и размер главного окна используются в других целях, а хранение в массиве только путает.
Разве массив является препятствием? Например имя переменной $WHXY уже говорит о том что первые два элемента ширина и высота, а вторые два координаты. Легко ориентироваться, а если использовать имена будет 4 длинных имени. Я сейчас хочу добавить эту функцию _SetCoor в UserGUI.au3 (и в справочный файл) и думаю выложить с 4 имеющимися функциями, и хочу оставить передачу данных в виде массива, а не 4-х параметров.
 
Верх