@@ -14,20 +14,31 @@ namespace SqlAnalyzer.App.Services;
1414
1515public sealed class ResultWorkspaceController
1616{
17+ private const double ColumnResizeHandleWidth = 7.0 ;
18+ private const double ColumnResizeHandleOverflow = 2.0 ;
19+ private const double MinDataColumnWidth = 48.0 ;
20+ private const double MaxDataColumnWidth = 720.0 ;
21+
1722 private readonly Dictionary < int , Border > _headerBorders = [ ] ;
1823 private readonly HashSet < int > _pinnedHeaderColumns = [ ] ;
1924 private readonly Dictionary < ResultRowViewItem , Border > _rowBorders = [ ] ;
2025 private readonly Dictionary < ResultRowViewItem , Border > _actionBorders = [ ] ;
2126 private readonly Dictionary < ( ResultRowViewItem Row , int ColumnIndex ) , Border > _cellBorders = [ ] ;
27+ private readonly Dictionary < int , int > _columnVisualIndexes = [ ] ;
28+ private readonly List < Grid > _scrollableGrids = [ ] ;
2229 private readonly HashSet < ResultRowViewItem > _selectedRows = [ ] ;
2330 private readonly HashSet < ( ResultRowViewItem Row , int ColumnIndex ) > _selectedCells = [ ] ;
2431 private ResultRowViewItem ? _selectedRow ;
2532 private ResultRowViewItem ? _selectionAnchorRow ;
2633 private ResultCellContext ? _cellSelectionAnchor ;
2734 private ResultCellContext ? _selectedCell ;
35+ private int _resizingColumnIndex = - 1 ;
36+ private double _resizeStartX ;
37+ private double _resizeStartWidth ;
2838 private bool _isDraggingSelection ;
2939 private bool _isDraggingCellSelection ;
3040 private bool _isDraggingHeaderSelection ;
41+ private bool _isResizingColumn ;
3142
3243 public ResultRowViewItem ? SelectedRow => _selectedRow ;
3344
@@ -41,6 +52,8 @@ public sealed class ResultWorkspaceController
4152
4253 public bool IsDraggingHeaderSelection => _isDraggingHeaderSelection ;
4354
55+ public bool IsResizingColumn => _isResizingColumn ;
56+
4457 public ResultCellContext ? SelectedCell => _selectedCell ;
4558
4659 public int SelectedCellSelectionCount => _selectedCells . Count ;
@@ -93,10 +106,12 @@ public Border BuildScrollableHeaderRow(
93106 {
94107 ColumnDefinitions = new ColumnDefinitions ( string . Join ( "," , columnWidths . Select ( width => $ "{ width } ") ) )
95108 } ;
109+ _scrollableGrids . Add ( headerGrid ) ;
96110
97111 for ( int visualIndex = 0 ; visualIndex < orderedColumns . Count ; visualIndex ++ )
98112 {
99113 int columnIndex = orderedColumns [ visualIndex ] ;
114+ _columnVisualIndexes [ columnIndex ] = visualIndex ;
100115 ResultColumnViewItem column = resultSet . Columns [ columnIndex ] ;
101116 string subtitle = headerSubtitleFormatter ( column ) ;
102117 Border headerCell = new ( )
@@ -188,6 +203,7 @@ public Border BuildScrollableDataRow(
188203 {
189204 ColumnDefinitions = new ColumnDefinitions ( string . Join ( "," , columnWidths . Select ( width => $ "{ width } ") ) )
190205 } ;
206+ _scrollableGrids . Add ( rowGrid ) ;
191207
192208 for ( int visualIndex = 0 ; visualIndex < orderedColumns . Count ; visualIndex ++ )
193209 {
@@ -407,15 +423,91 @@ public void Reset()
407423 _rowBorders . Clear ( ) ;
408424 _actionBorders . Clear ( ) ;
409425 _cellBorders . Clear ( ) ;
426+ _columnVisualIndexes . Clear ( ) ;
427+ _scrollableGrids . Clear ( ) ;
410428 _selectedRows . Clear ( ) ;
411429 _selectedCells . Clear ( ) ;
412430 _selectedRow = null ;
413431 _selectionAnchorRow = null ;
414432 _cellSelectionAnchor = null ;
415433 _selectedCell = null ;
434+ _resizingColumnIndex = - 1 ;
435+ _resizeStartX = 0 ;
436+ _resizeStartWidth = 0 ;
416437 _isDraggingSelection = false ;
417438 _isDraggingCellSelection = false ;
418439 _isDraggingHeaderSelection = false ;
440+ _isResizingColumn = false ;
441+ }
442+ public bool TryGetHeaderResizeColumnAtPoint ( Visual relativeTo , Point point , out int columnIndex )
443+ {
444+ double bestDistance = double . MaxValue ;
445+ int bestColumnIndex = - 1 ;
446+ foreach ( ( int key , Border border ) in _headerBorders )
447+ {
448+ if ( ! TryBuildHitRect ( border , relativeTo , out Rect rect ) ||
449+ point . Y < rect . Top ||
450+ point . Y > rect . Bottom ||
451+ point . X < rect . Right - ColumnResizeHandleWidth ||
452+ point . X > rect . Right + ColumnResizeHandleOverflow )
453+ {
454+ continue ;
455+ }
456+
457+ double distance = Math . Abs ( point . X - rect . Right ) ;
458+ if ( distance < bestDistance )
459+ {
460+ bestDistance = distance ;
461+ bestColumnIndex = key ;
462+ }
463+ }
464+
465+ columnIndex = bestColumnIndex ;
466+ return bestColumnIndex >= 0 ;
467+ }
468+ public void BeginColumnResize ( ResultSetViewItem resultSet , Visual relativeTo , Point point , int columnIndex )
469+ {
470+ if ( columnIndex < 0 ||
471+ columnIndex >= resultSet . Columns . Count ||
472+ ! _headerBorders . TryGetValue ( columnIndex , out Border ? border ) ||
473+ ! TryBuildHitRect ( border , relativeTo , out Rect rect ) )
474+ {
475+ return ;
476+ }
477+
478+ _isResizingColumn = true ;
479+ _isDraggingSelection = false ;
480+ _isDraggingCellSelection = false ;
481+ _isDraggingHeaderSelection = false ;
482+ _resizingColumnIndex = columnIndex ;
483+ _resizeStartX = point . X ;
484+ _resizeStartWidth = Math . Clamp ( rect . Width , MinDataColumnWidth , MaxDataColumnWidth ) ;
485+ resultSet . Columns [ columnIndex ] . ManualWidth = _resizeStartWidth ;
486+ }
487+ public bool UpdateColumnResize ( ResultSetViewItem resultSet , double pointerX , bool isLeftButtonPressed )
488+ {
489+ if ( ! _isResizingColumn || ! isLeftButtonPressed )
490+ {
491+ return false ;
492+ }
493+
494+ if ( _resizingColumnIndex < 0 || _resizingColumnIndex >= resultSet . Columns . Count )
495+ {
496+ EndColumnResize ( ) ;
497+ return false ;
498+ }
499+
500+ double width = Math . Clamp ( _resizeStartWidth + pointerX - _resizeStartX , MinDataColumnWidth , MaxDataColumnWidth ) ;
501+ resultSet . Columns [ _resizingColumnIndex ] . ManualWidth = width ;
502+ ApplyColumnWidth ( _resizingColumnIndex , width ) ;
503+ return true ;
504+ }
505+ public void EndColumnResize ( )
506+ {
507+ _isResizingColumn = false ;
508+ _resizingColumnIndex = - 1 ;
509+ _resizeStartX = 0 ;
510+ _resizeStartWidth = 0 ;
419511 }
420512 public bool TryGetHeaderColumnAtPoint ( Visual relativeTo , Point point , out int columnIndex , bool ignoreVerticalBounds = false )
421513 {
@@ -550,11 +642,29 @@ public double[] BuildColumnWidths(
550642
551643 widths [ visualIndex ] = columnIndex < 0
552644 ? 14
553- : Math . Clamp ( ( maxLength * 7 ) + 16 , 76 , 210 ) ;
645+ : resultSet . Columns [ columnIndex ] . ManualWidth is { } manualWidth
646+ ? Math . Clamp ( manualWidth , MinDataColumnWidth , MaxDataColumnWidth )
647+ : Math . Clamp ( ( maxLength * 7 ) + 16 , 76 , 210 ) ;
554648 }
555649
556650 return widths ;
557651 }
652+ private void ApplyColumnWidth ( int columnIndex , double width )
653+ {
654+ if ( ! _columnVisualIndexes . TryGetValue ( columnIndex , out int visualIndex ) )
655+ {
656+ return ;
657+ }
658+
659+ GridLength gridLength = new ( width ) ;
660+ foreach ( Grid grid in _scrollableGrids )
661+ {
662+ if ( visualIndex >= 0 && visualIndex < grid . ColumnDefinitions . Count )
663+ {
664+ grid . ColumnDefinitions [ visualIndex ] . Width = gridLength ;
665+ }
666+ }
667+ }
558668 private static int GetHeaderMeasurementLength ( ResultColumnViewItem column , Func < ResultColumnViewItem , string > headerFormatter )
559669 {
560670 string rawName = string . IsNullOrWhiteSpace ( column . RawName ) ? column . HeaderText : column . RawName ;
0 commit comments