home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1998 March
/
Chip_1998-03_cd.bin
/
zkuste
/
delphi
/
ruzkomp
/
SPLIT32.ZIP
/
SplitterWnd.pas
< prev
Wrap
Pascal/Delphi Source File
|
1997-07-03
|
40KB
|
1,132 lines
(*************************************************)
// 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.