// Use file "cansas.laz" for testing unit CompareDistUnit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons, ExtCtrls, ComCtrls, Spin, FunctionsLib, Globals, GraphLib, DataProcs, MainDM, MainUnit, BasicStatsFormUnit, ReportFrameUnit, ChartFrameUnit; type TCompareTo = (ctTheoreticalDistrib, ctVariable); TCompareDist = (cd_Normal, cd_t, cd_ChiSq, cd_F, cd_Poisson); { TCompareDistFrm } TCompareDistFrm = class(TBasicStatsForm) Bevel1: TBevel; DF1Edit: TEdit; DF2Edit: TEdit; DistGroup: TGroupBox; DF1Label: TLabel; DF2Label: TLabel; NoIntervalsEdit: TSpinEdit; NoIntervalsLabel: TLabel; PageControl1: TPageControl; ParamsSplitter: TSplitter; NormalDistChk: TRadioButton; FreqChartPage: TTabSheet; tDistChk: TRadioButton; ChiSqDistChk: TRadioButton; FDistChk: TRadioButton; PoissonDistChk: TRadioButton; ReportPage: TTabSheet; CumFreqChartPage: TTabSheet; Notebook: TNotebook; BarPlotBtn: TSpeedButton; LinePlotBtn: TSpeedButton; TheoreticalDistPage: TPage; VariablePage: TPage; ParamsPanel: TPanel; BothChk: TCheckBox; OptionsGroup: TGroupBox; ResetBtn: TButton; ComputeBtn: TButton; CloseBtn: TButton; CompareGroup: TRadioGroup; VarOneEdit: TEdit; VarTwoEdit: TEdit; Label2: TLabel; Label3: TLabel; Var1InBtn: TBitBtn; Var1OutBtn: TBitBtn; Var2InBtn: TBitBtn; Var2OutBtn: TBitBtn; Label1: TLabel; VarList: TListBox; procedure CloseBtnClick(Sender: TObject); procedure CompareGroupClick(Sender: TObject); procedure ComputeBtnClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); procedure DistChange(Sender: TObject); procedure ResetBtnClick(Sender: TObject); procedure Var1InBtnClick(Sender: TObject); procedure Var1OutBtnClick(Sender: TObject); procedure Var2InBtnClick(Sender: TObject); procedure Var2OutBtnClick(Sender: TObject); procedure VarListDblClick(Sender: TObject); procedure VarListSelectionChange(Sender: TObject; {%H-}User: boolean); private FReportFrame: TReportFrame; FCumFreqChartFrame: TChartFrame; FFreqChartFrame: TChartFrame; FAutoSized: Boolean; CompareTo: TCompareTo; CompareDist: TCompareDist; procedure CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; AMin, AIncrement: Double; ANumIntervals, AColIndex: Integer); procedure CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; AMin, AMax: Double; ANumIntervals, ANumCases: Integer; ACompareDist: TCompareDist; DF1: Integer = -1; DF2: Integer = -1); procedure CalcIntervals(var AMin, AMax, AIntervalsize: Double; out ANumIntervals: Integer); function CalcMinMax(out AMin, AMax: Double; AColIndex: Integer): Integer; procedure CalcTheoreticalDist(XValues, FreqValues, CumFreqValues: DblDyneVec; ANumIntervals, ANumCases: Integer; ACompareDist: TCompareDist; out AName: String); procedure DisplayReport(XValue1, XValue2, FreqValues1, FreqValues2, CumFreqValues1, CumFreqValues2: DblDyneVec; AName1, AName2: String; ANumIntervals: Integer); procedure Plot(AChartFrame: TChartFrame; Y1Values, Y2Values: DblDyneVec; ASeriesTitle1, ASeriesTitle2, AYTitle, ATitle: String); procedure UpdateBtnStates; procedure UpdateDF1; function Validate(ANumCases: Integer; out AMsg: String; out AControl: TWinControl): Boolean; public procedure Reset; override; end; var CompareDistFrm: TCompareDistFrm; implementation {$R *.lfm} uses Math, TACustomSeries, TASeries, Utils, MathUnit; { TCompareDistFrm } { Get frequency and cumulative frequency of cases in each interval } procedure TCompareDistFrm.CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; AMin, AIncrement: Double; ANumIntervals, AColIndex: Integer); var j, k: Integer; value: Double; begin // Get border points of the intervals for j := 0 to ANumIntervals do // no "-1" because last point is needed XValues[j] := AMin + j*AIncrement; // count values in these intervals for j := 1 to NoCases do begin if not ValidValue(j, AColIndex) then continue; value := StrToFloat(OS3MainFrm.DataGrid.Cells[AColIndex, j]); for k := 0 to ANumIntervals-1 do begin if (value >= XValues[k]) and (value < XValues[k+1]) then FreqValues[k] := FreqValues[k] + 1; end; end; // Calculate cumulative frequencies CumFreqValues[0] := FreqValues[0]; for j := 1 to ANumIntervals-1 do CumFreqValues[j] := CumFreqValues[j-1] + FreqValues[j]; end; { Calculate frequencies and cumulative frequencies for a theoretical distribution } procedure TCompareDistFrm.CalcFreq(XValues, FreqValues, CumFreqValues: DblDyneVec; AMin, AMax: Double; ANumIntervals, ANumCases: Integer; ACompareDist: TCompareDist; DF1: Integer = -1; DF2: Integer = -1); var dx: Double; i: Integer; procedure Calc(AProb1, AProb2: Double); begin FreqValues[i] := abs(AProb2 - AProb1) * ANumCases; end; begin dx := (AMax - AMin) / ANumIntervals; for i := 0 to ANumIntervals do XValues[i] := AMin + i * dx; for i := 0 to ANumIntervals - 1 do case ACompareDist of cd_Normal: Calc(NormalDist(XValues[i]), NormalDist(XValues[i+1])); cd_t: Calc(0.5 * ProbT(XValues[i], DF1), 0.5 * ProbT(XValues[i+1], DF1)); cd_ChiSq: Calc(ChiSquaredProb(XValues[i], DF1), ChiSquaredProb(XValues[i+1], DF1)); cd_F: Calc(ProbF(XValues[i], DF1, DF2), ProbF(XValues[i+1], DF1, DF2)); cd_Poisson: Calc(PoissonCDF(round(XValues[i]), DF1), PoissonCDF(round(XValues[i+1]), DF1)); end; CumFreqValues[0] := FreqValues[0]; for i := 1 to ANumIntervals - 1 do CumFreqValues[i] := CumFreqValues[i-1] + FreqValues[i]; end; procedure TCompareDistFrm.CalcIntervals(var AMin, AMax, AIntervalsize: Double; out ANumIntervals: Integer); var intervalSize: Double; m: Double; e: Integer; begin intervalSize := (AMax - AMin) / NoIntervalsEdit.Value; if intervalSize = 0 then intervalSize := 1; MantisseAndExponent(intervalSize, m, e); m := round(m); AIntervalSize := m * IntPower(10, e); AMin := floor(AMin / AIntervalSize) * AIntervalSize; AMax := ceil(AMax / AIntervalSize) * AIntervalSize; ANumIntervals := round((AMax - AMin) / AIntervalSize); end; { Calculates minimum and maximum of the values in the given column. Also, counts the valid numbers in this column and returns it as function result } function TCompareDistFrm.CalcMinMax(out AMin, AMax: Double; AColIndex: Integer): Integer; var j: Integer; value: Double; numCases: Integer; begin AMin := Infinity; AMax := -Infinity; numCases := 0; for j := 1 to NoCases do begin if not ValidValue(j, AColIndex) then continue; value := StrToFloat(OS3MainFrm.DataGrid.Cells[AColIndex, j]); if value > AMax then AMax := value; if value < AMin then AMin := value; inc(numCases); end; Result := numCases; end; procedure TCompareDistFrm.CalcTheoreticalDist(XValues, FreqValues, CumFreqValues: DblDyneVec; ANumIntervals, ANumCases: Integer; ACompareDist: TCompareDist; out AName: String); var min, max: Double; DF1: Integer = -1; DF2: Integer = -1; a: Double; begin if TryStrToFloat(DF1Edit.Text, a) then DF1 := round(a); if TryStrToFloat(DF2Edit.Text, a) then DF2 := round(a); case ACompareDist of cd_Normal: begin min := -3.0; max := 3.0; AName := 'Normal dist'; end; cd_t: begin min := -3.0; max := 3.0; AName := 't dist'; end; cd_ChiSq: begin min := 0.0; max := 20.0; AName := 'Chi-sq dist'; end; cd_F: begin min := 0.0; max := 2.0; AName := 'F dist'; end; cd_Poisson: ; // will be handled separately end; CalcFreq(XValues, FreqValues, CumFreqValues, min, max, ANumIntervals, ANumCases, ACompareDist, DF1, DF2); end; procedure TCompareDistFrm.CloseBtnClick(Sender: TObject); begin Close; end; procedure TCompareDistFrm.CompareGroupClick(Sender: TObject); begin CompareTo := TCompareTo(CompareGroup.ItemIndex); Notebook.PageIndex := CompareGroup.ItemIndex; end; procedure TCompareDistFrm.ComputeBtnClick(Sender: TObject); var var1Freq: DblDyneVec = nil; // could be IntDyneVec, but simpler charting this way var2Freq: DblDyneVec = nil; xValue1: DblDyneVec = nil; xValue2: DblDyneVec = nil; cumfreq1: DblDyneVec = nil; cumfreq2: DblDyneVec = nil; i, col1, col2, nCases, noInts: integer; min1, max1, min2, max2: double; incrSize1: Double = 0.0; incrSize2: Double = 0.0; name1, name2, msg: string; C: TWinControl; begin // Get columns of the variables col1 := 0; for i := 1 to NoVariables do if VarOneEdit.Text = OS3MainFrm.DataGrid.Cells[i, 0] then col1 := i; col2 := 0; if CompareTo = ctVariable then for i := 1 to NoVariables do if VarTwoEdit.Text = OS3MainFrm.DataGrid.Cells[i, 0] then col2 := i; // Check existence of required variables msg := ''; case CompareTo of ctTheoreticalDistrib: if col1 = 0 then msg := 'Variable not specified.'; ctVariable: if col1 = 0 then msg := 'Variable One is not specified.' else if col2 = 0 then msg := 'Variable Two is not specified.'; end; if msg <> '' then begin ErrorMsg(msg); exit; end; // Get min and max values for variable in col1, as well as true number of cases nCases := CalcMinMax(min1, max1, col1); // Validate edit controls if not Validate(nCases, msg, C) then begin C.SetFocus; ErrorMsg(msg); end; // Get number of intervals CalcIntervals(min1, max1, incrSize1, noInts); // Get mem for the arrays SetLength(var1Freq, noInts); Setlength(cumFreq1, noInts); SetLength(xValue1, noInts + 1); // Border points of the intervals, one more than intervals SetLength(var2Freq, noInts); SetLength(cumFreq2, noInts); Setlength(xValue2, noInts + 1); // Repeat for variable 2 (if Compare To is selected as "Another variable") if CompareTo = ctVariable then begin CalcMinMax(min2, max2, col2); incrSize2 := (max2 - min2) / noInts; name2 := VarTwoEdit.Text; end; // Get frequency of cases in each interval CalcFreq(xValue1, var1Freq, cumFreq1, min1, incrSize1, noInts, col1); // Repeat for 2nd variable, if required if CompareTo = ctVariable then CalcFreq(xValue2, var2Freq, cumFreq2, min2, incrSize2, noInts, col2); // Get theoretical distribution frequencies for selected distribution, if required. if CompareTo = ctTheoreticalDistrib then begin if CompareDist = cd_Poisson then begin CalcFreq(xValue2, var2Freq, cumFreq2, min1, min2, noInts, nCases, compareDist, StrToInt(DF1Edit.Text)); name2 := 'Poisson'; end else CalcTheoreticalDist(xValue2, var2Freq, cumFreq2, noInts, nCases, compareDist, name2); end; name1 := VarOneEdit.Text; // Print distributions to report DisplayReport(xValue1, xValue2, var1Freq, var2Freq, cumfreq1, cumFreq2, name1, name2, noInts); // Plot the cumulative distributions Plot(FCumFreqChartFrame, cumFreq1, cumFreq2, VarOneEdit.Text, name2, 'Cumulative Frequency', 'Plot of Cumulative Distributions'); // Plot the frequency distrigutions, if requested. if BothChk.Checked then Plot(FFreqChartFrame, var1Freq, var2Freq, VarOneEdit.Text, name2, 'Frequency', 'Plot of Distributions'); FreqChartPage.TabVisible := BothChk.Checked; (* // Print distributions to report name1 := CenterString(name1, 12); name2 := CenterString(name2, 12); lReport := TStringList.Create; try lReport.Add('DISTRIBUTION COMPARISON by Bill Miller'); lReport.Add(''); lReport.Add('%12s %12s %12s %12s %12s %12s', [ name1, name1, name1, name2, name2, name2 ]); lReport.Add('%12s %12s %12s %12s %12s %12s', [ CenterString('X1 Value', 12), CenterString('Frequency', 12), CenterString('Cum. Freq.', 12), CenterString('X2 Value', 12), CenterString('Frequency', 12), CenterString('Cum. Freq.', 12) ]); lReport.Add('------------ ------------ ------------ ------------ ------------ ------------'); for i := 1 to noints do lReport.Add('%12.3f %12.0f %12.3f %12.3f %12.0f %12.3f', [ XValue1[i-1], Var1Freq[i-1], Cumfreq1[i-1], XValue2[i-1], Var2Freq[i-1], Cumfreq2[i-1] ]); lReport.Add(''); KS := KolmogorovTest(noInts, Cumfreq1, noInts, Cumfreq2, '', lReport); lReport.Add('Kolmogorov-Smirnov statistic: %5.3f', [KS]); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; *) end; procedure TCompareDistFrm.DisplayReport(XValue1, XValue2, FreqValues1, FreqValues2, CumFreqValues1, CumFreqValues2: DblDyneVec; AName1, AName2: String; ANumIntervals: Integer); var lReport: TStrings; i: Integer; KS: Double; begin AName1 := CenterString(AName1, 12); AName2 := CenterString(AName2, 12); lReport := TStringList.Create; try lReport.Add('DISTRIBUTION COMPARISON by Bill Miller'); lReport.Add(''); lReport.Add('%12s %12s %12s %12s %12s %12s', [ AName1, AName1, AName1, AName2, AName2, AName2 ]); lReport.Add('%12s %12s %12s %12s %12s %12s', [ CenterString('X1 Value', 12), CenterString('Frequency', 12), CenterString('Cum. Freq.', 12), CenterString('X2 Value', 12), CenterString('Frequency', 12), CenterString('Cum. Freq.', 12) ]); lReport.Add('------------ ------------ ------------ ------------ ------------ ------------'); for i := 0 to ANumIntervals-1 do lReport.Add('%12.3f %12.0f %12.3f %12.3f %12.0f %12.3f', [ XValue1[i], FreqValues1[i], CumFreqValues1[i], XValue2[i], FreqValues2[i], CumFreqValues2[i] ]); lReport.Add(''); KS := KolmogorovTest(ANumIntervals, CumFreqValues1, ANumIntervals, CumFreqValues2, '', lReport); lReport.Add('Kolmogorov-Smirnov statistic: %5.3f', [KS]); FReportFrame.DisplayReport(lReport); finally lReport.Free; end; end; procedure TCompareDistFrm.DistChange(Sender: TObject); begin DF1Edit.Visible := CompareDist <> cd_Normal; DF1Label.Visible := DF1Edit.Visible; DF1Label.Caption := 'D.F.'; DF2Edit.Visible := CompareDist = cd_F; DF2Label.Visible := DF2Edit.Visible; if NormalDistChk.Checked then CompareDist := cd_Normal else if tDistChk.Checked then begin CompareDist := cd_t; UpdateDF1; end else if ChiSqDistChk.Checked then CompareDist := cd_ChiSq else if FDistChk.Checked then begin CompareDist := cd_F; DF1Label.Caption := 'D.F. 1'; end else if PoissonDistChk.Checked then begin CompareDist := cd_Poisson; DF1Label.Caption := 'Mean'; UpdateDF1; end else raise Exception.Create('Distribution not supported.'); end; procedure TCompareDistFrm.FormActivate(Sender: TObject); var w: Integer; begin if FAutoSized then exit; w := MaxValue([ResetBtn.Width, ComputeBtn.Width, CloseBtn.Width]); ResetBtn.Constraints.MinWidth := w; ComputeBtn.Constraints.MinWidth := w; CloseBtn.Constraints.MinWidth := w; Notebook.AutoSize := true; ParamsPanel.Constraints.MinWidth := Max( 3*w + 2*CloseBtn.BorderSpacing.Left, Min( CompareGroup.Width * 2 - Var1InBtn.Width + VarList.BorderSpacing.Right, OptionsGroup.Width) ); ParamsPanel.Constraints.MinHeight := Notebook.Top + Notebook.Height + OptionsGroup.BorderSpacing.Top + OptionsGroup.Height + Bevel1.Height + CloseBtn.Height + CloseBtn.BorderSpacing.Top; Constraints.MinWidth := ParamsPanel.Constraints.MinWidth + 300; Constraints.MinHeight := ParamsPanel.Constraints.MinHeight + ParamsPanel.BorderSpacing.Top * 2; if Width < Constraints.MinWidth then Width := 1; if Height < Constraints.MinHeight then Height := 1; // Notebook.AutoSize := false; Position := poDesigned; FAutoSized := true; end; procedure TCompareDistFrm.FormCreate(Sender: TObject); begin Assert(OS3MainFrm <> nil); if GraphFrm = nil then Application.CreateForm(TGraphFrm, GraphFrm); InitForm(self); FReportFrame := TReportFrame.Create(self); FReportFrame.Parent := ReportPage; FReportFrame.Align := alClient; FCumFreqChartFrame := TChartFrame.Create(self); FCumFreqChartFrame.Parent := CumFreqChartPage; FCumFreqChartFrame.Align := alClient; FCumFreqChartFrame.Chart.BottomAxis.Intervals.MaxLength := 80; FCumFreqChartFrame.Chart.BottomAxis.Intervals.MinLength := 30; FCumFreqChartFrame.SetYTitle('Cumulative frequency'); FCumFreqChartFrame.SetTitle('Plot of Cumulative Distributions'); FFreqChartFrame := TChartFrame.Create(self); FFreqChartFrame.Parent := FreqChartPage; FFreqChartFrame.Align := alClient; FFreqChartFrame.Chart.BottomAxis.Intervals.MaxLength := 80; FFreqChartFrame.Chart.BottomAxis.Intervals.MinLength := 30; FFreqChartFrame.SetYTitle('Frequency'); FFreqChartFrame.SetTitle('Plot of Distributions'); Reset; end; procedure TCompareDistFrm.Plot(AChartFrame: TChartFrame; Y1Values, Y2Values: DblDyneVec; ASeriesTitle1, ASeriesTitle2, AYTitle, ATitle: String); var ser1, ser2: TChartSeries; plotType: TPlotType; begin AChartFrame.Clear; AChartFrame.SetTitle(ATitle); AChartFrame.SetXTitle('Interval Index'); AChartFrame.SetYTitle(AYTitle); if BarPlotBtn.Down then plotType := ptBars else plotType := ptLines; ser1 := AChartFrame.PlotXY(plotType, nil, Y1Values, nil, nil, ASeriesTitle1, DATA_COLORS[0]); ser2 := AChartFrame.PlotXY(plotType, nil, Y2Values, nil, nil, ASeriesTitle2, DATA_Colors[1]); if (ser1 is TBarSeries) then begin with ser1 as TBarSeries do begin BarWidthPercent := 40; BarOffsetPercent := -20; end; with ser2 as TBarSeries do begin BarWidthPercent := 40; BarOffsetPercent := +20; end; end; end; procedure TCompareDistFrm.Reset; var i: integer; begin VarList.Clear; VarOneEdit.Text := ''; VarTwoEdit.Text := ''; DF1Edit.Text := ''; DF2Edit.Text := ''; for i := 1 to NoVariables do VarList.Items.Add(OS3MainFrm.DataGrid.Cells[i,0]); CompareGroup.ItemIndex := 0; CompareGroupClick(nil); NormalDistChk.Checked := true; DistChange(nil); FReportFrame.Clear; FCumFreqChartFrame.Clear; FFreqChartFrame.Clear; FreqChartPage.TabVisible := false; end; procedure TCompareDistFrm.ResetBtnClick(Sender: TObject); begin Reset; end; procedure TCompareDistFrm.UpdateBtnStates; begin Var1InBtn.Enabled := (VarList.ItemIndex > -1) and (VarOneEdit.Text = ''); Var2InBtn.Enabled := (VarList.ItemIndex > -1) and (VarTwoEdit.Text = ''); Var1OutBtn.Enabled := VarOneEdit.Text <> ''; Var2OutBtn.Enabled := VarTwoEdit.Text <> ''; FReportFrame.UpdateBtnStates; FCumFreqChartFrame.UpdateBtnStates; FFreqChartFrame.UpdateBtnStates; end; procedure TCompareDistFrm.UpdateDF1; procedure DFCandidates(AVarName: String; out AMean: Double; out ANumCases: Integer); var col, i: Integer; begin for col := 1 to NoVariables do if AVarName = OS3MainFrm.DataGrid.Cells[col, 0] then begin AMean := 0; ANumCases := 0; for i := 1 to NoCases do if ValidValue(i, col) then begin AMean := AMean + StrToFloat(OS3MainFrm.DataGrid.Cells[col, i]); inc(ANumCases); end; if ANumCases > 1 then AMean := AMean / ANumCases else AMean := NaN; exit; end; AMean := NaN; ANumCases := -1; end; var m: Double; n: Integer; begin if (CompareTo = ctTheoreticalDistrib) then begin DFCandidates(VarOneEdit.Text, m, n); case CompareDist of cd_t : if n > 0 then DF1Edit.Text := IntToStr(n-1); cd_Poisson: if not IsNaN(m) then DF1Edit.Text := FormatFloat('0', m); end; end; end; function TCompareDistFrm.Validate(ANumCases: Integer; out AMsg: String; out AControl: TWinControl): Boolean; var n: Integer = 0; begin Result := false; if CompareDist <> cd_Normal then begin if DF1Edit.Text = '' then begin AMsg := 'This control cannot be empty.'; AControl := DF1Edit; exit; end; if not TryStrToInt(DF1Edit.Text, n) or (n < 0) then begin AMsg := 'Positive integer value required.'; AControl := DF1Edit; exit; end; if (n >= ANumCases) and (CompareDist <> cd_Poisson) then begin AMsg := 'Degrees of freedom cannot be greater than the number of cases.'; AControl := DF1Edit; exit; end; end; if CompareDist = cd_F then begin if DF2Edit.Text = '' then begin AMsg := 'This control cannot be empty.'; AControl := DF2Edit; exit; end; if not TryStrToInt(DF2Edit.Text, n) or (n < 0) then begin AMsg := 'Positive integer value required.'; AControl := DF2Edit; exit; end; if n >= ANumCases then begin AMsg := 'Degrees of freedom cannot be greater than the number of cases.'; AControl := DF2Edit; exit; end; end; Result := true; end; procedure TCompareDistFrm.Var1InBtnClick(Sender: TObject); var i: integer; begin i := 0; while (VarOneEdit.Text = '') and (i < VarList.Items.Count) do begin if VarList.Selected[i] then begin VarOneEdit.Text := VarList.Items[i]; VarList.Items.Delete(i); i := 0; end else inc(i); end; UpdateBtnStates; UpdateDF1; end; procedure TCompareDistFrm.Var1OutBtnClick(Sender: TObject); begin if VarOneEdit.Text <> '' then begin VarList.Items.Add(VarOneEdit.Text); VarOneEdit.Text := ''; end; UpdateBtnStates; end; procedure TCompareDistFrm.Var2InBtnClick(Sender: TObject); var i: integer; begin i := 0; while (VarTwoEdit.Text = '') and (i < VarList.Items.Count) do begin if VarList.Selected[i] then begin VarTwoEdit.Text := VarList.Items[i]; VarList.Items.Delete(i); i := 0; end else inc(i); end; UpdateBtnStates; end; procedure TCompareDistFrm.Var2OutBtnClick(Sender: TObject); begin if VarTwoEdit.Text <> '' then begin VarList.Items.Add(VarTwoEdit.Text); VarTwoEdit.Text := ''; end; UpdateBtnStates; end; procedure TCompareDistFrm.VarListDblClick(Sender: TObject); var index: Integer; begin index := VarList.ItemIndex; if (index > -1) then begin if VarOneEdit.Text = '' then begin VarOneEdit.Text := VarList.Items[index]; VarList.Items.Delete(index); end; if CompareGroup.ItemIndex = 1 then // Compare to another variable begin VarTwoEdit.Text := VarList.Items[index]; VarList.Items.Delete(index); end; UpdateBtnStates; end; end; procedure TCompareDistFrm.VarListSelectionChange(Sender: TObject; User: boolean); begin UpdateBtnStates; end; end.