проблема исключения нарушения доступа вызвана вызовом метода Destroy
на объект, который уже был уничтожен. Позвольте мне объяснить ситуацию.
Представьте, что у вас есть 3 вкладки со следующими индексами и Browsers
массива со следующими экземплярами браузера:
ChromeTabs.Tabs Browsers
---------------------- ----------------------
Index Tab name Index Browser
---------- ---------- ---------- ----------
0 Tab 1 0 Browser 1
1 Tab 2 1 Browser 2
2 Tab 3 2 Browser 3
Теперь нажмите на кнопке закрытия вкладки промежуточной (в закладке индексируется 1) и в OnButtonCloseTabClick
запуске это:
procedure TForm1.ChromeTabsButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// the ATab.Index equals 1
Browsers[ATab.Index].Destroy;
Close := True;
end;
Это приведет к уничтожению экземпляра браузера под названием Browser 2
из приведенной выше таблицы. Это не будет проблемой, когда массив будет повторно проиндексирован, как это делает коллекция Tabs
. Давайте посмотрим, что происходит с закладками и массивом:
ChromeTabs.Tabs Browsers
---------------------- ----------------------
Index Tab name Index Browser
---------- ---------- ---------- ----------
0 Tab 1 0 Browser 1
1 Tab 3 1 --- <-- dangling pointer
2 Browser 3
Как вы можете видеть, Tabs
коллекции была переиндексирована после закрытия второй вкладки, но ваш массив не был. Вы просто уничтожили экземпляр объекта, но висящий указатель от этого уничтоженного объекта все еще существует, по одному и тому же индексу.
Теперь при нажатии на кнопку закрытия вкладки второй раз (вкладка называется Tab 3
), вы будете запускать тот же код, как и раньше в обработчике события:
procedure TForm1.ChromeTabsButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// as before, the ATab.Index equals 1, but this time you'll get AV
// since object from element Browsers[1] has been destroyed before
// and now that element contains just dangling pointer
Browsers[ATab.Index].Destroy; // <--
Close := True;
end;
Но на этот раз вы» ll получить нарушение доступа, потому что объект из элемента Browsers[1]
был уничтожен раньше.
Для вашей цели нет массива подходящего типа коллекции. Я бы посоветовал вам использовать TObjectList<T>
набор списков объектов дженериков. Используя эту коллекцию, я бы переписал ваш код таким образом:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
System.Generics.Collections, ChromeTabs, ChromeTabsClasses, cefvcl;
type
TForm1 = class(TForm)
ChromeTabs1: TChromeTabs;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ChromeTabs1ButtonAddClick(Sender: TObject;
var Handled: Boolean);
procedure ChromeTabs1ButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
procedure ChromeTabs1ActiveTabChanging(Sender: TObject; AOldTab,
ANewTab: TChromeTab; var Allow: Boolean);
private
FBrowsers: TObjectList<TChromium>;
public
{ Public declarations }
end;
var
Form1: TForm1;
Tab_Closed:Boolean=False;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// create instance of the object list and let it manage
// lifetime of the inserted objects
FBrowsers := TObjectList<TChromium>.Create;
FBrowsers.OwnsObjects := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// release the object list
FBrowsers.Free;
end;
procedure TForm1.ChromeTabs1ButtonAddClick(Sender: TObject;
var Handled: Boolean);
var
ChromiumInstance: TChromium;
begin
// create an instance of the browser component and
// initiliaze its properties - here it's simplified
ChromiumInstance := TChromium.Create(nil);
ChromiumInstance.Parent := Self;
ChromiumInstance.SetBounds(8, 8, 150, 150);
// now add the new browser instance to the collection
FBrowsers.Add(ChromiumInstance);
end;
procedure TForm1.ChromeTabs1ButtonCloseTabClick(Sender: TObject;
ATab: TChromeTab; var Close: Boolean);
begin
// delete the browser instance from the collection; since we've
// assigned True to the OwnsObjects property of the collection,
// we don't need to care of freeing the browser instance
FBrowsers.Delete(ATab.Index);
// allow the tab to close
Close := True;
//and fix tab close
Tab_Closed:=True;
end;
procedure TForm1.ChromeTabs1ActiveTabChanging(Sender: TObject; AOldTab,
ANewTab: TChromeTab; var Allow: Boolean);
begin
// check if there's an "old tab" and if so, check also if we have its
// index in the range of our collection; if so, then hide the browser
if Assigned(AOldTab) and (AOldTab.Index < FBrowsers.Count) then
FBrowsers[AOldTab.Index].Visible := False;
// and show the activated tab browser
If((ChromeTabs.Tabs.Count<>1)) Then
Begin
If((ANewTab.Index=(ChromeTabs.Tabs.Count-1)) AND Tab_Closed=True) Then
FBrowsers[AOldTab.Index].Visible := True
Else
FBrowsers[ANewTab.Index].Visible := True;
End
Else FBrowsers[ANewTab.Index].Visible := True;
//Now Tab is not closed
Tab_Closed:=False;
end;
end.
Вам нужно будет переинсталлировать этот массив объектов «TChromium». Представьте, что у вас три вкладки, сначала с индексом 0, второй с индексом 1 и третьим с индексом 2.Теперь вы закрываете вторую вкладку с индексом 1, уничтожаете объект «TChromium» (элемент «Браузеры» с индексом 1), но этот элемент в массиве остается указывать на уничтоженный объект. Затем вкладки получают переиндексацию, поэтому первая вкладка по-прежнему равна 0, но предыдущая третьца получает индекс 1. И вы пытаетесь получить доступ к элементу с индексом 1, как и раньше, когда вы закрываете эту вторую вкладку. AV вы получаете, потому что вы вызываете метод 'Destroy' на уничтоженном объекте. – TLama
Но определенно лучше для этого будет использовать 'коллекцию обобщений 'tobjectList вместо этого массива. –
TLama
Как я могу переиндексировать этот массив? А что такое TObjectList? – Priler