TTreeView и контекстное (popup) меню в Delphi и С++ Builder

2010-06-14 papirosnik Delphi

Здесь осветим некоторые моменты, возникающие при привязке контекстного меню к компоненте TTreeView.

Часто бывает удобно отображать некоторую информацию в виде дерева. Например, список каталогов, подкаталогов и файлов в них — что и сделано в Проводнике Windows. Для отображения всей иерархии как нельзя лучше подходит VCL компонента TTreeView из стандартного набора Delphi и/или C++ Builder. Версия среды разработки значения не имеет, так как поведение компоненты на данный момент не отличается от того, что было 15 лет назад. Мы будем использовать Embarcadero RAD Studio 2010 (чем обусловлен такой выбор, кратко рассказано здесь).

Создадим пустой проект: VCL Form Application. В качестве языка программирования условимся использовать C++ Builder. Для Delphi всё будет практически аналогично, за небольшими исключениями в синтаксисе: вместо "->" надо ставить ".", вместо фигурных операторных скобок — begin…end; ну да ладно с синтаксисом — не принципиально и к теме отношения не имеет.

Покладём на форму компоненту TTreeView и переименуем его в инспекторе объектов на tvContent, например. Хотя в небольших демонстрационных проектах можно оставлять имена по умолчанию, но лучше выработать привычку именовать компоненты со смыслом. В качестве префикса я обычно использую две-три буквы из типа компоненты, и затем с большой буквы идёт собственно имя, несущее смысловую нагрузку. Например: frmMain (TForm — главная Форма), btnCancel (TButton — кнопка Отмена), tvContent (TTreeView — дерево просмотра), miNewChapter (TMenuItem — пункт меню Новая глава). Ну вот, что-то опять меня уводит в сторону — возвращаемся к нашей форме.

Допустим мы хотим, чтобы при клике правой кнопкой на нашем tvContent появлялось всплывающее меню, предлагающее нам удалить узел (узел имеет тип TTreeNode, в дальнейшем буду называть его нодой.) , вставить новый, добавить дочерний, переименовать и т.п. Кладём на форму компоненту TPopupMenu (расположена на вкладке Standard) и добавляем туда пункты меню:Popup menu for TTreeView object

Далее, последовательно кликаем по пунктам меню, чтобы создать для них обработчики и забиваем и наполняем их таким кодом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
void __fastcall TfrmMain::miAddChapterClick(TObject *Sender)
{
	TTreeNode *node = tvContent->Selected;
	if (node)
	{
		int n = node->Count+1;
		AnsiString s = IntToStr(n);
		TTreeNode *pn = node;
		while (pn)
		{
			s = IntToStr(pn->Index+1)+"."+s;
			pn = pn->Parent;
 
		}
 
		TTreeNode *newnode = tvContent->Items->AddChild(node,"Subchapter "+s);
		node->Expand(true);
		tvContent->Selected = newnode;
	}
	else
	{
		int n = tvContent->Items->Count+1;
		tvContent->Items->Add(0,"Chapter "+IntToStr(n));
 
	}
}
//---------------------------------------------------------------------------
 
void __fastcall TfrmMain::tvContentMouseDown(TObject *Sender, TMouseButton Button,
		  TShiftState Shift, int X, int Y)
{
	TTreeNode *node = tvContent->GetNodeAt(X,Y);
	if (Button==mbRight && !node)
	{
		tvContent->Selected = 0;
	}
	else
	if (Button==mbRight)
	{
		TTreeNode *node = tvContent->GetNodeAt(X,Y);
		if (node) tvContent->Selected = node;
	}
 
}
//---------------------------------------------------------------------------
 
void __fastcall TfrmMain::miDelChapterClick(TObject *Sender)
{
	TTreeNode *node = tvContent->Selected;
	if (node && !tvContent->IsEditing() && Ask(L"Do you want to delete selected chapter and all its subchapters?"))
	{
		node->Delete();
	}
}
//---------------------------------------------------------------------------
 
void __fastcall TfrmMain::PopupMenu1Popup(TObject *Sender)
{
	TTreeNode *node = tvContent->Selected;
	miAddChapter->Caption=node?"Add subchapter":"New chapter";
	miDelChapter->Enabled = node!=0;
	miRenChapter->Enabled = node!=0;
	if (node)
	{
		actExpand->Enabled = (node->Count>0) && (!node->Expanded);
		actCollapse->Enabled = (node->Count>0) && (node->Expanded);
	}
 
}
//---------------------------------------------------------------------------
 
void __fastcall TfrmMain::miRenChapterClick(TObject *Sender)
{
	tvContent->Selected->EditText();
}
//---------------------------------------------------------------------------
 
void __fastcall TfrmMain::miCollapse2Click(TObject *Sender)
{
   tvContent->Selected->Collapse(true);
}

В функции miAddChapterClick все танцы с бубном происходят ради того, чтобы мы могли добавлять либо родительскую ноду, либо дочернюю — в зависимости от того, кликаем мы по ноде или по пустому месту tvContent;

Ну и собственно на что хотел обратить внимание, это на miDelChapterClick. Нюанс заключается в следующем. Если бы не было проверки на режим редактирования isEditing(), то мы бы получили неприятный глюк. В процессе переименования ноды (по клавише F2 или в контекстном меню пункт Rename) нажатие клавиши Del привело бы не к удалению символа справа от курсора, а к удалению всей ноды, со всеми её детьми. Этого мы никак не хотим.   Проверка в miDelChapterClick позволяет легко и элегантно избежать нежелательного эффекта и реализовать поведение объекта так, как задумано и интуитивно понятно.

C++ Buider, Delphi, Embarcadero, VCL Components,

4 комментария to “TTreeView и контекстное (popup) меню в Delphi и С++ Builder”


Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Powered by WordPress. Designed by elogi.