unit BollingerGrid; interface Uses Controls, Grids, Classes, Types, Windows, Messages; Type // If a string is missing or invalid, it should be set to ''. If a double // is missing or invalid it should be set to NaN. TBollingerEntry = Record Symbol : String; Last : Double; PrevClose : Double; DistanceFromBB : Double; ReceivedTime : TDateTime; End; TBollingerEntries = Array Of TBollingerEntry; TBollingerGrid = Class(TDrawGrid) Private FEntries : TBollingerEntries; FExtraColumnName : String; Procedure SetColWidth(ACol : LongInt; Const SizeHint : String); Procedure WMNCCalcSize(var msg: TMessage); message WM_NCCALCSIZE; Procedure SetExtraColumnName(Value : String); Protected Procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); Override; Procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Override; Public // I make a copy of this so you can change the original. // The stuff you want to show up on top is entry 0 of the array. Procedure SetEntries(Entries : TBollingerEntries); // Presumably you just set visible = true, parent = self, and align = alClient. Constructor Create(AOwner: TWinControl); Reintroduce; // By default this is the distance from the bollinger band. Internally // the field names always reference this. But we can display something // else to the user. Property ExtraColumnName : String Read FExtraColumnName Write SetExtraColumnName; End; implementation // This entire unit is mostly a clone of QuickStrikeGrid.pas. Uses ExternalLinkingUnit, Math, Graphics, SysUtils; Const ColSymbol = 0; ColLast = 1; ColUpDay = 2; ColDistanceFromBB = 3; Procedure TBollingerGrid.SetExtraColumnName(Value : String); Begin FExtraColumnName := Value; InvalidateCell(ColDistanceFromBB, 0) End; Procedure TBollingerGrid.WMNCCalcSize(var msg: TMessage); Var Style : Integer; Begin // Disable the horizontal scroll bar to make it look more like what they // already had. // Code inspired by: http://www.delphi3000.com/articles/article_3254.asp style := getWindowLong( handle, GWL_STYLE ); If (Style And WS_HSCROLL) <> 0 Then SetWindowLong(Handle, GWL_STYLE, Style And Not WS_HSCROLL); Inherited End; Procedure TBollingerGrid.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); Var ClientRect : TRect; Function ClientWidth : Integer; Begin Result := ClientRect.Right - ClientRect.Left End; Function ClientHeight : Integer; Begin Result := ClientRect.Bottom - ClientRect.Top End; Procedure TextRightJustify(TextString : String); Var TextHeight, TextWidth : Integer; Begin TextHeight := Canvas.TextHeight(TextString); TextWidth := Canvas.TextWidth(TextString); Canvas.Brush.Style := bsClear; Canvas.TextRect(ClientRect, ClientRect.Left + Max(ClientWidth - TextWidth, 0), ClientRect.Top + Max(ClientHeight - TextHeight, 0) Div 2, TextString) End; Procedure TextLeftJustify(TextString : String); Var TextHeight : Integer; Begin TextHeight := Canvas.TextHeight(TextString); Canvas.Brush.Style := bsClear; Canvas.TextRect(ClientRect, ClientRect.Left, ClientRect.Top + Max(ClientHeight - TextHeight, 0) Div 2, TextString) End; Procedure TextCenter(TextString : String); Var TextHeight, TextWidth : Integer; Begin TextHeight := Canvas.TextHeight(TextString); TextWidth := Canvas.TextWidth(TextString); Canvas.TextRect(ClientRect, ClientRect.Left + Max(ClientWidth - TextWidth, 0) Div 2, ClientRect.Top + Max(ClientHeight - TextHeight, 0) Div 2, TextString); End; Function TooWide(TextString : String) : Boolean; Begin Result := Canvas.TextWidth(TextString) > ClientWidth End; Function MakeIntegerFit(I : Integer) : String; Procedure Adjust(Adjusted : Double; Symbol : Char); Begin Result := Format('%.2f%s', [Adjusted, Symbol]); If TooWide(Result) Then Begin Result := Format('%.1f%s', [Adjusted, Symbol]); If TooWide(Result) Then Begin Result := Format('%.0f%s', [Adjusted, Symbol]); If TooWide(Result) Then Result := '...' End End End; Begin Result := Format('%d', [I]); If TooWide(Result) Then If (I < 1000) Then Result := '...' Else If (I < 1000000) Then Adjust(I / 1.0e+3, 'K') Else Adjust(I / 1.0e+6, 'M') End; Function MakeStringFit(S : String) : String; Var Count, SpaceRemaining : Integer; Begin If TooWide(S) Then Begin Count := 1; SpaceRemaining := ClientWidth - Canvas.TextWidth('...'); While Count <= Length(S) Do Begin SpaceRemaining := SpaceRemaining - Canvas.TextWidth(S[Count]); If SpaceRemaining < 0 Then Break; Inc(Count) End; Result := Copy(S, 0, Pred(Count)) + '...'; End Else Result := S; End; Function MakePriceFit(P : Double) : String; Begin Result := Format('%f', [P]); If TooWide(Result) Then Result := '...' End; Function MakePercentFit(P : Double) : String; Begin Result := Format('%f%%', [P]); If TooWide(Result) Then Result := '...' End; Procedure SetFontColor(P : Double); Begin If P > 0.0 Then Canvas.Font.Color := $64ff64 // Green Else If P < 0.0 Then Canvas.Font.Color := $4b4bff // Red Else Canvas.Font.Color := clWhite End; Var DataIndex, DataTypeIndex : Integer; EntryValid : Boolean; Name : String; Begin { TBollingerGrid.DrawCell } DataIndex := Pred(ARow); EntryValid := (DataIndex >= 0) And (DataIndex < Length(FEntries)); DataTypeIndex := ACol; Canvas.Font.Style := []; If gdFixed In AState Then Canvas.Brush.Color := FixedColor Else Canvas.Brush.Color := Color; Canvas.FillRect(ARect); ClientRect := ARect; If (gdFixed In AState) And Ctl3D Then Begin DrawEdge(Canvas.Handle, ARect, BDR_RAISEDINNER, BF_BOTTOM Or BF_RIGHT Or BF_TOP Or BF_LEFT); Inc(ClientRect.Left); Inc(ClientRect.Top); Dec(ClientRect.Right); Dec(ClientRect.Bottom) End; If ClientWidth > 14 Then Begin Inc(ClientRect.Left, 3); Dec(ClientRect.Right, 3) End; If gdFixed In AState Then Begin Canvas.Font.Color := clBlack; Case DataTypeIndex Of ColSymbol : Name := 'Symbol'; ColLast : Name := 'Last'; ColUpDay : Name := 'Chg'; ColDistanceFromBB : Name := ExtraColumnName; Else Name := ''; End; TextCenter(MakeStringFit(Name)) { This is a column or row header. When we allow people to configure the sort operation, we should consider using the triangles from TSortableHeaderCellRenderer.DrawTriangle in MarketViewBase.Pas to show which row or column is the sort key, and the direction of the sort operation. } End Else If EntryValid Then Begin With FEntries[DataIndex] Do Case DataTypeIndex Of ColSymbol : Begin SetFontColor(0.0); TextLeftJustify(MakeStringFit(Symbol)) End; ColLast : If Not IsNaN(Last) Then Begin SetFontColor(Last); TextRightJustify(MakePriceFit(Last)) End; ColUpDay : If Not (IsNaN(Last) Or IsNaN(PrevClose)) Then Begin SetFontColor(Last - PrevClose); TextRightJustify(MakePriceFit(Last - PrevClose)) End; ColDistanceFromBB : If Not (IsNaN(DistanceFromBB)) Then Begin SetFontColor(DistanceFromBB); TextRightJustify(MakePriceFit(DistanceFromBB)) End; End End End; { TBollingerGrid.DrawCell } Procedure TBollingerGrid.SetEntries(Entries : TBollingerEntries); Begin FEntries := Entries; SetLength(FEntries, Length(FEntries)); RowCount := Succ(Max(1, Length(FEntries))); Invalidate End; Procedure TBollingerGrid.SetColWidth(ACol : LongInt; Const SizeHint : String); Begin If Ctl3D Then ColWidths[ACol] := 16 + Canvas.TextWidth(SizeHint) Else ColWidths[ACol] := 12 + Canvas.TextWidth(SizeHint) End; Procedure TBollingerGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Var DataIndex, Col, Row : Integer; Begin Inherited; If Button = mbLeft Then Begin MouseToCell(X, Y, Col, Row); DataIndex := Pred(Row); If (DataIndex >= 0) And (DataIndex < Length(FEntries)) Then ExternalLinkingWindow.SendSymbol(FEntries[DataIndex].Symbol) End End; Constructor TBollingerGrid.Create(AOwner: TWinControl); Begin Inherited Create(AOwner); DoubleBuffered := True; Color := clBlack; ExtraColumnName := 'BBand'; ColCount := 4; RowCount := 2; FixedRows := 1; FixedCols := 0; Options := [goColSizing, goThumbTracking]; DefaultDrawing := False; If Assigned(AOwner) Then Begin Parent := AOwner; Canvas.Font := Font; DefaultRowHeight := Canvas.TextHeight('Xy()') + 4; SetColWidth(ColSymbol, 'Symbol'); SetColWidth(ColLast, '100.00'); SetColWidth(ColUpDay, '-1.93'); SetColWidth(ColDistanceFromBB, '-13.3%') End; End; end.