home *** CD-ROM | disk | FTP | other *** search
- (*************************************************)
- // TSplitterWnd
- // by Chris Monson - finished 06/03/97
- // This is a very nice little control that you can
- // use to split up windows in Delphi 2.0, 3.0, or
- // C++ builder. You should have received a document
- // with this unit. If not, please contact me at
- // ckmonson@burgoyne.com.
- (***************************************************)
-
- unit SplitterWnd;
-
- interface
-
- uses
- Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
- ExtCtrls, DsgnIntf;
-
- const
- (*********** Defaults ***************************)
- DEFAULT_HORZPANESIZE = 50;
- DEFAULT_VERTPANESIZE = 75;
- DEFAULT_NUMPANES = 2;
- DEFAULT_THICKNESS = 3;
- DEFAULT_HEIGHT = 200;
- DEFAULT_WIDTH = 400;
- (************************************************)
-
- (*********** Design time constants **************)
- SplitterWndVerbs : array[0..1] of String =
- ('New Pane',
- 'Equalize Panes');
-
- SplitterWndNumVerbs = 2;
- (************************************************)
-
- type
- TSplitterWnd = class;
-
- (********************** TPane ***************************)
- (********************************************************)
- TPane = class(TScrollBox)
- private
- FSplitterWnd : TSplitterWnd;
- FDivPercent : Extended;
- FPaneIndex : Integer;
- FPaneSize,
- FMinPaneSize : Word;
- protected
- procedure WMMove( var Message : TWMMove ); message WM_MOVE;
- procedure WMSize( var Message : TWMSize ); message WM_SIZE;
- procedure SetSplitterWnd( sw : TSplitterWnd );
- procedure SetPaneIndex( pIndex : Integer );
- procedure SetPaneSize( ps : Word );
- public
- Constructor Create( AOwner : TComponent );override;
- Destructor Destroy;override;
- published
- property SplitterWnd : TSplitterWnd read FSplitterWnd write SetSplitterWnd;
- property PaneIndex : Integer read FPaneIndex write SetPaneIndex;
- property PaneSize : Word read FPaneSize write SetPaneSize stored True;
- property MinPaneSize : Word read FMinPaneSize write FMinPaneSize default 0;
- end;
-
- (********************** TSplitterWnd ********************)
- (********************************************************)
- TOrientation = ( swHorizontal, swVertical );
- TBarStyle = ( sbCheckered, sbSolid );
- TDrawDragRectEvent = procedure( Sender : TSplitterWnd;
- var DrawRect : TRect;
- var OwnerDraw : Boolean ) of object;
-
- TSplitterWnd = class(TCustomPanel)
- private
- FPanes : TList;
- FOrientation : TOrientation;
- FThickness : Byte;
- FCursorVert,
- FCursorHorz : TCursor;
- FProportionalResize : Boolean;
- FOnDrawDragRect : TDrawDragRectEvent;
- FOnEraseDragRect : TDrawDragRectEvent;
- FBarStyle : TBarStyle;
- FAllowSizing : Boolean;
-
- AllPanesLoaded : Boolean;
- BarDragging : Integer;
- OldMousePosition : TPoint;
- protected
- procedure MouseDown( Button: TMouseButton; Shift: TShiftState;
- X, Y: Integer); override;
- procedure MouseMove( Shift: TShiftState; X, Y: Integer); override;
- procedure MouseUp ( Button: TMouseButton; Shift: TShiftState;
- X, Y: Integer); override;
-
- procedure WMSize( var Message : TWMSize );message WM_SIZE;
- procedure AdjustLastPaneSize;
- procedure CreateWnd;override;
-
- procedure InsertPane( p : TPane );
- procedure RemovePane( p : TPane );
-
- procedure InvertBarRect( curRect : TRect );
-
- procedure CheckMouseBounds( var pos : TPoint );
-
- function GetMinimumPaneSize( bIndex : Word ) : Integer;
- function GetDifferenceBetweenPaneAndPoint( pIndex : Integer;
- pt : TPoint ):Integer;
- function GetDistance( bIndex : Integer ): Integer;
- function GetDefaultPaneSize : Integer;
- function GetRectAtDistance( d : Integer ) : TRect;
- function GetRectAtPoint( p : TPoint ) : TRect;
- function GetBarRect( bIndex : Integer ) : TRect;
-
- function GetPane( paneIndex : Byte):TPane;
- function GetNumPanes : Byte;
- function LeftBar( value : Integer ) : Integer;
- function RightBar( value : Integer ) : Integer;
- procedure SetOrientation( o : TOrientation );
- procedure SetThickness( t : Byte );
- procedure SetCursorHorz (ch : TCursor);
- procedure SetCursorVert (ch : TCursor);
-
- procedure MovePane( oldIndex, newIndex : Integer );
- procedure ResetPaneIndices;
- procedure RecalculatePaneSizes( IncludeLast : Boolean );
- procedure ResetPaneSizes;
- procedure UpdatePaneRects;
-
- procedure GetChildren( Proc : TGetChildProc );override;
- procedure SetChildOrder( Child : TComponent; Order : Integer );override;
- public
- Constructor Create( AOwner : TComponent );override;
- Destructor Destroy;override;
-
- property Panes[ paneIndex : Byte ] : TPane read GetPane;
- property NumPanes : Byte read GetNumPanes;
- published
- property Orientation : TOrientation read FOrientation write SetOrientation stored True;
- property Thickness : Byte read FThickness write SetThickness stored True;
- property CursorHorz : TCursor read FCursorHorz write SetCursorHorz stored True;
- property CursorVert : TCursor read FCursorVert write SetCursorVert stored True;
- property ProportionalResize : Boolean read FProportionalResize
- write FProportionalResize default True;
- property BarStyle : TBarStyle read FBarStyle write FBarStyle stored True;
- property AllowSizing : Boolean read FAllowSizing write FAllowSizing default True;
- {Events}
- property OnDrawDragRect : TDrawDragRectEvent read FOnDrawDragRect
- write FOnDrawDragRect;
- property OnEraseDragRect : TDrawDragRectEvent read FOnEraseDragRect
- write FOnEraseDragRect;
- { Redeclared properties }
- {Properties}
- property Align;
- property BevelInner;
- property BevelOuter;
- property BevelWidth;
- property BorderWidth;
- property BorderStyle;
- property Enabled;
- property Color;
- property Ctl3D;
- property ParentColor;
- property ParentCtl3D;
- property ParentShowHint;
- property ShowHint;
- property TabOrder;
- property TabStop;
- property Visible;
- {Events}
- property OnEnter;
- property OnExit;
- property OnMouseDown;
- property OnMouseMove;
- property OnMouseUp;
- property OnResize;
- end;
-
- (****************** TSplitterWndEditor ******************)
- (********************************************************)
- TSplitterWndEditor = class(TDefaultEditor)
- procedure ExecuteVerb(Index: Integer); override;
- function GetVerb(Index: Integer): string; override;
- function GetVerbCount: Integer; override;
- end;
-
- (***************** Globals *****************************)
- (*******************************************************)
-
- procedure Register;
-
- (*************************************************************************)
- (*************************************************************************)
-
- implementation
-
- (*************************************************************************)
- (***************************** TPane *************************************)
- (*************************************************************************)
- // Constructor TPane.Create
- // Initialize properties
- Constructor TPane.Create( AOwner : TComponent );
- begin
- inherited Create( AOwner );
- FMinPaneSize := 0;
- end;
-
- // Destructor TPane.Destroy
- // This makes sure that the pane is removed from the splitter window
- // when it is removed. Without doing that, we'll get all kinds of
- // access violations when the pane tries to access the panes.
- Destructor TPane.Destroy;
- begin
- if FSplitterWnd <> nil then FSplitterWnd.RemovePane(Self);
- inherited;
- end;
-
- // Procedure TPane.WMMove( var Message : TWMMove );
- // Windows message trap. Panes are not to be moved except by the
- // splitter window.
- procedure TPane.WMMove( var Message : TWMMove );
- begin
- inherited;
- if (csDesigning in ComponentState) and (FSplitterWnd <> nil) then
- FSplitterWnd.UpdatePaneRects;
- end;
-
- // procedure TPane.WMSize( var Message : TWMSize );
- // See reasons for this procedure in TPane.WMMove.
- procedure TPane.WMSize( var Message : TWMSize );
- begin
- inherited;
- if (csDesigning in ComponentState) and (FSplitterWnd <> nil) then
- FSplitterWnd.UpdatePaneRects;
- end;
-
- // procedure TPane.SetSplitterWnd( sw : TSplitterWnd );
- // sw : pointer to the splitter window to set
- // This procedure removes the pane from its old splitter window (if
- // one exists) and calls the new splitter window's InsertPane.
- procedure TPane.SetSplitterWnd( sw : TSplitterWnd );
- begin
- if (sw <> FSplitterWnd) then begin
- if FSplitterWnd <> nil then FSplitterWnd.RemovePane(Self);
- Parent := sw;
- // sw.InsertPane will set FSplitterWnd since these components are friends.
- if sw <> nil then sw.InsertPane(Self); // This will set FSplitterWnd
- end;
- end;
-
- // procedure TPane.SetPaneIndex( pIndex : Integer );
- // pIndex : new pane index.
- // This acts a lot like the tabindex property of a TTabSheet. It changes
- // the order of the pane in the splitter window.
- procedure TPane.SetPaneIndex( pIndex : Integer );
- var
- oldIndex : integer;
- begin
- if FSplitterWnd <> nil then
- begin
- oldIndex := PaneIndex;
- FSplitterWnd.MovePane(oldIndex, pIndex);
- end;
- end;
-
- // procedure TPane.SetPaneSize( ps : Word );
- // This is one of the more complex procedures. A short explanation
- // on its organization follows: The first few lines of code are going
- // to be executed before the SplitterWnd has been assigned, ie, when
- // the pane is being loaded. If the pane is being loaded, then it is
- // always the last one in the splitter window's list, which makes it
- // a special case and it won't size correctly. If the splitter window
- // has indeed been assigned, then the panes are all loaded, and this is
- // just being added to the splitter window. It may take a few look-overs
- // to understand just what I did here.
- procedure TPane.SetPaneSize( ps : Word );
- var
- OldPaneSize,
- MinPaneSizeThis,
- MinPaneSizeNext,
- MaxPaneSize,
- DualPaneSize : Integer;
- NextPane : TPane;
- begin
- { if the SplitterWnd has not yet been assigned, that means that the
- panes are being added, so no error checking and no pane size adjustment
- will be done yet. }
- if FSplitterWnd = nil then
- begin
- FPaneSize := ps;
- Exit;
- end;
-
- { if we get this far, that means a splitter window is assigned and the
- panes have all been loaded. The next section here makes sure that
- none of the panes go over or under their max's and min's. }
- if FSplitterWnd.NumPanes = 1 then
- begin
- Case FSplitterWnd.Orientation of
- swHorizontal : FPaneSize := FSplitterWnd.Height;
- swVertical : FPaneSize := FSplitterWnd.Width;
- end;
- end
- else if (FSplitterWnd.NumPanes > 1) and
- (PaneIndex < (FSplitterWnd.NumPanes-1)) then
- begin
- OldPaneSize := FPaneSize;
- NextPane := FSplitterWnd.Panes[PaneIndex+1];
-
- // Minimum is easy. It's just a matter of deciding on the orientation
- MinPaneSizeThis := FSplitterWnd.GetMinimumPaneSize(PaneIndex);
- MinPaneSizeNext := FSplitterWnd.GetMinimumPaneSize(PaneIndex+1);
-
- // Maximum is a little more complex. I have to check the size of the
- // neighboring pane and make sure IT won't be too small when this one
- // is enlarged.
- MaxPaneSize := OldPaneSize +
- (NextPane.PaneSize -
- FSplitterWnd.Thickness -
- MinPaneSizeNext);
- // Store the total size of this pane and the next pane so that both will
- // be sized correctly. ( Enlarging this one makes the next one smaller,
- // and making it smaller enlarges the neighboring pane. )
- DualPaneSize := PaneSize + NextPane.PaneSize;
- FPaneSize := ps; // ps was passed into the procedure
- // Reset the sizes to max or min if they went over or under
- if FPaneSize > MaxPaneSize then
- FPaneSize := MaxPaneSize
- else if FPaneSize < MinPaneSizeThis then
- FPaneSize := MinPaneSizeThis;
- // Make sure the pane next door is sized correctly
- NextPane.FPaneSize := DualPaneSize - FPaneSize;
- end;
- // There may be some errors in pane sizing - the last pane in the splitter
- // window will take up the slack
- FSplitterWnd.AdjustLastPaneSize;
- // Change the visual appearance of the panes.
- FSplitterWnd.UpdatePaneRects;
- end;
-
- (*************************************************************************)
- (***************************** TSplitterWnd ******************************)
- (*************************************************************************)
- { Overridden methods }
- // Constructor TSplitterWnd.Create( AOwner : TComponent );
- // Initialize things like the size, etc. Allocate memory
- // for the pane list, and set the AllPanesLoaded variable to
- // false. Certain things can't be done if the panes aren't all
- // loaded, yet.
- Constructor TSplitterWnd.Create( AOwner : TComponent );
- begin
- inherited Create(AOwner);
- // Visual stuff - caption is set later (CreateWnd) to prevent
- // it from showing during design-time.
- BevelOuter := bvNone;
- Height := DEFAULT_HEIGHT;
- Width := DEFAULT_WIDTH;
- FCursorVert := crHSplit;
- FCursorHorz := crVSplit;
- FThickness := DEFAULT_THICKNESS;
- FOrientation := swVertical;
- FProportionalResize := True;
- FAllowSizing := True;
- // Non-visual initialization
- FPanes := TList.Create;
- AllPanesLoaded := False;
- BarDragging := -1;
- end;
-
- // Destructor TSplitterWnd.Destroy;
- // Deallocate the panes list.
- Destructor TSplitterWnd.Destroy;
- begin
- FPanes.Free;
- inherited;
- end;
-
- // function TSplitterWnd.GetDifferenceBetweenPaneAndPoint(
- // pIndex : Integer; pt : TPoint ):Integer;
- //
- // pIndex : index in pane list of pane to check
- // pt : current point.
- // The purpose of this function is to find out how big
- // a pane should be given the current cursor position.
- // It returns a pixel value that is dependent on orientation.
- function TSplitterWnd.GetDifferenceBetweenPaneAndPoint(
- pIndex : Integer; pt : TPoint ):Integer;
- begin
- Case Orientation of
- swHorizontal : Result := pt.Y - GetDistance( pIndex );
- swVertical : Result := pt.X - GetDistance( pIndex );
- end;
- end;
-
- // function TSplitterWnd.GetDistance( bIndex : Integer ): Integer;
- // bIndex : index of pane to find the "distance" of.
- // The function returns the total distance from the left or top
- // of the splitter window to the right or bottom of the pane
- // specified in pixels.
- function TSplitterWnd.GetDistance( bIndex : Integer ): Integer;
- var
- curPane : Integer;
- begin
- Result := 0;
- For curPane := 0 to bIndex do
- Result := Result + Panes[curPane].PaneSize;
- end;
-
- // procedure TSplitterWnd.MouseDown( Button: TMouseButton; Shift: TShiftState;
- // Overridden mouseDown procedure. If the SplitterWnd receives any mouse
- // events at all, that means that the cursor is on a drag bar, since any
- // of the non-draggable areas of the window are covered by panes.
- // This sets the BarDragging variable, which specifies which bar is in
- // the dragging mode. It also calls the InvertBarRect procedure, which
- // draws an inverted bar on the splitter window and its children.
- procedure TSplitterWnd.MouseDown( Button: TMouseButton; Shift: TShiftState;
- X, Y: Integer);
- var
- curPane,
- curDistance : Integer;
- mousePoint : TPoint;
- curRect : TRect;
- OwnerDraw : Boolean;
- begin
- inherited MouseDown( Button, Shift, X, Y );
-
- BarDragging := -1;
- if (AllowSizing) then
- begin
- curDistance := 0;
- for curPane := 0 to NumPanes-2 do begin
- curDistance := curDistance + Panes[curPane].PaneSize;
- curRect := GetRectAtDistance( curDistance );
- mousePoint := Point( X, Y );
- if PtInRect( curRect, mousePoint ) then
- BarDragging := curPane;
- end;
- OldMousePosition := POINT( X, Y );
- // Draw the drag rectangle
- OwnerDraw := True;
- curRect := GetRectAtPoint( OldMousePosition );
- if Assigned( OnDrawDragRect ) then
- OnDrawDragRect( Self, curRect, OwnerDraw );
- if OwnerDraw then
- InvertBarRect( curRect );
- end;
- end;
-
- // procedure TSplitterWnd.MouseUp( Button: TMouseButton; Shift: TShiftState;
- // Overridden MouseUp procedure. If any splitter bars were in drag mode,
- // the pane sizes need to be updated according to where the user has
- // dropped the bar.
- procedure TSplitterWnd.MouseUp( Button: TMouseButton; Shift: TShiftState;
- X, Y: Integer);
- var
- OwnerDraw : Boolean;
- curRect : TRect;
- begin
- if BarDragging <> -1 then
- begin
- // Draw the drag rectangle
- OwnerDraw := True;
- curRect := GetRectAtPoint( OldMousePosition );
- if Assigned( OnEraseDragRect ) then
- OnEraseDragRect( Self, curRect, OwnerDraw );
- if OwnerDraw then
- InvertBarRect( curRect );
- // Change the pane size according to where the bar was dropped.
- Panes[BarDragging].PaneSize :=
- Panes[BarDragging].PaneSize +
- GetDifferenceBetweenPaneAndPoint( BarDragging, OldMousePosition );
- end;
- BarDragging := -1;
- inherited MouseUp( Button, Shift, X, Y );
- end;
-
- // procedure TSplitterWnd.MouseMove( Shift: TShiftState; X, Y: Integer);
- // Overridden MouseMove procedure. If any bars are dragging, then the
- // inverted rectangle needs to be updated. Boundary checking is also done
- // here so that the user can see just where the dragging limit is.
- procedure TSplitterWnd.MouseMove( Shift: TShiftState; X, Y: Integer);
- var
- tempMousePos : TPoint;
- oldRect, newRect,
- curRect : TRect;
- OwnerDraw : Boolean;
- begin
- inherited MouseMove( Shift, X, Y );
- if BarDragging <> -1 then
- begin
- tempMousePos := POINT( X, Y );
- CheckMouseBounds( tempMousePos );
- oldRect := GetRectAtPoint( OldMousePosition );
- newRect := GetRectAtPoint( tempMousePos );
- // Don't update the rectangles if out of bounds.
- // This will eliminate the flickering.
- if not EqualRect(oldRect,newRect) then begin
- // Erase the old rectangle
- OwnerDraw := True;
- curRect := GetRectAtPoint( OldMousePosition );
- if Assigned( OnEraseDragRect ) then
- OnEraseDragRect(Self, curRect, OwnerDraw);
- if OwnerDraw then
- InvertBarRect( curRect );
- // Draw the new rectangle
- OwnerDraw := True;
- curRect := GetRectAtPoint( tempMousePos );
- if Assigned( OnDrawDragRect ) then
- OnDrawDragRect(Self, curRect, OwnerDraw);
- if OwnerDraw then
- InvertBarRect( curRect );
- end;
- OldMousePosition := tempMousePos;
- end;
- end;
-
- // procedure TSplitterWnd.WMSize( var message : TWMSize );
- // Windows size message sent to the splitter window. All of the panes will
- // need to be resized. The recalculatePaneSizes procedure maintains
- // the size percentage of all of the panes.
- procedure TSplitterWnd.WMSize( var message : TWMSize );
- begin
- inherited;
- if ProportionalResize then
- RecalculatePaneSizes( True ) else
- AdjustLastPaneSize;
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.AdjustLastPaneSize;
- // This procedure is usually called after some pane sizes have been
- // changed. It makes the last pane exactly fill the remaining space
- // in the splitter window so that no weird effects happen near the
- // right/bottom edges of the window.
- procedure TSplitterWnd.AdjustLastPaneSize;
- var
- TotalPaneSize,
- FullWindowSize,
- curPane : Integer;
- begin
- if NumPanes = 0 then exit;
-
- Case Orientation of
- swHorizontal : FullWindowSize := Height;
- swVertical : FullWindowSize := Width;
- end;
- TotalPaneSize := 0;
- for curPane := 0 to NumPanes - 1 do
- TotalPaneSize := TotalPaneSize + Panes[curPane].PaneSize;
-
- if TotalPaneSize <> FullWindowSize
- then Panes[NumPanes-1].FPaneSize :=
- Panes[NumPanes-1].FPaneSize + (FullWindowSize - TotalPaneSize);
- end;
-
- // procedure TSplitterWnd.CreateWnd;
- // By the time this procedure is called, all of the panes have
- // been loaded and the caption has been auto-set. This overrides
- // the caption and allows some of the special functions to work
- // that only apply when all of the panes have been loaded.
- procedure TSplitterWnd.CreateWnd;
- begin
- inherited;
- Caption := '';
- AllPanesLoaded := True;
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.InsertPane( p : TPane );
- // This inserts a new pane into the control. If the new pane is
- // being inserted by the compiler (ie, being loaded), then the
- // new pane sizes should NOT be recalculated. If it is being inserted
- // by the developer through the design interface or during runtime,
- // the pane sizes will be recalculated to give it space.
- procedure TSplitterWnd.InsertPane( p : TPane );
- var
- NewPosition : Integer;
- begin
- NewPosition := FPanes.Add(p);
- p.FSplitterWnd := Self;
- p.FPaneIndex := NewPosition;
- if (AllPanesLoaded) then
- RecalculatePaneSizes(False);
- end;
-
- // procedure TSplitterWnd.RemovePane( p : TPane );
- // This takes a pane out of the control, but does NOT free up
- // the resources associated with it. It resets the pane indices
- // so there are no "holes" in the PaneIndex properties, and it
- // recalculates the pane sizes so they fill up the splitter window.
- procedure TSplitterWnd.RemovePane( p : TPane );
- begin
- if FPanes.IndexOf(p) <> -1 then
- begin
- p.FSplitterWnd := nil;
- p.FPaneIndex := -1;
- FPanes.Remove(p);
- ResetPaneIndices;
- if ProportionalResize then
- RecalculatePaneSizes( True ) else
- AdjustLastPaneSize;
- end;
- end;
-
- // procedure TSplitterWnd.CheckMouseBounds( var pos : TPoint );
- // This simply checks to see if the mouse can drag the bar any further
- // than it already has. If it can, it will. Otherwise, it will not.
- procedure TSplitterWnd.CheckMouseBounds( var pos : TPoint );
- var
- MinPaneSizeThis,
- MinPaneSizeNext,
- PrevDistance,
- NextDistance : Integer;
- begin
- MinPaneSizeThis := GetMinimumPaneSize( BarDragging );
- MinPaneSizeNext := GetMinimumPaneSize( BarDragging + 1 );
- if BarDragging > 0 then
- PrevDistance := GetDistance( BarDragging - 1) + MinPaneSizeThis else
- PrevDistance := MinPaneSizeThis;
- NextDistance := GetDistance( BarDragging + 1 ) - MinPaneSizeNext;
-
- Case Orientation of
- swHorizontal :
- begin
- if pos.Y < PrevDistance then
- pos.Y := PrevDistance;
- if pos.Y > NextDistance then
- pos.Y := NextDistance;
- end;
- swVertical :
- begin
- if pos.X < PrevDistance then
- pos.X := PrevDistance;
- if pos.X > NextDistance then
- pos.X := NextDistance;
- end;
- end;
- end;
-
- // function TSplitterWnd.GetMinimumPaneSize:Integer;
- // This simply returns twice the size of a scroll bar if the
- // MinPaneSize property is 0 and FMinPaneSize if not. The type
- // of scroll bar is determined by the orientation.
- function TSplitterWnd.GetMinimumPaneSize( bIndex : Word ):Integer;
- begin
- if (Panes[bIndex].MinPaneSize = 0) then
- begin
- Case Orientation of
- swHorizontal : Result := GetSystemMetrics( SM_CYHSCROLL )*2;
- swVertical : Result := GetSystemMetrics( SM_CXVSCROLL )*2;
- end;
- end
- else
- Result := Panes[bIndex].MinPaneSize;
- end;
-
- // function TSplitterWnd.GetDefaultPaneSize:Integer;
- // Just a wrapper for the constants found at the beginning of the
- // unit. It was cumbersome to put a lot of case statements into
- // the code, so they are found in all of these little functions.
- function TSplitterWnd.GetDefaultPaneSize:Integer;
- begin
- Case Orientation of
- swHorizontal :
- if NumPanes < 1 then
- Result := Height else
- Result := DEFAULT_HORZPANESIZE;
- swVertical :
- if NumPanes < 1 then
- Result := Width else
- Result := DEFAULT_VERTPANESIZE;
- end;
- end;
-
- // procedure TSplitterWnd.InvertBarRect( pos : TPoint );
- // This draws an inverted rectangle on the splitter window and its
- // children. I used a call to GetDCEx to make the rectangle draw
- // itself over the children and the parent. Otherwise, the rectangle
- // would have been hidden by any children placed on the splitter window,
- // including the panes themselves.
- procedure TSplitterWnd.InvertBarRect( curRect : TRect );
- var
- DC : HDC;
- grayPattern : array [0..8] of WORD; { I have added this }
- grayBitmap : HBITMAP;
- halftoneBrush : HBRUSH;
- oldobject : HBRUSH;
- i : Integer;
- begin
- DC := GetDCEx( Handle, 0, DCX_CACHE or DCX_PARENTCLIP );
-
- Case BarStyle of
- sbSolid : InvertRect( DC, curRect );
- sbCheckered : begin
- for i:= 0 to 8 do
- grayPattern[i] := WORD($5555 shl (i AND 1));
- grayBitmap := CreateBitmap(8, 8, 1, 1, @grayPattern);
- if (grayBitmap <> 0) then
- begin
- halftoneBrush := CreatePatternBrush(grayBitmap);
- DeleteObject(grayBitmap);
- end;
- oldobject := SelectObject(DC, halftoneBrush);
- PatBlt( DC,
- curRect.Left,
- curRect.Top,
- curRect.Right - curRect.Left,
- curRect.Bottom - curRect.Top,
- PATINVERT);
- SelectObject(DC,oldobject);
- DeleteObject(halftoneBrush);
- end; // sbCheckered case
- end; // Case
- ReleaseDC( Handle, DC );
- end;
-
- // function TSplitterWnd.GetRectAtDistance( d : Integer ) : TRect;
- // d : pixel distance from left or top of splitter window.
- // This function returns a rectangle that will show when the mouse
- // is dragging a bar around.
- function TSplitterWnd.GetRectAtDistance( d : Integer ) : TRect;
- begin
- Case Orientation of
- swHorizontal :
- Result := RECT( 0,
- d - LeftBar(Thickness),
- Width,
- d + RightBar(Thickness)+1);
- swVertical :
- Result := RECT( d - LeftBar(Thickness),
- 0,
- d + RightBar(Thickness)+1,
- Height );
- end;
- end;
-
- // function TSplitterWnd.GetRectAtPoint( p : TPoint ) : TRect;
- // p : point at which to find the rectangle.
- // This is very similar to GetRectAtDistance, except it works
- // on a point.
- function TSplitterWnd.GetRectAtPoint( p : TPoint ) : TRect;
- begin
- Case Orientation of
- swHorizontal :
- Result := RECT( 0,
- p.Y - LeftBar(Thickness),
- Width,
- p.Y + RightBar(Thickness)+1);
- swVertical :
- Result := RECT( p.X - LeftBar(Thickness),
- 0,
- p.X + RightBar(Thickness)+1,
- Height );
- end;
- end;
-
- // function TSplitterWnd.GetBarRect( bIndex : Integer ) : TRect;
- // bIndex : Bar Index. Which bar to get the rectangle for.
- // This returns the bar's rectangle so that panes can be resized
- // after the dragging operation has stopped.
- function TSplitterWnd.GetBarRect( bIndex : Integer ) : TRect;
- var
- curPane,
- distance : Integer;
- begin
- curPane := 0;
- distance := 0;
- While (curPane <= bIndex) do
- begin
- distance := distance + Panes[curPane].PaneSize;
- inc(curPane);
- end;
- Result := GetRectAtDistance( distance );
- end;
-
- // function TSplitterWnd.GetPane( paneIndex : Byte ) : TPane;
- // Read Property function for the Panes array property.
- function TSplitterWnd.GetPane( paneIndex : Byte ) : TPane;
- begin
- Result := FPanes[paneIndex];
- end;
-
-
- // function TSplitterWnd.GetNumPanes : Byte;
- // Gets the number of panes. A read property function.
- function TSplitterWnd.GetNumPanes : Byte;
- begin
- Result := FPanes.Count;
- end;
-
- // procedure TSplitterWnd.SetOrientation( o : TOrientation );
- // Changes the orientation of the splitter window. It is
- // the Orientation property's write procedure. This procedure
- // does a couple of important things. It sets the pane sizes
- // to the same relative percentage that they had in the previous
- // orientation (with some error, I would imagine) and it changes
- // the SplitterWnd cursor.
- procedure TSplitterWnd.SetOrientation( o : TOrientation );
- var
- curPane,
- TotalPaneSize : Integer;
- Pane : TPane;
- begin
- if o = FOrientation then exit;
- FOrientation := o;
-
- if NumPanes = 0 then exit;
-
- TotalPaneSize := 0;
- For curPane := 0 to NumPanes - 1 do
- begin
- Pane := TPane(FPanes[curPane]);
- Case FOrientation of
- swHorizontal : // Changing from vertical to horizontal
- begin
- Pane.FPaneSize := Round( (Pane.PaneSize / Width) * Height );
- cursor := CursorHorz;
- end;
- swVertical : // Changing from horizontal to vertical
- begin
- Pane.FPaneSize := Round( (Pane.PaneSize / Height) * Width );
- cursor := CursorVert;
- end;
- end; // Case
- TotalPaneSize := TotalPaneSize + Pane.PaneSize;
- end;
- AdjustLastPaneSize;
- { All errors have been accounted for, so now update the pane rectangles }
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.SetThickness( t : Byte );
- // Thickness write property. Updates the pane appearance
- // to account for a thicker or thinner drag bar.
- procedure TSplitterWnd.SetThickness( t : Byte );
- begin
- if t < 1 then t := 1;
- FThickness := t;
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.SetCursorHorz( ch : TCursor );
- // Sets the CursorHorz property. If the orientation
- // is correct, it also sets the cursor.
- procedure TSplitterWnd.SetCursorHorz( ch : TCursor );
- begin
- if Orientation = swHorizontal
- then Cursor := ch;
- FCursorHorz := ch;
- end;
-
- // procedure TSplitterWnd.SetCursorVert( ch : TCursor );
- // Sets the CursorVert property. See SetCursorHorz.
- procedure TSplitterWnd.SetCursorVert( ch : TCursor );
- begin
- if Orientation = swVertical
- then Cursor := ch;
- FCursorVert := ch;
- end;
-
- // function TSplitterWnd.LeftBar( value : Integer ):Integer;
- // Gets the left half width (or top half) of the splitter bars.
- // The bar positions are defined by their centers, so this
- // function truncates the floating point value, and the right
- // function rounds it to account for odd thicknesses.
- function TSplitterWnd.LeftBar( value : Integer ):Integer;
- begin
- Result := Trunc( value / 2 );
- end;
-
- // function TSplitterWnd.RightBar( value : Integer ):Integer;
- // See TSplitterWnd.LeftBar.
- function TSplitterWnd.RightBar( value : Integer ):Integer;
- begin
- Result := Round( value / 2 );
- end;
-
- // procedure TSplitterWnd.MovePane( oldIndex, newIndex : Integer );
- // Swaps pane positions in the list and changes their indices.
- // This allows for panes to be moved around if desired.
- procedure TSplitterWnd.MovePane( oldIndex, newIndex : Integer );
- begin
- FPanes.Move(oldIndex, newIndex);
- ResetPaneIndices;
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.UpdatePaneRects;
- // This procedure is the meat and bones of the visual appearance of the
- // splitter window. It checks all of the pane sizes and it draws them
- // correctly on the splitter window. Depending on the orientation and
- // whether or not a pane is the first or last, special cases come into
- // consideration, since the bar positions are calculated according to
- // their middles and not their edges.
- procedure TSplitterWnd.UpdatePaneRects;
- var
- curPane : Integer;
- curRect : TRect;
- Left, Top, Right, Bottom : Integer;
- Pane,
- PrevPane : TPane;
- begin
- if (NumPanes = 1) then begin
- TPane(FPanes[0]).SetBounds(0,0,Width,Height);
- exit;
- end;
-
- For curPane := 0 to NumPanes-1 do begin
- Pane := TPane(FPanes[curPane]);
- if curPane = 0 then
- PrevPane := TPane(FPanes[curPane]) else
- PrevPane := TPane(FPanes[curPane-1]);
-
- case Orientation of
- swHorizontal :
- begin
- if curPane = 0 then
- Pane.SetBounds(
- 0,
- 0,
- Width,
- Pane.PaneSize - LeftBar(Thickness) )
- else if curPane = (NumPanes-1) then
- Pane.SetBounds(
- 0,
- PrevPane.Top + PrevPane.Height + Thickness,
- Width,
- Pane.PaneSize - RightBar(Thickness) )
- else // one of the middle panes
- Pane.SetBounds(
- 0,
- PrevPane.Top + PrevPane.Height + Thickness,
- Width,
- Pane.PaneSize - Thickness);
- end;
- swVertical :
- begin
- if curPane = 0 then
- Pane.SetBounds(
- 0,
- 0,
- Pane.PaneSize - LeftBar(Thickness),
- Height )
- else if curPane = (NumPanes-1) then
- Pane.SetBounds(
- PrevPane.Left + PrevPane.Width + Thickness,
- 0,
- Pane.PaneSize - RightBar(Thickness),
- Height )
- else // one of the middle panes
- Pane.SetBounds(
- PrevPane.Left + PrevPane.Width + Thickness,
- 0,
- Pane.PaneSize - Thickness,
- Height );
- end;
- end; // Case
- end;
- end;
-
- // procedure TSplitterWnd.ResetPaneIndices;
- // Very simple. Goes through the list and assigns the pane indices
- // to their indices in the list. The panes are always in order.
- procedure TSplitterWnd.ResetPaneIndices;
- var
- curPane : Integer;
- begin
- For curPane := 0 to NumPanes-1 do begin
- Panes[curPane].FPaneIndex := curPane;
- end;
- end;
-
- // procedure TSplitterWnd.RecalculatePaneSizes( IncludeLast : Boolean );
- // IncludeLast : this is set to True if ALL of the panes are to be
- // recalculated, and it is set to False if all but the last pane are
- // to be resized. The IncludeLast parameter is always FALSE when a new
- // pane has just been added and the others are shifting around to get
- // out of its way. This allows for immediate deletion of the new pane
- // without disturbing the original positions of the preceding panes.
- procedure TSplitterWnd.RecalculatePaneSizes( IncludeLast : Boolean );
- var
- TotalPaneSize,
- FullWindowSize,
- curPane,
- HighPane : Integer;
- begin
- Case Orientation of
- swHorizontal : FullWindowSize := Height;
- swVertical : FullWindowSize := Width;
- end;
- if IncludeLast then
- HighPane := NumPanes - 1 else
- begin
- HighPane := NumPanes - 2;
- FullWindowSize := FullWindowSize - Panes[NumPanes-1].PaneSize;
- end;
-
- if HighPane >= 0 then
- begin
- TotalPaneSize := 0;
- for curPane := 0 to HighPane do
- TotalPaneSize := TotalPaneSize + Panes[curPane].PaneSize;
-
- { The total pane size will include all of the panes unless the last is
- excluded. The FullWindowSize variable is the size of the full window
- size in which the specified panes should fit. For example, if there
- are three panes, and then one is added, the three panes already fill
- the entire window. The new window size for those three panes will be
- the size of the splitter window minus the size of the last pane that
- was recently added. The remaining panes will fit inside of the new
- window size. Their relative size percentages will remain the same. }
- for curPane := 0 to HighPane do
- Panes[curPane].FPaneSize := Round( Panes[curPane].FPaneSize *
- ( FullWindowSize / TotalPaneSize ) );
- end;
- AdjustLastPaneSize;
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.ResetPaneSizes;
- // This sets all of the pane sizes the same.
- procedure TSplitterWnd.ResetPaneSizes;
- var
- curPane,
- TotalPaneSize : Integer;
- Pane : TPane;
- begin
- if NumPanes = 0 then exit;
-
- TotalPaneSize := 0;
- For curPane := 0 to NumPanes-1 do
- begin
- Case Orientation of
- swHorizontal :
- Panes[curPane].FPaneSize := Round(Height/NumPanes);
- swVertical :
- Panes[curPane].FPaneSize := Round(Width /NumPanes);
- end;
- TotalPaneSize := TotalPaneSize + TPane(FPanes[curPane]).PaneSize;
- end;
-
- AdjustLastPaneSize;
- { All errors have been accounted for, so now update the pane rectangles }
- UpdatePaneRects;
- end;
-
- // procedure TSplitterWnd.GetChildren( Proc : TGetChildProc );
- // I don't know why this is in here, but it has to be
- // to avoid access violations at design time when loading
- // panes. Oh, well. It works.
- procedure TSplitterWnd.GetChildren( Proc : TGetChildProc );
- var
- I: Integer;
- begin
- for I := 0 to FPanes.Count - 1 do Proc(TComponent(FPanes[I]));
- end;
-
- // procedure TSplitterWnd.SetChildOrder(Child: TComponent; Order: Integer);
- // Just sets the child order. I am not sure it even needs to be here.
- procedure TSplitterWnd.SetChildOrder(Child: TComponent; Order: Integer);
- begin
- TPane(Child).PaneIndex := Order;
- end;
-
- (*************************************************************************)
- (************************** TSplitterWndEditor ***************************)
- (*************************************************************************)
- procedure TSplitterWndEditor.ExecuteVerb(Index: Integer);
- var
- SplitterWnd : TSplitterWnd;
- Pane : TPane;
- Designer : TFormDesigner;
- begin
- // Make sure that the context menu will still work when clicking on a pane
- if Component is TSplitterWnd
- then SplitterWnd := TSplitterWnd(Component)
- else SplitterWnd := TPane(Component).SplitterWnd;
- Designer := Self.Designer;
- if Index = 0 then
- begin
- if SplitterWnd <> nil
- then begin
- // Create a new pane.
- Pane := TPane.Create(Designer.Form);
- try
- Pane.Name := Designer.UniqueName(TPane.ClassName);
- Pane.Parent := SplitterWnd;
- Pane.SplitterWnd := SplitterWnd;
- Pane.FPaneSize := SplitterWnd.GetDefaultPaneSize;
- SplitterWnd.RecalculatePaneSizes(False);
- except
- Pane.Free;
- raise;
- end;
- // Select the new pane
- Designer.SelectComponent(Pane);
- // Make the save icon change colors
- Designer.Modified;
- end;
- end
- // Set all of the pane sizes the same
- else if Index = 1 then
- begin
- SplitterWnd.ResetPaneSizes;
- end;
- end;
-
- function TSplitterWndEditor.GetVerb(Index: Integer): string;
- begin
- Result := SplitterWndVerbs[Index];
- end;
-
- function TSplitterWndEditor.GetVerbCount: Integer;
- begin
- Result := SplitterWndNumVerbs;
- end;
-
- (*************************************************************************)
- (***************************** Register **********************************)
- (*************************************************************************)
- procedure Register;
- begin
- RegisterComponents('Added', [TSplitterWnd]);
- RegisterClasses([TPane]);
-
- RegisterComponentEditor(TSplitterWnd, TSplitterWndEditor);
- RegisterComponentEditor(TPane, TSplitterWndEditor);
- end;
-
- end.
-