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"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<RequiredPackages Count="2">
<Item1>
<PackageName Value="LCL"/>
<PackageName Value="TAChartLazarusPkg"/>
</Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages>
<Units Count="2">
<Unit0>

View File

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

View File

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

View File

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

View File

@ -969,8 +969,10 @@ type
FOnWriteCellData: TsWorkbookWriteCellDataEvent;
FOnReadCellData: TsWorkbookReadCellDataEvent;
FOnChangeWorksheet: TsWorksheetEvent;
FOnRenameWorksheet: TsWorksheetEvent;
FOnAddWorksheet: TsWorksheetEvent;
FOnRemoveWorksheet: TsRemoveWorksheetEvent;
FOnRemovingWorksheet: TsWorksheetEvent;
FOnSelectWorksheet: TsWorksheetEvent;
FOnChangePalette: TNotifyEvent;
FFileName: String;
@ -1033,6 +1035,14 @@ type
function ValidWorksheetName(var AName: String;
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 }
function AddFont(const AFontName: String; ASize: Single;
AStyle: TsFontStyles; AColor: TsColor): Integer; overload;
@ -1093,8 +1103,12 @@ type
property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet;
{@@ This event fires whenever a workbook is loaded }
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;
{@@ This event fires BEFORE a worksheet is deleted }
property OnRemovingWorksheet: TsWorksheetEvent read FOnRemovingWorksheet write FOnRemovingWorksheet;
{@@ This event fires when a worksheet is made "active"}
property OnSelectWorksheet: TsWorksheetEvent read FOnSelectWorksheet write FOnSelectWorksheet;
{@@ This event allows to provide external cell data for writing to file,
@ -3597,7 +3611,7 @@ begin
begin
FName := AName;
if (FWorkbook.FLockCount = 0) and Assigned(FWorkbook.FOnChangeWorksheet) then
FWorkbook.FOnChangeWorksheet(FWorkbook, self);
FWorkbook.FOnRenameWorksheet(FWorkbook, self);
end;
end;
@ -7098,6 +7112,8 @@ begin
i := GetWorksheetIndex(AWorksheet);
if (i <> -1) and (AWorksheet <> nil) then
begin
if Assigned(FOnRemovingWorksheet) then
FOnRemovingWorksheet(self, AWorksheet);
FWorksheets.Delete(i);
AWorksheet.Free;
if Assigned(FOnRemoveWorksheet) then
@ -7199,6 +7215,150 @@ begin
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 }
{@@ ----------------------------------------------------------------------------

View File

@ -1,11 +1,15 @@
{
fpspreadsheetgrid.pas
{ fpspreadsheetchart.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;
{$mode objfpc}{$H+}
@ -15,11 +19,11 @@ interface
uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
// TChart
{tasources,} TACustomSource,
// FPSpreadsheet Visual
fpspreadsheetctrls, fpspreadsheetgrid,
TACustomSource,
// FPSpreadsheet
fpspreadsheet, fpsutils;
fpspreadsheet, fpsutils,
// FPSpreadsheet Visual
fpspreadsheetctrls, fpspreadsheetgrid;
type
@ -83,8 +87,8 @@ type
FWorkbookSource: TsWorkbookSource;
FWorkbook: TsWorkbook;
FWorksheets: array[TsXYRange] of TsWorksheet;
FRangeStr: array[TsXYRange] of String;
FRanges: array[TsXYRange] of TsCellRangeArray;
FDirections: array[TsXYRange] of TsSelectionDirection;
FPointsNumber: Cardinal;
function GetRange(AIndex: TsXYRange): String;
function GetWorkbook: TsWorkbook;
@ -94,10 +98,13 @@ type
procedure SetWorkbookSource(AValue: TsWorkbookSource);
protected
FCurItem: TChartDataItem;
function BuildRangeStr(AIndex: TsXYRange; AListSeparator: char = #0): String;
function CountValues(AIndex: TsXYRange): Integer;
function GetCount: Integer; override;
function GetItem(AIndex: Integer): PChartDataItem; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Prepare; overload;
procedure Prepare(AIndex: TsXYRange); overload;
procedure SetYCount(AValue: Cardinal); override;
public
destructor Destroy; override;
@ -270,27 +277,65 @@ end;
{ TsWorkbookChartSource }
{------------------------------------------------------------------------------}
{@@ ----------------------------------------------------------------------------
Destructor of the WorkbookChartSource.
Removes itself from the WorkbookSource's listener list.
-------------------------------------------------------------------------------}
destructor TsWorkbookChartSource.Destroy;
begin
if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self);
inherited Destroy;
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
@param AIndex Identifies whether values in the x or y ranges are counted.
-------------------------------------------------------------------------------}
function TsWorkbookChartSource.CountValues(AIndex: TsXYRange): Integer;
var
ir: Integer;
range: TsCellRange;
begin
Result := 0;
case FDirections[AIndex] of
fpsVerticalSelection:
for ir:=0 to High(FRanges[AIndex]) do
inc(Result, FRanges[AIndex, ir].Row2 - FRanges[AIndex, ir].Row1 + 1);
fpsHorizontalSelection:
for ir:=0 to High(FRanges[AIndex]) do
inc(Result, FRanges[AIndex, ir].Col2 - FRanges[AIndex, ir].Col1 + 1);
for range in FRanges[AIndex] do
begin
if range.Col1 = range.Col2 then
inc(Result, range.Row2 - range.Row1 + 1)
else
if range.Row1 = range.Row2 then
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;
@ -306,6 +351,10 @@ end;
{@@ ----------------------------------------------------------------------------
Main ChartSource method called from the series requiring data for plotting.
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;
var
@ -325,25 +374,8 @@ end;
by the workbook's FormatSettings.
-------------------------------------------------------------------------------}
function TsWorkbookChartsource.GetRange(AIndex: TsXYRange): String;
var
L: TStrings;
ir: Integer;
begin
if FWorksheets[AIndex] = nil then
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;
Result := FRangeStr[AIndex];
end;
{@@ ----------------------------------------------------------------------------
@ -358,45 +390,59 @@ begin
FWorkbook := Result;
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;
out ANumber: Double; out AText: String);
var
range: TsCellRange;
i, j: Integer;
idx, ir: Integer;
len: Integer;
row, col: Cardinal;
cell: PCell;
begin
cell := nil;
i := 0;
case FDirections[XOrY] of
fpsVerticalSelection:
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;
idx := 0;
if FRanges[XOrY] = nil then
exit;
fpsHorizontalSelection:
for j:=0 to High(FRanges[XOrY]) do begin
range := FRanges[XOrY, j];
len := range.Col2 - range.Col1 + 1;
if (APointIndex >= i) and (APointIndex < i + len) then begin
row := range.Row1;
col := range.Col1 + APointIndex - i;
cell := FWorksheets[XOrY].FindCell(row, col);
break;
end;
inc(i, len);
for range in FRanges[XOrY] do
begin
if (range.Col1 = range.Col2) then // vertical range
begin
len := range.Row2 - range.Row1 + 1;
if (APointIndex >= idx) and (APointIndex < idx + len) then
begin
row := range.Row1 + APointIndex - idx;
col := range.Col1;
break;
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;
if cell = nil then begin
cell := FWorksheets[XOrY].FindCell(row, col);
if cell = nil then
begin
ANumber := NaN;
AText := '';
end else
@ -417,9 +463,11 @@ end;
and to worksheet changes by selecting the tab corresponding to the selected
worksheet.
@param AChangedItems Set with elements identifying whether workbook, worksheet
cell content or cell formatting has changed
@param AChangedItems Set with elements identifying whether workbook,
worksheet, cell content or cell formatting has changed
@param AData Additional data, not used here
@see TsNotificationItem
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.ListenerNotification(
AChangedItems: TsNotificationItems; AData: Pointer = nil);
@ -431,9 +479,26 @@ var
begin
Unused(AData);
// Worksheet changes
if (lniWorksheet in AChangedItems) and (Workbook <> nil) then
Reset;
// Workbook has been successfully loaded, all sheets are ready
if (lniWorkbook in AChangedItems) then
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
// x or y range(s).
@ -457,6 +522,7 @@ begin
end;
end;
{@@ ----------------------------------------------------------------------------
Standard component notification: The ChartSource is notified that the
WorkbookSource is being removed.
@ -469,6 +535,57 @@ begin
SetWorkbookSource(nil);
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,
in particular, enforces recalculation of axis limits
@ -491,59 +608,9 @@ end;
character defined in the Workbook's FormatSettings.
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.SetRange(AIndex: TsXYRange; const AValue: String);
var
s: String;
p, i: Integer;
L: TStrings;
sd: TsSelectionDirection;
sd0: TsSelectionDirection;
begin
if (FWorkbook = nil) then
exit;
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;
FRangeStr[AIndex] := AValue;
Prepare;
end;
{@@ ----------------------------------------------------------------------------
@ -564,12 +631,11 @@ end;
{@@ ----------------------------------------------------------------------------
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);
begin
FYCount := AValue;
// currently not used
end;

View File

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

View File

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