dotnet :: winforms :: finding the component container

Jun 28, 2004 02:59

В процессе подготовки к экзамену по матфизике задался интересным вопросом.
Пусть у нас есть компонент (Component). Мы хотим, чтобы он получал уведомления о событиях от всех контролов определённого типа (скажем, события TextChanged всех TextBox'ов), находящихся на той же форме.

Для этого нам нужно перечислить все TextBox'ы на этой форме, и подключить наш обработчик к желаемому событию. В первом приближение для компонента это можно сделать так:

private void AttachEvents()
{
IContainer container = this.Site.Container;

foreach(Component component in container.Components)
{
TextBox textBox = component as TextBox;
if(textBox == null) continue;

textBox.TextChanged += new EventHandler(textBox_TextChanged);
}
}

Но это решает только часть проблемы. Представим себе, что на форму были добавлены новые TextBox'ы после вызова нашей функции. Естественно, их события не будут перехвачены.

К счастью, у форм (Form) есть событие, унаследованное от Control - ControlAdded.
Нам остаётся подключить к этому событию свой обработчик, и проблема решена.

Вот тут начинается самая сложная часть. Как из компонента получить форму, на которой он находится ?
Казалось бы, Component.Site.Container (типа IContainer) должен ей и соответствовать.
Вовсе нет.

Как выяснилось, класс Form даже не имплементирует интерфейс IContainer. А IContainer, который может получить компонент через свой Site - соответствует внутренней переменной, создаваемой дизайнером Visual Studio в коде формы:

private System.ComponentModel.IContainer components;

Тупик.

Но как-то же это можно сделать !
Дальнейшее исследование показало интересную вещь. Стандартному компоненту ErrorProvider тоже нужен доступ к объекту формы. И он каким-то образом добивается автоматиеского добавления следущей строчки в код процедуры InitializeComponent (автоматически создаваемой дизайнером форм):

this.errorProvider.ContainerControl = this;

что даёт ему ссылку на содержащую его форму.

Никаких специальных аттрибутов, которые бы позволяли добиться такого эффекта, мне обнаружить не удалось.
Но поиск в Google дал, всё-таки, интересные результаты. Как выяснилось, для этого нужно переопределить свойство Site в своём компоненте следующим образом:

public override ISite Site
{
get { return base.Site; }
set
{
base.Site = value;
if (value == null)
{
return;
}

IDesignerHost host = value.GetService(
typeof(IDesignerHost)) as IDesignerHost;

IComponent componentHost = host.RootComponent;
if (componentHost is ContainerControl)
{
ContainerControl = componentHost as ContainerControl;
}
}
}

В этом коде самым интересным является использование метода GetService интерфейса IServiceProvider, но об этом я не буду сейчас подробно говорить.

Вот пара ссылок на эту тему:
[1] Wired Prairie - Finding the Component Container
[2] Component/Control Partnership (google groups)





Previous post Next post
Up