Skip to content

Commit 633fdaa

Browse files
committed
.
1 parent ab30c6c commit 633fdaa

File tree

1 file changed

+98
-54
lines changed

1 file changed

+98
-54
lines changed

src/MsExcelDiff/SpreadsheetCompare.cs

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,16 @@ static async Task MaximizeWindow(Process process)
225225

226226
// Wait briefly for the window to finish layout after maximize
227227
await Task.Delay(500);
228-
CenterVerticalSplit(process.MainWindowHandle);
228+
CenterSplits(process.MainWindowHandle);
229229
return;
230230
}
231231

232232
await Task.Delay(100);
233233
}
234234
}
235235

236-
static void CenterVerticalSplit(IntPtr mainWindow)
236+
static void CenterSplits(IntPtr mainWindow)
237237
{
238-
// Collect all child windows with their parent, class name, and rect
239238
var children = new List<(IntPtr Handle, IntPtr Parent, string ClassName, RECT Rect)>();
240239
EnumChildWindows(mainWindow, (hwnd, _) =>
241240
{
@@ -245,8 +244,7 @@ static void CenterVerticalSplit(IntPtr mainWindow)
245244
return true;
246245
}, IntPtr.Zero);
247246

248-
// Log child window hierarchy for diagnostics
249-
Log.Information("CenterVerticalSplit: found {Count} child windows", children.Count);
247+
Log.Information("CenterSplits: found {Count} child windows", children.Count);
250248
foreach (var child in children)
251249
{
252250
var w = child.Rect.Right - child.Rect.Left;
@@ -257,12 +255,21 @@ static void CenterVerticalSplit(IntPtr mainWindow)
257255
child.Rect.Left, child.Rect.Top, w, h);
258256
}
259257

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.
258+
CenterSplit(children, SplitOrientation.Vertical);
259+
CenterSplit(children, SplitOrientation.Horizontal);
260+
}
261+
262+
enum SplitOrientation
263+
{
264+
Vertical,
265+
Horizontal
266+
}
267+
268+
static void CenterSplit(List<(IntPtr Handle, IntPtr Parent, string ClassName, RECT Rect)> children, SplitOrientation orientation)
269+
{
263270
var bestArea = 0;
264-
var bestLeftRect = default(RECT);
265-
var bestRightRect = default(RECT);
271+
var bestFirstRect = default(RECT);
272+
var bestSecondRect = default(RECT);
266273
var bestParent = IntPtr.Zero;
267274

268275
foreach (var group in children.GroupBy(c => c.Parent))
@@ -275,88 +282,125 @@ static void CenterVerticalSplit(IntPtr mainWindow)
275282
{
276283
var a = siblings[i];
277284
var b = siblings[j];
278-
var heightA = a.Rect.Bottom - a.Rect.Top;
279-
var heightB = b.Rect.Bottom - b.Rect.Top;
280-
281285
var widthA = a.Rect.Right - a.Rect.Left;
282286
var widthB = b.Rect.Right - b.Rect.Left;
287+
var heightA = a.Rect.Bottom - a.Rect.Top;
288+
var heightB = b.Rect.Bottom - b.Rect.Top;
283289

284-
if (heightA < 100 || heightB < 100 ||
285-
widthA <= 0 || widthB <= 0)
290+
if (widthA <= 0 || widthB <= 0 ||
291+
heightA <= 0 || heightB <= 0)
286292
{
287293
continue;
288294
}
289295

290-
if (Math.Abs(heightA - heightB) > 20 ||
291-
Math.Abs(a.Rect.Top - b.Rect.Top) > 20)
296+
GetClientRect(group.Key, out var parentClient);
297+
298+
bool isMatch;
299+
if (orientation == SplitOrientation.Vertical)
300+
{
301+
// Side-by-side: same height/top, span parent width
302+
isMatch = heightA >= 100 && heightB >= 100 &&
303+
Math.Abs(heightA - heightB) <= 20 &&
304+
Math.Abs(a.Rect.Top - b.Rect.Top) <= 20 &&
305+
Math.Max(a.Rect.Right, b.Rect.Right) - Math.Min(a.Rect.Left, b.Rect.Left) >= parentClient.Right * 0.8;
306+
}
307+
else
308+
{
309+
// Stacked: same width/left, span parent height
310+
isMatch = widthA >= 100 && widthB >= 100 &&
311+
Math.Abs(widthA - widthB) <= 20 &&
312+
Math.Abs(a.Rect.Left - b.Rect.Left) <= 20 &&
313+
Math.Max(a.Rect.Bottom, b.Rect.Bottom) - Math.Min(a.Rect.Top, b.Rect.Top) >= parentClient.Bottom * 0.8;
314+
}
315+
316+
if (!isMatch)
292317
{
293318
continue;
294319
}
295320

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)
321+
// Require a gap between the panels (the splitter bar).
322+
// Adjacent windows without a gap (e.g. ribbon/content) are not splits.
323+
int gap;
324+
if (orientation == SplitOrientation.Vertical)
325+
{
326+
var left = a.Rect.Left < b.Rect.Left ? a.Rect : b.Rect;
327+
var right = a.Rect.Left < b.Rect.Left ? b.Rect : a.Rect;
328+
gap = right.Left - left.Right;
329+
}
330+
else
331+
{
332+
var top = a.Rect.Top < b.Rect.Top ? a.Rect : b.Rect;
333+
var bottom = a.Rect.Top < b.Rect.Top ? b.Rect : a.Rect;
334+
gap = bottom.Top - top.Bottom;
335+
}
336+
337+
if (gap <= 0)
299338
{
300339
continue;
301340
}
302341

303-
var area = (a.Rect.Right - a.Rect.Left) * heightA +
304-
(b.Rect.Right - b.Rect.Left) * heightB;
342+
var area = widthA * heightA + widthB * heightB;
305343
if (area <= bestArea)
306344
{
307345
continue;
308346
}
309347

310348
bestArea = area;
311349
bestParent = group.Key;
312-
if (a.Rect.Left <= b.Rect.Left)
350+
if (orientation == SplitOrientation.Vertical)
313351
{
314-
bestLeftRect = a.Rect;
315-
bestRightRect = b.Rect;
352+
bestFirstRect = a.Rect.Left <= b.Rect.Left ? a.Rect : b.Rect;
353+
bestSecondRect = a.Rect.Left <= b.Rect.Left ? b.Rect : a.Rect;
316354
}
317355
else
318356
{
319-
bestLeftRect = b.Rect;
320-
bestRightRect = a.Rect;
357+
bestFirstRect = a.Rect.Top <= b.Rect.Top ? a.Rect : b.Rect;
358+
bestSecondRect = a.Rect.Top <= b.Rect.Top ? b.Rect : a.Rect;
321359
}
322360
}
323361
}
324362
}
325363

