Skip to content

Commit a1f8c47

Browse files
committed
Update SpreadsheetCompare.cs
1 parent 07b05eb commit a1f8c47

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

src/MsExcelDiff/SpreadsheetCompare.cs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,18 +222,194 @@ static async Task MaximizeWindow(Process process)
222222
// SW_MAXIMIZE = 3
223223
ShowWindow(process.MainWindowHandle, 3);
224224
SetForegroundWindow(process.MainWindowHandle);
225+
226+
// Wait briefly for the window to finish layout after maximize
227+
await Task.Delay(500);
228+
CenterVerticalSplit(process.MainWindowHandle);
225229
return;
226230
}
227231

228232
await Task.Delay(100);
229233
}
230234
}
231235

236+
static void CenterVerticalSplit(IntPtr mainWindow)
237+
{
238+
// Collect all child windows with their parent, class name, and rect
239+
var children = new List<(IntPtr Handle, IntPtr Parent, string ClassName, RECT Rect)>();
240+
EnumChildWindows(mainWindow, (hwnd, _) =>
241+
{
242+
GetWindowRect(hwnd, out var rect);
243+
var className = GetWindowClassName(hwnd);
244+
children.Add((hwnd, GetParent(hwnd), className, rect));
245+
return true;
246+
}, IntPtr.Zero);
247+
248+
// Log child window hierarchy for diagnostics
249+
Log.Information("CenterVerticalSplit: found {Count} child windows", children.Count);
250+
foreach (var child in children)
251+
{
252+
var w = child.Rect.Right - child.Rect.Left;
253+
var h = child.Rect.Bottom - child.Rect.Top;
254+
Log.Information(
255+
" hwnd={Handle} parent={Parent} class={ClassName} pos=({Left},{Top}) size={Width}x{Height}",
256+
child.Handle, child.Parent, child.ClassName,
257+
child.Rect.Left, child.Rect.Top, w, h);
258+
}
259+
260+
// Find the vertical splitter: look for pairs of side-by-side siblings
261+
// with similar height that together span most of their parent's width.
262+
// Pick the pair with the largest combined area.
263+
var bestArea = 0;
264+
var bestLeftRect = default(RECT);
265+
var bestRightRect = default(RECT);
266+
var bestParent = IntPtr.Zero;
267+
268+
foreach (var group in children.GroupBy(c => c.Parent))
269+
{
270+
var siblings = group.ToList();
271+
272+
for (var i = 0; i < siblings.Count; i++)
273+
{
274+
for (var j = i + 1; j < siblings.Count; j++)
275+
{
276+
var a = siblings[i];
277+
var b = siblings[j];
278+
var heightA = a.Rect.Bottom - a.Rect.Top;
279+
var heightB = b.Rect.Bottom - b.Rect.Top;
280+
281+
var widthA = a.Rect.Right - a.Rect.Left;
282+
var widthB = b.Rect.Right - b.Rect.Left;
283+
284+
if (heightA < 100 || heightB < 100 ||
285+
widthA <= 0 || widthB <= 0)
286+
{
287+
continue;
288+
}
289+
290+
if (Math.Abs(heightA - heightB) > 20 ||
291+
Math.Abs(a.Rect.Top - b.Rect.Top) > 20)
292+
{
293+
continue;
294+
}
295+
296+
GetClientRect(group.Key, out var parentClient);
297+
var totalSpan = Math.Max(a.Rect.Right, b.Rect.Right) - Math.Min(a.Rect.Left, b.Rect.Left);
298+
if (totalSpan < parentClient.Right * 0.8)
299+
{
300+
continue;
301+
}
302+
303+
var area = (a.Rect.Right - a.Rect.Left) * heightA +
304+
(b.Rect.Right - b.Rect.Left) * heightB;
305+
if (area <= bestArea)
306+
{
307+
continue;
308+
}
309+
310+
bestArea = area;
311+
bestParent = group.Key;
312+
if (a.Rect.Left <= b.Rect.Left)
313+
{
314+
bestLeftRect = a.Rect;
315+
bestRightRect = b.Rect;
316+
}
317+
else
318+
{
319+
bestLeftRect = b.Rect;
320+
bestRightRect = a.Rect;
321+
}
322+
}
323+
}
324+
}
325+
326+
if (bestArea == 0)
327+
{
328+
Log.Information("CenterVerticalSplit: no matching split panel pair found");
329+
return;
330+
}
331+
332+
// The splitter bar sits in the gap between the two panels.
333+
// Convert splitter screen position to parent client coordinates and
334+
// send mouse messages directly to the parent (SplitContainer) window.
335+
var splitterScreenX = (bestLeftRect.Right + bestRightRect.Left) / 2;
336+
var splitterScreenY = (bestLeftRect.Top + bestLeftRect.Bottom) / 2;
337+
338+
var splitterPoint = new POINT { X = splitterScreenX, Y = splitterScreenY };
339+
ScreenToClient(bestParent, ref splitterPoint);
340+
341+
GetClientRect(bestParent, out var client);
342+
var targetClientX = client.Right / 2;
343+
344+
Log.Information(
345+
"CenterVerticalSplit: sending drag from client ({FromX},{FromY}) to ({ToX},{ToY})",
346+
splitterPoint.X, splitterPoint.Y, targetClientX, splitterPoint.Y);
347+
348+
var downLParam = MakeLParam(splitterPoint.X, splitterPoint.Y);
349+
var moveLParam = MakeLParam(targetClientX, splitterPoint.Y);
350+
351+
// WM_LBUTTONDOWN = 0x0201, WM_MOUSEMOVE = 0x0200, WM_LBUTTONUP = 0x0202
352+
// MK_LBUTTON = 0x0001
353+
SendMessage(bestParent, 0x0201, (IntPtr)0x0001, downLParam);
354+
SendMessage(bestParent, 0x0200, (IntPtr)0x0001, moveLParam);
355+
SendMessage(bestParent, 0x0202, IntPtr.Zero, moveLParam);
356+
}
357+
358+
static IntPtr MakeLParam(int x, int y) =>
359+
(IntPtr)((y << 16) | (x & 0xFFFF));
360+
361+
delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
362+
363+
[StructLayout(LayoutKind.Sequential)]
364+
struct RECT
365+
{
366+
public int Left, Top, Right, Bottom;
367+
}
368+
369+
[StructLayout(LayoutKind.Sequential)]
370+
struct POINT
371+
{
372+
public int X, Y;
373+
}
374+
232375
[LibraryImport("user32.dll")]
233376
[return: MarshalAs(UnmanagedType.Bool)]
234377
private static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
235378

