fpspreadsheet: Avoid duplicate code in TsWorkbookChartSource. Fix crashes of the chartsource when used worksheets are renamed or deleted. More refined notification of visual controls.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3867 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-01-05 23:32:49 +00:00
parent 41fe618242
commit 2c81897ffc
8 changed files with 409 additions and 139 deletions

View File

@ -28,10 +28,13 @@
<FormatVersion Value="1"/> <FormatVersion Value="1"/>
</local> </local>
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="2">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="TAChartLazarusPkg"/>
</Item1> </Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages> </RequiredPackages>
<Units Count="2"> <Units Count="2">
<Unit0> <Unit0>

View File

@ -7,7 +7,7 @@ uses
cthreads, cthreads,
{$ENDIF}{$ENDIF} {$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset Interfaces, // this includes the LCL widgetset
Forms, main; Forms, tachartlazaruspkg, main;
{$R *.res} {$R *.res}

View File

@ -51,10 +51,13 @@
<FormatVersion Value="1"/> <FormatVersion Value="1"/>
</local> </local>
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="2">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="TAChartLazarusPkg"/>
</Item1> </Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages> </RequiredPackages>
<Units Count="2"> <Units Count="2">
<Unit0> <Unit0>

View File

@ -7,7 +7,7 @@ uses
cthreads, cthreads,
{$ENDIF}{$ENDIF} {$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset Interfaces, // this includes the LCL widgetset
Forms, mainfrm Forms, tachartlazaruspkg, mainfrm
{ you can add units after this }; { you can add units after this };
{$R *.res} {$R *.res}

View File

@ -969,8 +969,10 @@ type
FOnWriteCellData: TsWorkbookWriteCellDataEvent; FOnWriteCellData: TsWorkbookWriteCellDataEvent;
FOnReadCellData: TsWorkbookReadCellDataEvent; FOnReadCellData: TsWorkbookReadCellDataEvent;
FOnChangeWorksheet: TsWorksheetEvent; FOnChangeWorksheet: TsWorksheetEvent;
FOnRenameWorksheet: TsWorksheetEvent;
FOnAddWorksheet: TsWorksheetEvent; FOnAddWorksheet: TsWorksheetEvent;
FOnRemoveWorksheet: TsRemoveWorksheetEvent; FOnRemoveWorksheet: TsRemoveWorksheetEvent;
FOnRemovingWorksheet: TsWorksheetEvent;
FOnSelectWorksheet: TsWorksheetEvent; FOnSelectWorksheet: TsWorksheetEvent;
FOnChangePalette: TNotifyEvent; FOnChangePalette: TNotifyEvent;
FFileName: String; FFileName: String;
@ -1033,6 +1035,14 @@ type
function ValidWorksheetName(var AName: String; function ValidWorksheetName(var AName: String;
ReplaceDuplicateName: Boolean = false): Boolean; ReplaceDuplicateName: Boolean = false): Boolean;
{ String-to-cell/range conversion }
function TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
out ARow,ACol: Cardinal; AListSeparator: Char = #0): Boolean;
function TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
out ARange: TsCellRange; AListSeparator: Char = #0): Boolean;
function TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
out ARanges: TsCellRangeArray; AListSeparator: Char = #0): Boolean;
{ Font handling } { Font handling }
function AddFont(const AFontName: String; ASize: Single; function AddFont(const AFontName: String; ASize: Single;
AStyle: TsFontStyles; AColor: TsColor): Integer; overload; AStyle: TsFontStyles; AColor: TsColor): Integer; overload;
@ -1093,8 +1103,12 @@ type
property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet; property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet;
{@@ This event fires whenever a workbook is loaded } {@@ This event fires whenever a workbook is loaded }
property OnOpenWorkbook: TNotifyEvent read FOnOpenWorkbook write FOnOpenWorkbook; property OnOpenWorkbook: TNotifyEvent read FOnOpenWorkbook write FOnOpenWorkbook;
{@@ This event fires when a worksheet is deleted } {@@ This event fires whenever a worksheet is renamed }
property OnRenameWorksheet: TsWorksheetEvent read FOnRenameWorksheet write FOnRenameWorksheet;
{@@ This event fires AFTER a worksheet has been deleted }
property OnRemoveWorksheet: TsRemoveWorksheetEvent read FOnRemoveWorksheet write FOnRemoveWorksheet; property OnRemoveWorksheet: TsRemoveWorksheetEvent read FOnRemoveWorksheet write FOnRemoveWorksheet;
{@@ This event fires BEFORE a worksheet is deleted }
property OnRemovingWorksheet: TsWorksheetEvent read FOnRemovingWorksheet write FOnRemovingWorksheet;
{@@ This event fires when a worksheet is made "active"} {@@ This event fires when a worksheet is made "active"}
property OnSelectWorksheet: TsWorksheetEvent read FOnSelectWorksheet write FOnSelectWorksheet; property OnSelectWorksheet: TsWorksheetEvent read FOnSelectWorksheet write FOnSelectWorksheet;
{@@ This event allows to provide external cell data for writing to file, {@@ This event allows to provide external cell data for writing to file,
@ -3597,7 +3611,7 @@ begin
begin begin
FName := AName; FName := AName;
if (FWorkbook.FLockCount = 0) and Assigned(FWorkbook.FOnChangeWorksheet) then if (FWorkbook.FLockCount = 0) and Assigned(FWorkbook.FOnChangeWorksheet) then
FWorkbook.FOnChangeWorksheet(FWorkbook, self); FWorkbook.FOnRenameWorksheet(FWorkbook, self);
end; end;
end; end;
@ -7098,6 +7112,8 @@ begin
i := GetWorksheetIndex(AWorksheet); i := GetWorksheetIndex(AWorksheet);
if (i <> -1) and (AWorksheet <> nil) then if (i <> -1) and (AWorksheet <> nil) then
begin begin
if Assigned(FOnRemovingWorksheet) then
FOnRemovingWorksheet(self, AWorksheet);
FWorksheets.Delete(i); FWorksheets.Delete(i);
AWorksheet.Free; AWorksheet.Free;
if Assigned(FOnRemoveWorksheet) then if Assigned(FOnRemoveWorksheet) then
@ -7199,6 +7215,150 @@ begin
end; end;
*) *)
{ String-to-cell/range conversion }
{@@ ----------------------------------------------------------------------------
Analyses a string which can contain an array of cell ranges along with a
worksheet name. Extracts the worksheet (if missing the "active" worksheet of
the workbook is returned) and the cell's row and column indexes.
@param AText General cell range string in Excel notation,
i.e. worksheet name + ! + cell in A1 notation.
Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
@param AWorksheet Pointer to the worksheet referred to by AText. If AText
does not contain the worksheet name, the active worksheet
of the workbook is returned
@param ARow, ACol Zero-based row and column index of the cell identified
by ATest. If AText contains one ore more cell ranges
then the upper left corner of the first range is returned.
@param AListSeparator Character to separate the cell blocks in the text
If #0 then the ListSeparator of the workbook's FormatSettings
is used.
@returns TRUE if AText is a valid list of cell ranges, FALSE if not. If the
result is FALSE then AWorksheet, ARow and ACol may have unpredictable
values.
-------------------------------------------------------------------------------}
function TsWorkbook.TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
out ARow,ACol: Cardinal; AListSeparator: Char = #0): Boolean;
var
ranges: TsCellRangeArray;
begin
Result := TryStrToCellRanges(AText, AWorksheet, ranges, AListSeparator);
if Result then
begin
ARow := ranges[0].Row1;
ACol := ranges[0].Col1;
end;
end;
{@@ ----------------------------------------------------------------------------
Analyses a string which can contain an array of cell ranges along with a
worksheet name. Extracts the worksheet (if missing the "active" worksheet of
the workbook is returned) and the cell range (or the first cell range, if there
are several ranges).
@param AText General cell range string in Excel notation,
i.e. worksheet name + ! + cell in A1 notation.
Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
@param AWorksheet Pointer to the worksheet referred to by AText. If AText
does not contain the worksheet name, the active worksheet
of the workbook is returned
@param ARange TsCellRange records identifying the cell block. If AText
contains several cell ranges the first one is returned.
@param AListSeparator Character to separate the cell blocks in the text
If #0 then the ListSeparator of the workbook's FormatSettings
is used.
@returns TRUE if AText is a valid cell range, FALSE if not. If the
result is FALSE then AWorksheet and ARange may have unpredictable
values.
-------------------------------------------------------------------------------}
function TsWorkbook.TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
out ARange: TsCellRange; AListSeparator: Char = #0): Boolean;
var
ranges: TsCellRangeArray;
begin
Result := TryStrToCellRanges(AText, AWorksheet, ranges, AListSeparator);
if Result then ARange := ranges[0];
end;
{@@ ----------------------------------------------------------------------------
Analyses a string which can contain an array of cell ranges along with a
worksheet name. Extracts the worksheet (if missing the "active" worksheet of
the workbook is returned) and the range array.
@param AText General cell range string in Excel notation,
i.e. worksheet name + ! + cell in A1 notation.
Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
@param AWorksheet Pointer to the worksheet referred to by AText. If AText
does not contain the worksheet name, the active worksheet
of the workbook is returned
@param ARanges Array of TsCellRange records identifying the cell blocks
@param AListSeparator Character to separate the cell blocks in the text
If #0 then the ListSeparator of the workbook's FormatSettings
is used.
@returns TRUE if AText is a valid list of cell ranges, FALSE if not. If the
result is FALSE then AWorksheet and ARanges may have unpredictable
values.
-------------------------------------------------------------------------------}
function TsWorkbook.TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
out ARanges: TsCellRangeArray; AListSeparator: Char = #0): Boolean;
var
i: Integer;
s: String;
L: TStrings;
begin
Result := false;
AWorksheet := nil;
SetLength(ARanges, 0);
if AText = '' then
exit;
i := pos(SHEETSEPARATOR, AText);
if i = 0 then
AWorksheet := FActiveWorksheet
else begin
AWorksheet := GetWorksheetByName(Copy(AText, 1, i-1));
if AWorksheet = nil then
exit;
AText := Copy(AText, i+1, Length(AText));
end;
L := TStringList.Create;
try
if AListSeparator = #0 then
L.Delimiter := FormatSettings.ListSeparator
else
L.Delimiter := AListSeparator;
L.StrictDelimiter := true;
L.DelimitedText := AText;
if L.Count = 0 then
begin
AWorksheet := nil;
exit;
end;
SetLength(ARanges, L.Count);
for i:=0 to L.Count-1 do begin
if pos(':', L[i]) = 0 then begin
Result := ParseCellString(L[i], ARanges[i].Row1, ARanges[i].Col1);
if Result then begin
ARanges[i].Row2 := ARanges[i].Row1;
ARanges[i].Col2 := ARanges[i].Col1;
end;
end else
Result := ParseCellRangeString(L[i], ARanges[i]);
if not Result then begin
SetLength(ARanges, 0);
AWorksheet := nil;
exit;
end;
end;
finally
L.Free;
end;
end;
{ Font handling } { Font handling }
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------

View File

@ -1,11 +1,15 @@
{ { fpspreadsheetchart.pas }
fpspreadsheetgrid.pas
Chart data source designed to work together with TChart from Lazarus to display the data {@@ ----------------------------------------------------------------------------
and with TsWorksheetGrid from FPSpreadsheet to load data from a grid. Chart data source designed to work together with TChart from Lazarus
to display the data and with FPSpreadsheet to load data.
AUTHORS: Felipe Monteiro de Carvalho, Werner Pamler
LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus
distribution, for details about the license.
-------------------------------------------------------------------------------}
AUTHORS: Felipe Monteiro de Carvalho
}
unit fpspreadsheetchart; unit fpspreadsheetchart;
{$mode objfpc}{$H+} {$mode objfpc}{$H+}
@ -15,11 +19,11 @@ interface
uses uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
// TChart // TChart
{tasources,} TACustomSource, TACustomSource,
// FPSpreadsheet Visual
fpspreadsheetctrls, fpspreadsheetgrid,
// FPSpreadsheet // FPSpreadsheet
fpspreadsheet, fpsutils; fpspreadsheet, fpsutils,
// FPSpreadsheet Visual
fpspreadsheetctrls, fpspreadsheetgrid;
type type
@ -83,8 +87,8 @@ type
FWorkbookSource: TsWorkbookSource; FWorkbookSource: TsWorkbookSource;
FWorkbook: TsWorkbook; FWorkbook: TsWorkbook;
FWorksheets: array[TsXYRange] of TsWorksheet; FWorksheets: array[TsXYRange] of TsWorksheet;
FRangeStr: array[TsXYRange] of String;
FRanges: array[TsXYRange] of TsCellRangeArray; FRanges: array[TsXYRange] of TsCellRangeArray;
FDirections: array[TsXYRange] of TsSelectionDirection;
FPointsNumber: Cardinal; FPointsNumber: Cardinal;
function GetRange(AIndex: TsXYRange): String; function GetRange(AIndex: TsXYRange): String;
function GetWorkbook: TsWorkbook; function GetWorkbook: TsWorkbook;
@ -94,10 +98,13 @@ type
procedure SetWorkbookSource(AValue: TsWorkbookSource); procedure SetWorkbookSource(AValue: TsWorkbookSource);
protected protected
FCurItem: TChartDataItem; FCurItem: TChartDataItem;
function BuildRangeStr(AIndex: TsXYRange; AListSeparator: char = #0): String;
function CountValues(AIndex: TsXYRange): Integer; function CountValues(AIndex: TsXYRange): Integer;
function GetCount: Integer; override; function GetCount: Integer; override;
function GetItem(AIndex: Integer): PChartDataItem; override; function GetItem(AIndex: Integer): PChartDataItem; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Prepare; overload;
procedure Prepare(AIndex: TsXYRange); overload;
procedure SetYCount(AValue: Cardinal); override; procedure SetYCount(AValue: Cardinal); override;
public public
destructor Destroy; override; destructor Destroy; override;
@ -270,27 +277,65 @@ end;
{ TsWorkbookChartSource } { TsWorkbookChartSource }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{@@ ----------------------------------------------------------------------------
Destructor of the WorkbookChartSource.
Removes itself from the WorkbookSource's listener list.
-------------------------------------------------------------------------------}
destructor TsWorkbookChartSource.Destroy; destructor TsWorkbookChartSource.Destroy;
begin begin
if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self); if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self);
inherited Destroy; inherited Destroy;
end; end;
{@@ ----------------------------------------------------------------------------
Constructs the range string from the stored internal information. Is needed
to have the worksheet name in the range string in order to make the range
string unique.
-------------------------------------------------------------------------------}
function TsWorkbookChartSource.BuildRangeStr(AIndex: TsXYRange;
AListSeparator: Char = #0): String;
var
L: TStrings;
range: TsCellRange;
begin
if (FWorkbook = nil) or (FWorksheets[AIndex] = nil) or (Length(FRanges) = 0) then
exit('');
L := TStringList.Create;
try
if AListSeparator = #0 then
L.Delimiter := FWorkbook.FormatSettings.ListSeparator
else
L.Delimiter := AListSeparator;
L.StrictDelimiter := true;
for range in FRanges[AIndex] do
L.Add(GetCellRangeString(range, rfAllRel, true));
Result := FWorksheets[AIndex].Name + SHEETSEPARATOR + L.DelimitedText;
finally
L.Free;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Counts the number of x or y values contained in the x/y ranges Counts the number of x or y values contained in the x/y ranges
@param AIndex Identifies whether values in the x or y ranges are counted.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorkbookChartSource.CountValues(AIndex: TsXYRange): Integer; function TsWorkbookChartSource.CountValues(AIndex: TsXYRange): Integer;
var var
ir: Integer; ir: Integer;
range: TsCellRange;
begin begin
Result := 0; Result := 0;
case FDirections[AIndex] of for range in FRanges[AIndex] do
fpsVerticalSelection: begin
for ir:=0 to High(FRanges[AIndex]) do if range.Col1 = range.Col2 then
inc(Result, FRanges[AIndex, ir].Row2 - FRanges[AIndex, ir].Row1 + 1); inc(Result, range.Row2 - range.Row1 + 1)
fpsHorizontalSelection: else
for ir:=0 to High(FRanges[AIndex]) do if range.Row1 = range.Row2 then
inc(Result, FRanges[AIndex, ir].Col2 - FRanges[AIndex, ir].Col1 + 1); inc(Result, range.Col2 - range.Col1 + 1)
else
raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high.');
end; end;
end; end;
@ -306,6 +351,10 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Main ChartSource method called from the series requiring data for plotting. Main ChartSource method called from the series requiring data for plotting.
Retrieves the data from the workbook. Retrieves the data from the workbook.
@param AIndex Index of the data point in the series.
@return Pointer to a TChartDataItem record containing the x and y coordinates,
the data point mark text, and the individual data point color.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorkbookChartSource.GetItem(AIndex: Integer): PChartDataItem; function TsWorkbookChartSource.GetItem(AIndex: Integer): PChartDataItem;
var var
@ -325,25 +374,8 @@ end;
by the workbook's FormatSettings. by the workbook's FormatSettings.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorkbookChartsource.GetRange(AIndex: TsXYRange): String; function TsWorkbookChartsource.GetRange(AIndex: TsXYRange): String;
var
L: TStrings;
ir: Integer;
begin begin
if FWorksheets[AIndex] = nil then Result := FRangeStr[AIndex];
begin
Result := '';
exit;
end;
L := TStringList.Create;
try
L.Delimiter := Workbook.FormatSettings.ListSeparator;
for ir:=0 to High(FRanges[AIndex]) do
L.Add(GetCellRangeString(FRanges[AIndex, ir], rfAllRel, true));
Result := FWorksheets[AIndex].Name + SHEETSEPARATOR + L.DelimitedText;
finally
L.Free;
end;
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -358,45 +390,59 @@ begin
FWorkbook := Result; FWorkbook := Result;
end; end;
{@@ ----------------------------------------------------------------------------
Helper method the prepare the information required for the series data point.
@param XOrY Identifies whether the method retrieves the x or y
coordinate.
@param APointIndex Index of the data point for which the data are required
@param ANumber (output) x or y coordinate of the data point
@param AText Data point marks label text
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.GetXYItem(XOrY:TsXYRange; APointIndex: Integer; procedure TsWorkbookChartSource.GetXYItem(XOrY:TsXYRange; APointIndex: Integer;
out ANumber: Double; out AText: String); out ANumber: Double; out AText: String);
var var
range: TsCellRange; range: TsCellRange;
i, j: Integer; idx, ir: Integer;
len: Integer; len: Integer;
row, col: Cardinal; row, col: Cardinal;
cell: PCell; cell: PCell;
begin begin
cell := nil; cell := nil;
i := 0; idx := 0;
case FDirections[XOrY] of if FRanges[XOrY] = nil then
fpsVerticalSelection: exit;
for j:=0 to High(FRanges[XOrY]) do begin
range := FRanges[XOrY, j];
len := range.Row2 - range.Row1 + 1;
if (APointIndex >= i) and (APointIndex < i + len) then begin
row := range.Row1 + APointIndex - i;
col := range.Col1;
cell := FWorksheets[XOrY].FindCell(row, col);
break;
end;
inc(i, len);
end;
fpsHorizontalSelection: for range in FRanges[XOrY] do
for j:=0 to High(FRanges[XOrY]) do begin begin
range := FRanges[XOrY, j]; if (range.Col1 = range.Col2) then // vertical range
len := range.Col2 - range.Col1 + 1; begin
if (APointIndex >= i) and (APointIndex < i + len) then begin len := range.Row2 - range.Row1 + 1;
row := range.Row1; if (APointIndex >= idx) and (APointIndex < idx + len) then
col := range.Col1 + APointIndex - i; begin
cell := FWorksheets[XOrY].FindCell(row, col); row := range.Row1 + APointIndex - idx;
break; col := range.Col1;
end; break;
inc(i, len);
end; end;
inc(idx, len);
end else // horizontal range
if (range.Row1 = range.Row2) then
begin
len := range.Col2 - range.Col1 + 1;
if (APointIndex >= idx) and (APointIndex < idx + len) then
begin
row := range.Row1;
col := range.Col1 + APointIndex - idx;
break;
end;
end else
raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high');
end; end;
if cell = nil then begin
cell := FWorksheets[XOrY].FindCell(row, col);
if cell = nil then
begin
ANumber := NaN; ANumber := NaN;
AText := ''; AText := '';
end else end else
@ -417,9 +463,11 @@ end;
and to worksheet changes by selecting the tab corresponding to the selected and to worksheet changes by selecting the tab corresponding to the selected
worksheet. worksheet.
@param AChangedItems Set with elements identifying whether workbook, worksheet @param AChangedItems Set with elements identifying whether workbook,
cell content or cell formatting has changed worksheet, cell content or cell formatting has changed
@param AData Additional data, not used here @param AData Additional data, not used here
@see TsNotificationItem
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.ListenerNotification( procedure TsWorkbookChartSource.ListenerNotification(
AChangedItems: TsNotificationItems; AData: Pointer = nil); AChangedItems: TsNotificationItems; AData: Pointer = nil);
@ -431,9 +479,26 @@ var
begin begin
Unused(AData); Unused(AData);
// Worksheet changes // Workbook has been successfully loaded, all sheets are ready
if (lniWorksheet in AChangedItems) and (Workbook <> nil) then if (lniWorkbook in AChangedItems) then
Reset; Prepare;
// Used worksheet has been renamed?
if (lniWorksheetRename in AChangedItems) then
for xy in TsXYRange do
if TsWorksheet(AData) = FWorksheets[xy] then begin
FRangeStr[xy] := BuildRangeStr(xy);
Prepare(xy);
end;
// Used worksheet will be deleted?
if (lniWorksheetRemoving in AChangedItems) then
for xy in TsXYRange do
if TsWorksheet(AData) = FWorksheets[xy] then begin
FWorksheets[xy] := nil;
FRangeStr[xy] := BuildRangeStr(xy);
Prepare(xy);
end;
// Cell changes: Enforce recalculation of axes if modified cell is within the // Cell changes: Enforce recalculation of axes if modified cell is within the
// x or y range(s). // x or y range(s).
@ -457,6 +522,7 @@ begin
end; end;
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Standard component notification: The ChartSource is notified that the Standard component notification: The ChartSource is notified that the
WorkbookSource is being removed. WorkbookSource is being removed.
@ -469,6 +535,57 @@ begin
SetWorkbookSource(nil); SetWorkbookSource(nil);
end; end;
{@@ ----------------------------------------------------------------------------
Parses the x and y cell range strings and extracts internal information
(worksheet used, cell range coordinates)
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.Prepare;
begin
Prepare(rngX);
Prepare(rngY);
end;
{@@ ----------------------------------------------------------------------------
Parses the range string of the data specified by AIndex and extracts internal
information (worksheet used, cell range coordinates)
@param AIndex Identifies whether x or y cell ranges are analyzed
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.Prepare(AIndex: TsXYRange);
const
XY: array[TsXYRange] of string = ('x', 'y');
var
range: TsCellRange;
begin
if (FWorkbook = nil) or (FRangeStr[AIndex] = '') then begin
FWorksheets[AIndex] := nil;
SetLength(FRanges[AIndex], 0);
FPointsNumber := 0;
Reset;
exit;
end;
if FWorkbook.TryStrToCellRanges(FRangeStr[AIndex], FWorksheets[AIndex], FRanges[AIndex])
then begin
for range in FRanges[AIndex] do
if (range.Col1 <> range.Col2) and (range.Row1 <> range.Row2) then
raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high');
FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
// If x and y ranges are of different size empty data points will be plotted.
Reset;
// Make sure to include worksheet name in RangeString.
FRangeStr[AIndex] := BuildRangeStr(AIndex);
end else
if (FWorkbook.GetWorksheetCount > 0) then begin
if FWorksheets[AIndex] = nil then
raise Exception.CreateFmt('Worksheet of %s cell range "%s" does not exist.',
[XY[AIndex], FRangeStr[AIndex]])
else
raise Exception.CreateFmt('No valid %s cell range in "%s".',
[XY[AIndex], FRangeStr[AIndex]]);
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Resets internal buffers and notfies chart elements of the changes, Resets internal buffers and notfies chart elements of the changes,
in particular, enforces recalculation of axis limits in particular, enforces recalculation of axis limits
@ -491,59 +608,9 @@ end;
character defined in the Workbook's FormatSettings. character defined in the Workbook's FormatSettings.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.SetRange(AIndex: TsXYRange; const AValue: String); procedure TsWorkbookChartSource.SetRange(AIndex: TsXYRange; const AValue: String);
var
s: String;
p, i: Integer;
L: TStrings;
sd: TsSelectionDirection;
sd0: TsSelectionDirection;
begin begin
if (FWorkbook = nil) then FRangeStr[AIndex] := AValue;
exit; Prepare;
p := pos(SHEETSEPARATOR, AValue);
if p = 0 then
begin
FWorksheets[AIndex] := FWorkbook.ActiveWorksheet;
s := AValue;
end else
begin
s := Copy(AValue, 1, p-1);
FWorksheets[AIndex] := FWorkbook.GetWorksheetByName(s);
if FWorksheets[AIndex] = nil then
raise Exception.CreateFmt('%s cell range "%s" is in a non-existing '+
'worksheet.', [''+char(ord('x')+ord(AIndex)), AValue]);
s := Copy(AValue, p+1, Length(AValue));
end;
L := TStringList.Create;
try
L.Delimiter := FWorkbook.FormatSettings.ListSeparator;
L.DelimitedText := s;
if L.Count = 0 then
raise Exception.CreateFmt('No %s cell range contained in "%s".',
[''+char(ord('x')+ord(AIndex)), AValue]
);
sd := fpsVerticalSelection;
SetLength(FRanges[AIndex], L.Count);
for i:=0 to L.Count-1 do
if ParseCellRangeString(L[i], FRanges[AIndex, i]) then begin
if FRanges[AIndex, i].Col1 = FRanges[AIndex, i].Col2 then
sd := fpsVerticalSelection
else
if FRanges[AIndex, i].Row1 = FRanges[AIndex, i].Row2 then
sd := fpsHorizontalSelection
else
raise Exception.Create('Selection can only be 1 column wide or 1 row high');
end else
raise Exception.CreateFmt('No valid %s cell range in "%s".',
[''+char(ord('x')+ord(AIndex)), L[i]]
);
FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
// If x and y ranges are of different size empty data points will be plotted.
Reset;
finally
L.Free;
end;
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -564,12 +631,11 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Inherited ChartSource method telling the series how many y values are used. Inherited ChartSource method telling the series how many y values are used.
Currently we support only single valued data Currently we support only single valued data (YCount = 1).
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.SetYCount(AValue: Cardinal); procedure TsWorkbookChartSource.SetYCount(AValue: Cardinal);
begin begin
FYCount := AValue; FYCount := AValue;
// currently not used
end; end;

View File

@ -39,8 +39,10 @@ type
{@@ Describes during communication between WorkbookSource and visual controls {@@ Describes during communication between WorkbookSource and visual controls
which kind of item has changed: the workbook, the worksheet, a cell value, which kind of item has changed: the workbook, the worksheet, a cell value,
or a cell formatting, etc. } or a cell formatting, etc. }
TsNotificationItem = (lniWorkbook, lniWorksheet, lniCell, lniSelection, TsNotificationItem = (lniWorkbook,
lniAbortSelection, lniRow, lniPalette); lniWorksheet, lniWorksheetAdd, lniWorksheetRemoving, lniWorksheetRemove,
lniWorksheetRename,
lniCell, lniSelection, lniAbortSelection, lniRow, lniPalette);
{@@ This set accompanies the notification between WorkbookSource and visual {@@ This set accompanies the notification between WorkbookSource and visual
controls and describes which items have changed in the spreadsheet. } controls and describes which items have changed in the spreadsheet. }
TsNotificationItems = set of TsNotificationItem; TsNotificationItems = set of TsNotificationItem;
@ -81,6 +83,8 @@ type
procedure WorksheetAddedHandler(Sender: TObject; ASheet: TsWorksheet); procedure WorksheetAddedHandler(Sender: TObject; ASheet: TsWorksheet);
procedure WorksheetChangedHandler(Sender: TObject; ASheet: TsWorksheet); procedure WorksheetChangedHandler(Sender: TObject; ASheet: TsWorksheet);
procedure WorksheetRemovedHandler(Sender: TObject; ASheetIndex: Integer); procedure WorksheetRemovedHandler(Sender: TObject; ASheetIndex: Integer);
procedure WorksheetRemovingHandler(Sender: TObject; AWorksheet: TsWorksheet);
procedure WorksheetRenamedHandler(Sender: TObject; AWorksheet: TsWorksheet);
procedure WorksheetSelectedHandler(Sender: TObject; AWorksheet: TsWorksheet); procedure WorksheetSelectedHandler(Sender: TObject; AWorksheet: TsWorksheet);
protected protected
@ -772,6 +776,8 @@ begin
FWorkbook.OnAddWorksheet := @WorksheetAddedHandler; FWorkbook.OnAddWorksheet := @WorksheetAddedHandler;
FWorkbook.OnChangeWorksheet := @WorksheetChangedHandler; FWorkbook.OnChangeWorksheet := @WorksheetChangedHandler;
FWorkbook.OnRemoveWorksheet := @WorksheetRemovedHandler; FWorkbook.OnRemoveWorksheet := @WorksheetRemovedHandler;
FWorkbook.OnRemovingWorksheet := @WorksheetRemovingHandler;
FWorkbook.OnRenameWorksheet := @WorksheetRenamedHandler;
FWorkbook.OnSelectWorksheet := @WorksheetSelectedHandler; FWorkbook.OnSelectWorksheet := @WorksheetSelectedHandler;
FWorkbook.OnChangePalette := @WorkbookChangedPaletteHandler; FWorkbook.OnChangePalette := @WorkbookChangedPaletteHandler;
// Pass options to workbook // Pass options to workbook
@ -1227,7 +1233,7 @@ procedure TsWorkbookSource.WorksheetAddedHandler(Sender: TObject;
ASheet: TsWorksheet); ASheet: TsWorksheet);
begin begin
Unused(Sender); Unused(Sender);
NotifyListeners([lniWorkbook]); NotifyListeners([lniWorksheetAdd]);
SelectWorksheet(ASheet); SelectWorksheet(ASheet);
end; end;
@ -1275,10 +1281,34 @@ begin
end else end else
sheet := FWorksheet; sheet := FWorksheet;
FWorksheet := sheet; // is needed by listeners! FWorksheet := sheet; // is needed by listeners!
NotifyListeners([lniWorkbook]); NotifyListeners([lniWorksheetRemove]);
SelectWorksheet(sheet); SelectWorksheet(sheet);
end; end;
{@@ ----------------------------------------------------------------------------
Event handler called BEFORE a worksheet is deleted.
@param Sender Workbook containing the worksheet
@param AWorksheet Worksheet which is to be deleted
-------------------------------------------------------------------------------}
procedure TsWorkbookSource.WorksheetRemovingHandler(Sender: TObject;
AWorksheet: TsWorksheet);
begin
NotifyListeners([lniWorksheetRemoving], AWorksheet);
end;
{@@ ----------------------------------------------------------------------------
Event handler called after a worksheet has been renamed
@param Sender Workbook containing the worksheet
@param AWorksheet Worksheet which has been renamed
-------------------------------------------------------------------------------}
procedure TsWorkbookSource.WorksheetRenamedHandler(Sender: TObject;
AWorksheet: TsWorksheet);
begin
NotifyListeners([lniWorksheetRename], AWorksheet);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Event handler called whenever a the workbook makes a worksheet "active". Event handler called whenever a the workbook makes a worksheet "active".
@ -1388,16 +1418,23 @@ var
begin begin
Unused(AData); Unused(AData);
// Workbook changed // Workbook changed: new workbook, worksheet added/renamed/deleted
if (lniWorkbook in AChangedItems) then if (AChangedItems * [lniWorkbook, lniWorksheetAdd, lniWorksheetRemove, lniWorksheetRename] <> []) then
begin begin
inc(FLockCount); // avoid WorkbookSelect message when adding each tab inc(FLockCount); // avoid WorkbookSelect message when adding each tab
GetSheetList(Tabs); GetSheetList(Tabs);
TabIndex := Tabs.Count-1; if (lniWorkbook in AChangedItems) then
TabIndex := 0
else
if (lniWorksheetAdd in AChangedItems) then
TabIndex := Tabs.Count-1
else
if (lniWorksheetRename in AChangedItems) then
TabIndex := Workbook.GetWorksheetIndex(TsWorksheet(AData));
dec(FLockCount); dec(FLockCount);
end; end;
// Worksheet changed // Worksheet selected
if (lniWorksheet in AChangedItems) and (Worksheet <> nil) then if (lniWorksheet in AChangedItems) and (Worksheet <> nil) then
begin begin
i := Tabs.IndexOf(Worksheet.Name); i := Tabs.IndexOf(Worksheet.Name);

View File

@ -2902,7 +2902,8 @@ end;
procedure TsCustomWorksheetGrid.Loaded; procedure TsCustomWorksheetGrid.Loaded;
begin begin
inherited; inherited;
NewWorkbook(FInitColCount, FInitRowCount); if FWorkbookSource = nil then
NewWorkbook(FInitColCount, FInitRowCount);
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------