326364
if (bestArea == 0)
327365
{
328-
Log.Information("CenterVerticalSplit: no matching split panel pair found");
366+
Log.Information("CenterSplit({Orientation}): no matching pair found", orientation);
329367
return;
330368
}
331369

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;
370+
// Compute splitter position and target in screen coordinates
371+
GetWindowRect(bestParent, out var parentRect);
337372

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;
373+
int fromX, fromY, toX, toY;
374+
if (orientation == SplitOrientation.Vertical)
375+
{
376+
fromX = (bestFirstRect.Right + bestSecondRect.Left) / 2;
377+
fromY = (bestFirstRect.Top + bestFirstRect.Bottom) / 2;
378+
toX = (parentRect.Left + parentRect.Right) / 2;
379+
toY = fromY;
380+
}
381+
else
382+
{
383+
fromX = (bestFirstRect.Left + bestFirstRect.Right) / 2;
384+
fromY = (bestFirstRect.Bottom + bestSecondRect.Top) / 2;
385+
toX = fromX;
386+
toY = (parentRect.Top + parentRect.Bottom) / 2;
387+
}
343388

344389
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);
390+
"CenterSplit({Orientation}): drag screen ({FromX},{FromY}) to ({ToX},{ToY})",
391+
orientation, fromX, fromY, toX, toY);
392+
393+
SetCursorPos(fromX, fromY);
394+
Thread.Sleep(100);
395+
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, IntPtr.Zero);
396+
Thread.Sleep(100);
397+
SetCursorPos(toX, toY);
398+
Thread.Sleep(100);
399+
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, IntPtr.Zero);
356400
}
357401

358-
static IntPtr MakeLParam(int x, int y) =>
359-
(IntPtr)((y << 16) | (x & 0xFFFF));
402+
const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
403+
const uint MOUSEEVENTF_LEFTUP = 0x0004;
360404

361405
delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
362406

@@ -397,10 +441,10 @@ struct POINT
397441

398442
[LibraryImport("user32.dll")]
399443
[return: MarshalAs(UnmanagedType.Bool)]
400-
private static partial bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
444+
private static partial bool SetCursorPos(int x, int y);
401445

402-
[LibraryImport("user32.dll", EntryPoint = "SendMessageW")]
403-
private static partial IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
446+
[LibraryImport("user32.dll")]
447+
private static partial void mouse_event(uint dwFlags, int dx, int dy, uint dwData, IntPtr dwExtraInfo);
404448

405449
static string GetWindowClassName(IntPtr hWnd)
406450
{

0 commit comments

Comments
 (0)