النافذة تفتح شبه مكبرة، وزر Restore يعيدها إلى المساحة نفسها. تعديل الأرقام مرة واحدة لا يكفي، لأن إغلاق النافذة وهي كبيرة يعيد كتابة تلك الأرقام. الحل من البداية: تثبيت حجم عادي، ثم تشغيل حماية داخل حسابك ترفض أن يُحتسب الإطار الكبير حجمًا للاستعادة.
الأعراض
هذا سلوك شائع في ويندوز 11، وأضعف مما كان عليه حفظ النافذة في ويندوز 10 وويندوز 7:
- تفتح نافذة مستكشف الملفات بحجم يغطي سطح المكتب تقريبًا، وزر التحكم الأوسط يظهر كزر استعادة.
- الضغط على Restore يترك النافذة كبيرة، لأن «الحجم العادي» المحفوظ يساوي مساحة العمل أو إطار النافذة المكبرة.
- إذا صغّرت النافذة يدويًا وأغلقتها وهي غير مكبرة، تتذكر النافذة التالية هذا الحجم.
- إذا كبّرتها ثم أغلقتها وهي كبيرة، يُستبدل الحجم الجيد بالإطار الكبير. هنا تعود المشكلة حتى بعد تصحيح السجل يدويًا.
- بعد إعادة التشغيل، خصوصًا على لابتوب مع شاشة خارجية، قد تُفتح النافذة بالحجم الكبير مرة أخرى.
لماذا يفشل Restore، ولماذا تعود المشكلة
مستكشف الملفات يحفظ حجم آخر نافذة أُغلقت وموضعها، لا حجمًا مستقلًا لكل مجلد. مكان الحفظ في حسابك:
HKEY_CURRENT_USERSoftwareClassesLocal SettingsSoftwareMicrosoftWindowsShellBagsAllFoldersShell
لكل ترتيب شاشات أربع قيم:
WinPos1920x1080x96(1).left WinPos1920x1080x96(1).top WinPos1920x1080x96(1).right WinPos1920x1080x96(1).bottom
- دقة الشاشة الأساسية مثل
1920x1080. شاشتان بدقة 1920×1080 تبقيان بهذا الاسم، ولا يصبح 3840×1080. - رقم DPI لنسبة التكبير: 100% = 96، و125% = 120، و150% = 144، و175% = 168، و200% = 192.
- الرقم بين القوسين مثل
(1)أو(2)يفرّق بين ترتيبات الشاشات التي رآها ويندوز. مع شاشتين تُستخدم الخانة(2). الخانة(1)تُستخدم عندما يرى ويندوز شاشة واحدة، ومن ذلك لحظة الإقلاع قبل أن تجهز الشاشة الخارجية.
القيم الأربع هي أطراف نافذة الاستعادة بالبكسل. زر Restore يعرض هذا المستطيل كما هو. عندما يساوي المستطيل مساحة العمل، أو إطار النافذة وهي مكبرة، تبقى النافذة كبيرة.
الإطار المتقطع مساحة العمل. الإطار الداخلي آخر حجم عادي يجب أن يبقى محفوظًا لزر Restore.
مساحة الشاشة الأولى: 0,0 → 1920,1032. هذا مستطيل يجعل Restore بلا أثر.
بعد التكبير على الشاشة الثانية كُتب إطار مكبر فعلًا: 1911,3 → 3847,1051، أي أكبر من الشاشة ببضعة بكسلات بسبب الحد غير المرئي.
آخر حجم عادي كان 155,305 → 1471,870.
قيمة ShowCmd واحدة بجانب هذه الأطراف:
| القيمة | معنى الفتح التالي |
|---|---|
| 1 | فتح بالحجم المحفوظ في WinPos |
| 3 | فتح مكبر، مع استخدام WinPos عند الضغط على Restore |
في ويندوز 10 كان إغلاق النافذة وهي مكبرة يُبقي مستطيل الاستعادة السابق. في ويندوز 11 يُكتب الإطار الحالي داخل WinPos. كثيرًا ما تبقى ShowCmd على 1، فتفتح النافذة التالية كبيرة من الأصل، ولا يجد Restore حجمًا أصغر يعود إليه.
لذلك تصحيح الأرقام مرة واحدة يُصلح الفتح الحالي فقط. الإغلاق التالي والنافذة مكبرة يكتب الإطار الكبير من جديد، على خانة الشاشتين أو على خانة الشاشة الواحدة عند إيقاف التشغيل. قيم MinPos وMaxPos ذات الأرقام الكبيرة مثل 4294967295 موضع داخلي غير مستخدم، وتُترك كما هي.
ماذا يفعل الحل الكامل
الحل جزآن، وبالترتيب التالي. الجزء الأول يزرع الحجم الذي تريده. الجزء الثاني يمنع الإغلاق المكبر من استبداله.
| ما تفعله | ما يُحفظ لحجم Restore |
|---|---|
| تغلق النافذة بحجم عادي | هذا الحجم يصبح حجم الاستعادة |
| تكبّر النافذة ثم تغلقها وهي كبيرة | يُتجاهل الإطار الكبير، ويبقى آخر حجم عادي |
| تعيد تشغيل الجهاز | الحماية تعمل من جديد مع تسجيل الدخول وتطبّق القاعدة نفسها |
الحماية برنامج صغير في حسابك. يراقب مفتاح WinPos فقط. إذا كتب المستكشف مستطيلًا عاديًا يحفظه. إذا كتب مستطيلًا يطابق مساحة العمل أو إطار التكبير، بهامش نحو 24 بكسلًا، يعيد آخر حجم عادي خلال لحظة. وينسخ الحجم السليم أيضًا إلى خانات الترتيبات الأخرى لنفس الدقة ونفس نسبة التكبير، ومنها خانة الشاشة الواحدة التي تُقرأ في بداية الإقلاع.
الذاكرة الخاصة بهذه الحماية تُحفظ في HKEY_CURRENT_USERSoftwareExplorerRestoreGuard. هي لا تغيّر ShowCmd، ولا تحذف سجل المجلدات، ولا تعيد تشغيل explorer.exe، ولا تمس ملفات النظام. زر التكبير نفسه يبقى يعمل؛ الممنوع هو اعتماد الإطار الكبير حجمًا للاستعادة.
ما الذي نتركه كما هو
تنفيذ الحل من البداية
نفّذ الخطوات بهذا الترتيب. إغلاق حجم عادي أولًا يزرع الرقم الذي ستحميه الخطوة الأخيرة.
1. أغلق نافذة واحدة بالحجم الذي تريده
- افتح مستكشف الملفات.
- إذا كانت النافذة تغطي الشاشة فاضغط Restore. إن بقي الحجم كبيرًا فاسحب حافة أو زاوية. إذا كانت الحافة خارج الشاشة فاضغط
Alt+Space، ثم Size، ثم حرّكها بالأسهم، ثم Enter. - اضبط العرض والارتفاع والموضع، واترك النافذة غير مكبرة.
- أغلقها بزر الإغلاق، وأغلق أي نافذة مستكشف أخرى.
هذا يكتب الحجم السليم في الخانة المطابقة لعدد الشاشات المتصلة الآن.
2. خذ نسخة من المفتاح
- اضغط
Win+R، واكتبregedit، ثم Enter. - الصق المسار في شريط العنوان:
ComputerHKEY_CURRENT_USERSoftwareClassesLocal SettingsSoftwareMicrosoftWindowsShellBagsAllFoldersShell
- انقر بزر الفأرة الأيمن على
Shellواختر Export. - احفظ الملف على سطح المكتب باسم
Explorer-WinPos-backup.reg.
3. تأكد أن حجمًا سليمًا موجود
من الإعدادات: النظام ← العرض. دوّن الدقة ونسبة Scale، وقابلها برقم DPI في الجدول:
| النسبة | الرقم داخل اسم القيمة |
|---|---|
| 100% | 96 |
| 125% | 120 |
| 150% | 144 |
| 175% | 168 |
| 200% | 192 |
على شاشة 1920×1080 بنسبة 100% انظر إلى الأسماء التي تبدأ بـ WinPos1920x1080x96. هذا الأمر يعرض القيم ولا يكتب شيئًا:
$path = 'HKCU:SoftwareClassesLocal SettingsSoftwareMicrosoftWindowsShellBagsAllFoldersShell'
Get-ItemProperty $path |
ForEach-Object { $_.PSObject.Properties } |
Where-Object { $_.Name -like 'WinPos*' -or $_.Name -eq 'ShowCmd' } |
Sort-Object Name |
Select-Object Name, Value |
Format-Table -AutoSize
- الخانة السليمة أصغر من الشاشة بوضوح، وهي عادة الحجم الذي أغلقته في الخطوة الأولى.
- الخانة التالفة تطابق مساحة العمل، أو تتجاوز أطراف الشاشة بنحو 7 إلى 24 بكسلًا. مثال الشاشة الثانية: 1911, 3, 3847, 1051.
لا تحتاج إلى كتابة الأرقام بيدك إذا ظهرت خانة سليمة واحدة على الأقل. الحماية، عند تشغيلها، تنسخ هذا الحجم إلى أي خانة أخرى لنفس الدقة ونفس DPI ما زالت تملأ الشاشة.
إذا كانت كل خانات نسبتك الحالية تالفة، كرّر الخطوة الأولى وأغلق النافذة وهي غير مكبرة. إن تعذّر سحب الأطراف، اكتب نافذة في وسط مساحة العمل من محرر السجل واختر القاعدة Decimal. لشاشة عمل 1920×1032 ونافذة 1200×800:
left = (1920 - 1200) / 2 = 360 top = (1032 - 800) / 2 = 116 right = 360 + 1200 = 1560 bottom = 116 + 800 = 916
اجعل ShowCmd تساوي 1 حتى يفتح المستكشف بهذا الحجم مباشرة.
4. شغّل الحماية التي تتجاهل الإغلاق المكبر
اضغط Win+R، والصق %LOCALAPPDATA%ExplorerRestoreGuard، ثم Enter. إن لم يوجد المجلد فأنشئه. احفظ فيه ملفين بالاسمين التاليين تمامًا، ومن نوع «كل الملفات» حتى لا يضيف المفكرة .txt.
الملف الأول ExplorerRestoreGuard.ps1:
# Keeps File Explorer's restore rectangle when a window is closed while maximized.
# Windows 11 writes the maximized frame into WinPos. This watcher puts the last
# normal size back and remembers that size for the next close.
$ErrorActionPreference = 'Stop'
$mutex = New-Object System.Threading.Mutex($false, 'LocalExplorerRestoreGuard')
if (-not $mutex.WaitOne(0)) { exit 0 }
$ShellRel = 'SoftwareClassesLocal SettingsSoftwareMicrosoftWindowsShellBagsAllFoldersShell'
$GuardRel = 'SoftwareExplorerRestoreGuard'
$LogPath = Join-Path $env:LOCALAPPDATA 'ExplorerRestoreGuardguard.log'
$EdgeSlack = 24
Add-Type -AssemblyName System.Windows.Forms
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public static class ExplorerGuardNative {
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int RegNotifyChangeKeyValue(
SafeHandle hKey, bool bWatchSubtree, uint dwNotifyFilter, IntPtr hEvent, bool fAsynchronous);
}
"@
function Write-Log([string]$message) {
$line = '{0:yyyy-MM-dd HH:mm:ss} {1}' -f (Get-Date), $message
Add-Content -Path $LogPath -Value $line -Encoding UTF8
if ((Get-Item $LogPath).Length -gt 65536) {
$tail = Get-Content $LogPath -Tail 80
Set-Content -Path $LogPath -Value $tail -Encoding UTF8
}
}
function Convert-ToInt([object]$value) {
$n = [int64]$value
if ($n -gt 2147483647) { $n -= 4294967296 }
return [int]$n
}
function Get-Monitors {
$list = New-Object System.Collections.Generic.List[object]
foreach ($screen in [System.Windows.Forms.Screen]::AllScreens) {
$work = $screen.WorkingArea
$bounds = $screen.Bounds
$list.Add([pscustomobject]@{
WL = $work.Left; WT = $work.Top; WR = $work.Right; WB = $work.Bottom
ML = $bounds.Left; MT = $bounds.Top; MR = $bounds.Right; MB = $bounds.Bottom
}) | Out-Null
}
return $list
}
function Test-Near([int]$a, [int]$b) {
return [Math]::Abs($a - $b) -le $EdgeSlack
}
function Test-PoisonedRect([int]$left, [int]$top, [int]$right, [int]$bottom, $monitors) {
$width = $right - $left
$height = $bottom - $top
if ($width -lt 400 -or $height -lt 300) { return $false }
foreach ($m in $monitors) {
$workWidth = $m.WR - $m.WL
$workHeight = $m.WB - $m.WT
$monWidth = $m.MR - $m.ML
$monHeight = $m.MB - $m.MT
$nearWork = (Test-Near $left $m.WL) -and (Test-Near $top $m.WT) -and (Test-Near $right $m.WR) -and (Test-Near $bottom $m.WB)
$nearMonitor = (Test-Near $left $m.ML) -and (Test-Near $top $m.MT) -and (Test-Near $right $m.MR) -and (Test-Near $bottom $m.MB)
$coversWork = ($width -ge ($workWidth - $EdgeSlack)) -and ($height -ge ($workHeight - $EdgeSlack)) -and (Test-Near $left $m.WL) -and (Test-Near $top $m.WT)
$coversMonitor = ($width -ge ($monWidth - $EdgeSlack)) -and ($height -ge ($monHeight - $EdgeSlack)) -and (Test-Near $left $m.ML) -and ($top -le ($m.MT + $EdgeSlack))
if ($nearWork -or $nearMonitor -or $coversWork -or $coversMonitor) { return $true }
}
return $false
}
function Get-GuardKey {
return [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($GuardRel)
}
function Read-Groups {
$shell = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($ShellRel, $true)
$groups = @{}
foreach ($name in $shell.GetValueNames()) {
if ($name -notmatch '^WinPos(.+).(left|top|right|bottom)$') { continue }
$id = $Matches[1]
$edge = $Matches[2]
if (-not $groups.ContainsKey($id)) {
$groups[$id] = @{ left = $null; top = $null; right = $null; bottom = $null }
}
$groups[$id][$edge] = Convert-ToInt $shell.GetValue($name)
}
return @{ Shell = $shell; Groups = $groups }
}
function Save-Good([string]$id, [int]$left, [int]$top, [int]$right, [int]$bottom) {
$guard = Get-GuardKey
$guard.SetValue("$id.left", $left, 'DWord')
$guard.SetValue("$id.top", $top, 'DWord')
$guard.SetValue("$id.right", $right, 'DWord')
$guard.SetValue("$id.bottom", $bottom, 'DWord')
}
function Get-SavedGood([string]$id) {
$guard = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($GuardRel, $false)
if ($null -eq $guard) { return $null }
$edges = @('left', 'top', 'right', 'bottom')
$rect = @{}
foreach ($edge in $edges) {
$raw = $guard.GetValue("$id.$edge")
if ($null -eq $raw) { return $null }
$rect[$edge] = Convert-ToInt $raw
}
return $rect
}
function Get-SiblingGood([string]$id, $groups, $monitors) {
$prefix = ($id -replace '(d+)$', '')
foreach ($other in ($groups.Keys | Sort-Object)) {
if ($other -eq $id) { continue }
if (-not $other.StartsWith($prefix)) { continue }
$g = $groups[$other]
if ($null -eq $g.left) { continue }
if (Test-PoisonedRect $g.left $g.top $g.right $g.bottom $monitors) { continue }
return $g
}
return $null
}
function Write-Rect($shell, [string]$id, $rect) {
$shell.SetValue("WinPos$id.left", [int]$rect.left, 'DWord')
$shell.SetValue("WinPos$id.top", [int]$rect.top, 'DWord')
$shell.SetValue("WinPos$id.right", [int]$rect.right, 'DWord')
$shell.SetValue("WinPos$id.bottom", [int]$rect.bottom, 'DWord')
}
function Repair-Positions {
$monitors = @(Get-Monitors)
if ($monitors.Count -eq 0) { return }
$state = Read-Groups
$shell = $state.Shell
$groups = $state.Groups
foreach ($id in @($groups.Keys)) {
$g = $groups[$id]
if ($null -eq $g.left -or $null -eq $g.top -or $null -eq $g.right -or $null -eq $g.bottom) { continue }
if (Test-PoisonedRect $g.left $g.top $g.right $g.bottom $monitors) { continue }
Save-Good $id $g.left $g.top $g.right $g.bottom
}
foreach ($id in @($groups.Keys)) {
$g = $groups[$id]
if ($null -eq $g.left -or $null -eq $g.top -or $null -eq $g.right -or $null -eq $g.bottom) { continue }
if (-not (Test-PoisonedRect $g.left $g.top $g.right $g.bottom $monitors)) { continue }
$good = Get-SavedGood $id
if ($null -ne $good -and (Test-PoisonedRect $good.left $good.top $good.right $good.bottom $monitors)) {
$good = $null
}
if ($null -eq $good) { $good = Get-SiblingGood $id $groups $monitors }
if ($null -eq $good) {
$work = $monitors[0]
$width = 1200
$height = 800
$workWidth = $work.WR - $work.WL
$workHeight = $work.WB - $work.WT
if ($width -gt $workWidth - 80) { $width = $workWidth - 80 }
if ($height -gt $workHeight - 80) { $height = $workHeight - 80 }
$left = $work.WL + [int](($workWidth - $width) / 2)
$top = $work.WT + [int](($workHeight - $height) / 2)
$good = @{ left = $left; top = $top; right = ($left + $width); bottom = ($top + $height) }
}
Write-Rect $shell $id $good
Save-Good $id $good.left $good.top $good.right $good.bottom
Write-Log ("restored WinPos{0} from {1},{2},{3},{4} to {5},{6},{7},{8}" -f $id, $g.left, $g.top, $g.right, $g.bottom, $good.left, $good.top, $good.right, $good.bottom)
}
}
Write-Log 'started'
Repair-Positions
$notifyEvent = New-Object System.Threading.AutoResetEvent($false)
$shellKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($ShellRel, $false)
$useNotify = $false
try {
$hr = [ExplorerGuardNative]::RegNotifyChangeKeyValue($shellKey.Handle, $false, 4, $notifyEvent.SafeWaitHandle.DangerousGetHandle(), $true)
$useNotify = ($hr -eq 0)
Write-Log ("notify registration {0}" -f $hr)
} catch {
Write-Log ("notify unavailable: {0}" -f $_.Exception.Message)
}
while ($true) {
if ($useNotify) {
$signaled = $notifyEvent.WaitOne(5000)
if ($signaled) { Start-Sleep -Milliseconds 450 }
$hr = [ExplorerGuardNative]::RegNotifyChangeKeyValue($shellKey.Handle, $false, 4, $notifyEvent.SafeWaitHandle.DangerousGetHandle(), $true)
if ($hr -ne 0) { $useNotify = $false }
} else {
Start-Sleep -Seconds 1
}
try {
Repair-Positions
} catch {
Write-Log ("repair error: {0}" -f $_.Exception.Message)
}
}
الملف الثاني، في المجلد نفسه، باسم start.vbs. يشغّل الحماية بلا نافذة سوداء:
Set shell = CreateObject("Wscript.Shell")
script = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "ExplorerRestoreGuard.ps1"
shell.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & script & """", 0, False
ثم من PowerShell، بعد إغلاق نوافذ المستكشف، ثبّتها في بدء التشغيل وشغّلها الآن:
$dir = Join-Path $env:LOCALAPPDATA 'ExplorerRestoreGuard' $startup = Join-Path $env:APPDATA 'MicrosoftWindowsStart MenuProgramsStartupExplorerRestoreGuard.vbs' Copy-Item (Join-Path $dir 'start.vbs') $startup -Force Start-Process wscript.exe -ArgumentList '//B', (Join-Path $dir 'start.vbs') Start-Sleep -Seconds 2 Get-Content (Join-Path $dir 'guard.log') -Tail 20
السطر started يعني أن الحماية تعمل. سطر restored WinPos... يعني أنها وجدت خانة بحجم الشاشة وأعادتها إلى آخر حجم عادي. سطر notify registration 0 يعني أنها تنتظر كتابة المستكشف التالية فورًا.
التحقق من السلوكين
- افتح المستكشف، غيّر حجمه قليلًا وهو غير مكبر، ثم أغلقه. النافذة التالية تفتح بهذا الحجم الجديد، لأنه إغلاق عادي ويُعتمد.
- كبّر النافذة وأغلقها وهي كبيرة. خلال ثانية تقريبًا تعود أرقام
WinPosإلى الحجم العادي السابق، ونافذة جديدة تفتح به. سجلguard.logيذكر الأطراف الكبيرة التي رُفضت والأطراف التي أُعيدت. - أعد تشغيل الجهاز وافتح المستكشف. الحماية تبدأ مع تسجيل الدخول، والحجم العادي ما زال هو حجم Restore، بما في ذلك خانة الشاشة الواحدة.
x120 أو x144؛ أغلق نافذة واحدة بالحجم المطلوب على النسبة الجديدة والحماية تتولى الباقي.
إيقاف الحماية والرجوع
- اضغط
Win+R، والصقshell:startup، واحذفExplorerRestoreGuard.vbs. - من مدير المهام أوقف عملية
powershell.exeالتي يظهر في أمر تشغيلها اسمExplorerRestoreGuard.ps1. - إن أردت مسح ذاكرة الحماية فاحذف مفتاح
HKEY_CURRENT_USERSoftwareExplorerRestoreGuardوالمجلد%LOCALAPPDATA%ExplorerRestoreGuard. - لاسترجاع أرقام النافذة كما كانت قبل أي تعديل، انقر مرتين على
Explorer-WinPos-backup.regووافق.
WinPos. ويندوز 11 يضع مكانه إطار النافذة المكبرة عند الإغلاق. أغلق المستكشف مرة بالحجم الذي تريده، ثم اترك الحماية في حسابك تعيد هذا الحجم كلما كُتب إطار الشاشة الكاملة. هكذا لا يُحتسب الإغلاق المكبر حجمًا للاستعادة.