В процессе подготовки к экзамену по матфизике задался интересным вопросом.
Пусть у нас есть компонент (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)