236379
[LibraryImport("user32.dll")]
237380
[return: MarshalAs(UnmanagedType.Bool)]
238381
private static partial bool SetForegroundWindow(IntPtr hWnd);
382+
383+
[LibraryImport("user32.dll")]
384+
[return: MarshalAs(UnmanagedType.Bool)]
385+
private static partial bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
386+
387+
[LibraryImport("user32.dll")]
388+
[return: MarshalAs(UnmanagedType.Bool)]
389+
private static partial bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
390+
391+
[LibraryImport("user32.dll")]
392+
[return: MarshalAs(UnmanagedType.Bool)]
393+
private static partial bool GetClientRect(IntPtr hWnd, out RECT lpRect);
394+
395+
[LibraryImport("user32.dll")]
396+
private static partial IntPtr GetParent(IntPtr hWnd);
397+
398+
[LibraryImport("user32.dll")]
399+
[return: MarshalAs(UnmanagedType.Bool)]
400+
private static partial bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
401+
402+
[LibraryImport("user32.dll", EntryPoint = "SendMessageW")]
403+
private static partial IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
404+
405+
static string GetWindowClassName(IntPtr hWnd)
406+
{
407+
var buffer = new System.Text.StringBuilder(256);
408+
GetClassName(hWnd, buffer, buffer.Capacity);
409+
return buffer.ToString();
410+
}
411+
412+
[DllImport("user32.dll", CharSet = CharSet.Auto)]
413+
private static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);
414+
239415
}

0 commit comments

Comments
 (0)