msdn05_capa.JPG

Clique aqui para ler todos os artigos desta edição

 

Custom Crontrol Provider

por Dino Esposito

 

Às vezes, noto que os controles Windows® Forms do Visual Studio® .NET não oferecem algum recurso especial do qual necessito no aplicativo que estou construindo. Não estou afirmando que os controles do Windows Forms sejam inadequados, mas uma toolbox genérica provavelmente não atende aos requisitos de todos os usuários. Por exemplo, vamos considerar o controle TextBox. É fácil concordar a respeito de um conjunto mínimo de propriedades e métodos que esse controle deva suportar. No entanto, existe um vasto conjunto de propriedades extras que seriam bastante úteis. Por exemplo, suponha que você queira alterar a cor do plano de fundo (background color) quando o controle recebe o foco, uma máscara de entrada (input mask) ou uma lista autocomplete associada. É claro, eu prefiro ter um controle leve que possa ser estendido do que um componente inflado de recursos, que nem sempre usarei.

No Windows Forms a herança é apenas uma das opções para criar versões estendidas de controles existentes. Outra opção são os providers controls que adicionam novas propriedades aos controles existentes sem precisar usar herança. A idéia é que você use um extender control externo para fornecer novas propriedades e comportamentos aos controles existentes. A implementação dessas extensões não adiciona mais peso ao controle original; é uma alternativa leve à herança. Providers Controls tampouco substituem a herança; em vez disso, eles a estendem e a complementam de muitas maneiras.

Derive uma Nova Classe

Suponha que você queira trocar a cor de fundo (background color) de uma TextBox quando ela receber o foco. Uma opção é derivar uma nova classe. A Listagem 1 mostra uma classe simples que herda da TextBox e está vinculada aos eventos GotFocus e LostFocus. A classe AutoColorTextBox altera o background color quando o controle recebe o foco e restaura a cor original quando o evento LostFocus é invocado.

 

Listagem 1 O Controle Original

using System;

using System.Drawing;

using System.Windows.Forms;

 

namespace MsdnMag.ExtenderLib

{

  public class AutoColorTextBox : TextBox

  {

    public AutoColorTextBox()

    {

      SelectedBackColor = this.BackColor;

      m_oldBackColor = this.BackColor;

      this.GotFocus += new

      EventHandler(AutoColorTextBox_GotFocus);

      this.LostFocus += new

      EventHandler(AutoColorTextBox_LostFocus);

    }

    public Color SelectedBackColor;

    private Color m_oldBackColor;

 

    private void AutoColorTextBox_GotFocus(object sender, EventArgs e)

    {

      if (this.BackColor != SelectedBackColor)

      {

          m_oldBackColor = this.BackColor;

          this.BackColor = SelectedBackColor;

      }

    }

 

    private void AutoColorTextBox_LostFocus(object sender, EventArgs e)

    {

      if (m_oldBackColor != this.BackColor)

      {

          this.BackColor = m_oldBackColor;

          m_oldBackColor = this.BackColor;

      }

    }

  }

}

 

Você pode facilmente atribuir diferentes cores a cada textbox. Tudo que você precisa fazer é colocar instâncias da classe AutoColorTextBox no formulário e definir o background quando o controle receber o foco:

 

private void Form1_Load(object sender, EventArgs e)

{

  autoColorTextBox1.SelectedBackColor = Color.Cyan;

  autoColorTextBox2.SelectedBackColor = Color.Yellow;

  autoColorTextBox3.SelectedBackColor =

    Color.LightGreen;

}

 

A herança é uma excelente escolha quando você precisa personalizar o comportamento e a aparência dos controles. Mas, e se você quiser adicionar outro recurso à sua classe TextBox? Suponha que você queira que a textbox aceite apenas números e exiba todos os números negativos em vermelho. A Figura 1 mostra esta nova versão do controle. A classe AutoNumericTextBox herda da AutoColorTextBox definida anteriormente. Enquanto você precisar de ambos os recursos em todas as textboxes, a herança será a opção adequada.

 

image001.gif

Figura 1 O Controle Modificado

 

Mas e se você precisar usar extensões como atributos individuais? Da maneira como esses controles estão projetados, todos os recursos serão carregados mesmo que você precise apenas do modo numérico (veja a Listagem 2). Uma alternativa na qual as extensões sejam gerenciadas individualmente entrará em conflito com a idéia básica da herança. Na herança, um objeto possui todos os recursos da classe. Sendo assim, para ser capaz de combinar recursos básicos da classe TextBox com qualquer extensão possível, como cor automática (AutoColor) e modo numérico (Numeric mode), você precisa criar uma das seguintes classes: TextBox+AutoColor, TextBox+Numeric, ou TextBox+AutoColor+Numeric. Ajuste esse padrão para atender à complexidade (e à quantidade) de classes no mundo real e você entenderá por que a herança nem sempre é a melhor opção para a extensão de controles.

 

Listagem 2 Carregando Todos os Recursos

using System;

using System.Drawing;

using System.Windows.Forms;

 

namespace MsdnMag.ExtenderLib

{

  public class AutoNumericTextBox : AutoColorTextBox

  {

    public AutoNumericTextBox()

    {

      Value = 0.0f;

      NumericMode = false;

      m_oldForeColor = this.ForeColor;

      this.TextAlign = HorizontalAlignment.Right;

      this.LostFocus += new

        EventHandler(AutoNumericTextBox_LostFocus);

    }

 

    public double Value;

    private Color m_oldForeColor;

    public bool NumericMode;

 

    private void AutoNumericTextBox_LostFocus(object

      sender, EventArgs e)

    {

      if (NumericMode)

      {

        Value = Convert.ToDouble(this.Text);

        if (Value <0)

        {

          this.ForeColor = Color.Red;

          this.Font = new Font(Font.Name,

              Font.Size, FontStyle.Bold);  

        }

        else

        {

          this.ForeColor = m_oldForeColor;

          this.Font = new Font(Font.Name, Font.Size);  

        }

      }

    }

  }

}

 

Providers

O provider control permite estender os controles existentes sem que seja preciso criar nenhuma nova classe específica. O código que executa a extensão (eventos GotFocus e LostFocus) é carregado em uma classe externa que é anexada à classe base em run-time. O excelente suporte em design-time do Visual Studio .NET torna a presença dos providers controls praticamente imperceptível.

Para obter o código numérico e os recursos de cor automática, é necessário escrever dois provider controls; um para implementar a cor automática e o outro para o modo numérico. No formulário final, coloque apenas os providers de que realmente necessita. Desse modo, nenhum código desnecessário é carregado. Se precisar combinar duas ou mais extensões, você pode fazer isso vinculando mais providers ao mesmo controle.

Os provider controls provaram ser extremamente úteis em outros cenários também. Considere novamente a extensão que altera o background color de um controle TextBox. E se você quiser adicionar esse recurso aos controles RichTextBox e ListBox? Sem os providers sua única opção seria criar duas novas classes.

A beleza dos provider controls é que eles lhe permitem estender vários tipos de controles base. A principal função dos providers é adicionar propriedades personalizadas a controles existentes. Durante run-time, a infra-estrutura do Windows Forms submete o controle ao provider para a implementação real das propriedades estendidas. Ao fazer isso, o controle obtém uma referência à propriedade estendida e pode verificar seu tipo e nome, bem como ler e atualizar seu estado.

O Provider Control ToolTip

Antes de discutir como criar providers controls personalizados, vamos rever os extenders predefinidos oferecidos na toolbox do Windows Forms: controles ToolTip, HelpProvider e ErrorProvider. Todos eles estarão exibidos na área de componente da IDE do Visual Studio .NET, conforme mostrado na Figura 2. Cada provider control pode ter seu próprio conjunto de propriedades, que afetam a maneira como o controle trabalha. As propriedades específicas ao controle não têm nada a ver com as extensões adicionadas aos controles vinculados.

 

image002.gif

Figura 2 Ícones do Provider Control

 

O controle ToolTip lista as propriedades que lhe permitem configurar o tempo de exposição do texto pop-up, e o controle ErrorProvider permite alterar o ícone usado para representar a mensagem de erro e fazer com que o texto pisque sob demanda. Por fim, o controle HelpProvider necessita de um arquivo de Ajuda compilado para que funcione.

 

image003.gif

Figura 3 Propriedade ToolTip

 

Agora que você adicionou alguns controles de extensão ao formulário, selecione qualquer outro controle no formulário e role até o conjunto de propriedades dele. Você ficará surpreso em saber que todos os controles no formulário possuem agora uma propriedade ToolTip extra (veja a Figura 3). O nome exato da propriedade na grade é "ToolTip on toolTip1," onde toolTip1 é o nome do extender control. Defina essa propriedade para uma textbox e crie o projeto. O controle no formulário deve agora exibir uma tooltip. Selecione um botão e repita a operação. O botão também possui uma propriedade "ToolTip on toolTip1" que, quando definida, exibe uma tooltip quando o ponteiro do mouse passa sobre o botão. Isso significa que o extender control ToolTip pode estender virtualmente qualquer controle que você tenha em um formulário, com exceção apenas de outro controle ToolTip. Cada controle que puder ser exibido em um formulário receberá automaticamente uma propriedade ToolTip que, quando definida, exibirá uma descrição pop-up em run-time.

Desse modo, todos os controles que pertencem ao formulário compartilham o mesmo objeto ToolTip, que mantém uma tabela interna de pares control/caption. Assim como muitos outros controles do Windows Forms, o controle ToolTip é um wrapper criado em torno de um controle baseado em uma janela Win32®. A janela de tooltip do Win32 reside atrás do objeto ToolTip gerenciado. Essa janela é criada quando o objeto ToolTip gerenciado é instanciado e configurado para um estilo e delay. Em seguida, são adicionadas duas tabelas internas — uma para armazenar as associações de control/caption e outra para armazenar pares control/region.

 

Se estiver familiarizado com os controles ToolTip do Win32, você sabe que é possível usar uma única instância para exibir descrições pop-up em diferentes locais da tela. Em outras palavras, o funcionamento do controle ToolTip no Windows é significativamente diferente dos tooltips HTML, que são atributos vinculados a um elemento individual. O modelo no Visual Basic® 6.0 é semelhante ao cenário Web. Entretanto, não julgue esse comportamento por sua aparência. O mecanismo do Visual Basic 6.0 não é realmente diferente do modelo do Windows Forms. Na IDE do Visual Basic 6.0, a propriedade ToolTipText é uma propriedade estendida que o container ActiveX® adiciona a qualquer controle alojado. Sob a aparência desse modelo de programação aparentemente simples, somente uma janela tooltip Win32 é criada e usada para exibir texto para cada elemento constituinte que apresente uma legenda (caption) preenchida. Como fazer com que a janela tooltip exiba um texto sempre que o ponteiro do mouse passar sobre um dado controle? É simples — use regions.

Uma region tooltip consiste em uma área retangular, que a tooltip é chamada para monitorar. Sempre que o mouse entrar na área e parar ali pelo período de tempo especificado, a ToolTip moverá sua janela para esse local, definirá o caption e a exibirá na tela. No Windows Forms, cada controle que possua um caption preenchido recebe uma entrada na tabela de região (region). A região contém os limites da área de controle.

HelpProvider e ErrorProvider

O controle HelpProvider entra no jogo quando o usuário pressiona a tecla F1 enquanto o foco está em um controle do formulário. O HelpProvider adiciona três propriedades a cada controle: HelpNavigator, HelpKeyword e HelpString. HelpNavigator determina o que acontece quando o usuário solicita alguma ajuda. A propriedade aceita valores da enumeração (enum) do HelpNavigator e fornece acesso a elementos específicos do arquivo de Ajuda. HelpKeyword indica a palavra-chave ou o tópico a ser pesquisado, enquanto HelpString é exibida através de uma tooltip, conforme mostra a Figura 4. A propriedade HelpString independe de qualquer arquivo de Ajuda e exibe a tooltip quando a tecla F1 é pressionada.

 

image004.gif

Figura 4 Algum Texto da Ajuda

 

O ErrorProvider exibe um ícone ao lado de qualquer controle com uma mensagem de erro (veja a Figura 5). As propriedades adicionadas a cada controle respeitam os estilos gráficos do ícone — IconAlignment e IconPadding. Por padrão, o ícone é colocado à direita do controle e centralizado verticalmente. Não serão deixados pixels extras entre a borda do controle e o ícone, a não ser que você defina IconPadding como um valor diferente de zero.

 

image005.gif

Figura 5 Ícone de Erro

 

O controle ErrorProvider adiciona uma terceira entrada ao grid de propriedade de cada controle - Error. A propriedade Error contém a mensagem de erro que será exibida pelo controle. Diferentemente das tooltips, que raramente são determinadas em run-time, as mensagens de erro são uma propriedade dinâmica. Isso revela uma diferença importante entre os controles de extensão (extender controls) e as classes herdadas. As propriedades adicionadas a cada controle — digo, Error Message — não são realmente novas propriedades que você pode definir de forma programática. Você só pode definir propriedades estendidas em design-time porque o Visual Studio .NET faz esse truque, e não porque as propriedades sejam adicionadas dinamicamente aos controles. O código a seguir não compila, e a razão é óbvia - a propriedade Error não faz parte da interface de programação de uma TextBox -, mas você pode defini-la em design-time:

 

txtAge.Error = "Must be > 18";

 

Então, como definir dinamicamente uma mensagem de erro ou uma legenda de tooltip? Você faz isso usando um método no Extender Control. A linha de código anterior precisaria ser reescrita da seguinte maneira:

 

errorProvider1.SetError(txtAge, 18);

 

Você define a propriedade de texto da ToolTip em run-time usando uma sintaxe praticamente idêntica:

 

toolTip1.SetToolTip(txtAge, "Entre com uma idade maior que 18 ");

 

Existe um padrão por trás disso? Um Extender Control precisa fornecer um par de métodos GetXxx/SetXxx para cada propriedade Xxx que ele adiciona aos controles estendidos. Com esses métodos, um controle pode acessar ambas as propriedades em run-time. O protótipo desses métodos é mostrado aqui:

 

public string GetError(Control control);

public void SetError(Control control, string value);

 

Nessas assinaturas, o tipo da string é alterado de acordo com o tipo real da propriedade estendida. Acima de tudo, os Extender Controls são importantes por uma série de razões. Eles fornecem um modelo alternativo para estender os controles existentes, o qual é perpendicular à herança de classe. Em segundo lugar, o uso de extender Controls de forma sensata pode limitar significativamente a quantidade de código a ser escrito para cada formulário. Qual o fundamento comum sob a qual os controles de extensão devem basear sua funcionalidade? É a interface IExtenderProvider.

 

A Interface IExtenderProvider

A IExtenderProvider define apenas um método — CanExtend. A interface é definida da seguinte maneira:

 

public interface IExtenderProvider

{

  bool CanExtend(object extendee);

}

 

O Visual Studio .NET chama o método CanExtend para determinar quais objetos em um container devem receber as propriedades extensíveis. Qualquer componente que forneça propriedades extensíveis deve implementar o IExtenderProvider:

 

public class SimpleTextBoxExtender : Component, IExtenderProvider

{

 •••

}

 

À parte o fato de implementar a interface IExtenderProvider, um extensor é basicamente um controle. Por esse motivo, além de implementar a interface, você precisa fazer com que ela herde a funcionalidade básica do controle. No entanto, se você herdá-la de Control, o controle não poderá ser colocado na área de componentes (somente no formulário). Para fazer com que ele seja exibido na área de componentes, use Component como a classe base. Porém, em ambos os casos, o comportamento geral do extensor não mudará muito.

Uma classe de extender provider precisa ser marcada com um atributo [ProvideProperty]. O construtor do atributo de classe ProvideProperty usa dois argumentos. O primeiro parâmetro consiste no nome da propriedade a ser adicionada, e o segundo é tipo do objeto ao qual você fornece a propriedade.

O fragmento de código a seguir define um extender que adiciona a propriedade SelectedBackColor a todas as textboxes no formulário:

 

[ProvideProperty("SelectedBackColor", typeof(TextBox))]

class SimpleTextBoxExtender: Component, IExtenderProvider

{

   •••

}

 

Note que você também pode declarar a propriedade como uma propriedade estendida para virtualmente qualquer componente (usando IComponent ou Control ao invés de TextBox no atributo). Nesse caso, a implementação geralmente incluirá características que permitem que ela seja utilizada somente com uma categoria específica de controles.

Você implementa o método CanExtend para que ele retorne true para cada controle ao qual o Extender deseje adicionar propriedades. Veja aqui uma possível implementação de um simples extensor de TextBox:

 

public bool CanExtend(object extendee)

{

  return (extendee is TextBox);

}

 

Uma implementação similar também captura qualquer controle que herde da classe TextBox, inclusive as classes personalizadas criadas mais cedo. A Listagem 3 mostra o código-fonte de um extender control bastante simples. Ele implementa uma mudança no background color quando o controle recebe o foco. Em particular, o extensor (extender) define o par de métodos GetSelectedBackColor e SetSelectedBackColor. Eles podem ser chamados por qualquer textbox para obter ou definir programaticamente o background color. Além disso, o controle apresenta uma propriedade public, SelectedBackColor, que aparece na lista de propriedades e fornece a cor padrão. Observe que a propriedade não será exibida na lista de propriedades se você não implementá-la explicitamente por meio dos métodos de acesso get/set:

 

private Color m_SelectedBackColor;

public Color SelectedBackColor

{

  get {return m_SelectedBackColor;}

  set {m_SelectedBackColor = value;}

}

 

Listagem 3 Controle Extender

namespace Samples

{

  [ProvideProperty("SelectedBackColor",

    typeof(TextBox))]

  public class SimpleTextBoxExtender : Component,

        IExtenderProvider

  {

    public SimpleTextBoxExtender()

    {

       InitializeComponent();

 

       // Use uma hashtable para rastrear as cores

       // selecionadas para cada controle estendido

    }

 

    public bool CanExtend(object target)

    {

      return (target is TextBox);

    }

 

    private Color m_SelectedBackColor;

    public Color SelectedBackColor

    {

      get {return m_SelectedBackColor;}

      set {m_SelectedBackColor = value;}

    }

 

    private Color backupBackColor;

    public Color GetSelectedBackColor(Control control)

    {

      return SelectedBackColor;

    }

       

    public void SetSelectedBackColor(

      Control control, Color selColor)

    {

      TextBox t = (TextBox) control;

      SelectedBackColor = selColor;

      t.GotFocus += new

        EventHandler(TextBox_GotFocus);

      t.LostFocus += new

        EventHandler(TextBox_LostFocus);

    }

 

    private void InitializeComponent()

    {

      SelectedBackColor = Color.Cyan;

    }

 

    private void TextBox_GotFocus(object sender,

      EventArgs e)

    {

      TextBox t = (TextBox) sender;

      backupBackColor = t.BackColor;

      t.BackColor = SelectedBackColor;

    }

 

    private void TextBox_LostFocus(object sender,

      EventArgs e)

    {

      TextBox t = (TextBox) sender;

      t.BackColor = backupBackColor;

    }

  }

}

 

Se você usar esse controle em um aplicativo de exemplo, ele funcionará sem problemas. No entanto, ao ser codificado dessa maneira, o controle se torna muito simples mas não particularmente funcional. A principal desvantagem é que o background color é único em todas as textboxes. Além disso, forçar eventos é uma operação delicada, que necessita de mais atenção. Tente usar uma das textboxes derivadas com o extender e você verá a confusão resultante. Os extenders mais sérios precisam manter uma tabela dos controles vinculados e armazenar as configurações individualmente.

 

Escrevendo um Extender Control Personalizado

Vamos aprimorar o extensor TextBox simples para que ele forneça melhor suporte a cada controle estendido. O extender adicionará agora duas propriedades — as background color e foreground color (veja a Listagem 4). Cada controle estendido recebe uma classe de informação, que é estruturada desta maneira:

 

public class TextBoxInfo

{

  public Color SelectedBackColor;

  public Color OldBackColor;

  public Color SelectedForeColor;

  public Color OldForeColor;

  public bool  EventsWired;

}

 

Listagem 4 Adicionando Background e Foreground Color

[ProvideProperty("SelectedBackColor", typeof(TextBox))]

[ProvideProperty("SelectedForeColor", typeof(TextBox))]

public class TextBoxExtender : Component, IExtenderProvider

{

  private Hashtable Extendees;

 

  public TextBoxExtender()

  {

    InitializeComponent();

 

    // Use uma hashtable para rastrear as cores

    // selecionadas para cada controle estendido

    this.Extendees = new Hashtable();

  }

  •••

}

 

Essa classe rastreia as cores a serem usadas e também as cores a serem recuperadas, além de invocar os eventos GotFocus e LostFocus apenas uma vez, o que é indicado pela propriedade EventsWired. O código para os pares Get/Set é um pouco mais complicado. Comecemos pelo Get:

 

public Color GetSelectedBackColor(Control control)

{

  // Recuperar info relacionada

  TextBox t = (TextBox) control;

  TextBoxInfo info = (TextBoxInfo) Extendees[t];

 

  return info.SelectedBackColor;

}

 

O parâmetro control faz referência ao controle estendido atual — neste caso, uma TextBox. Após obter uma referência ao controle subjacente, você também poderá filtrar alguns controles com base no nome ou em qualquer outra propriedade que possa acessar. Você usa a referência ao controle como a chave para acessar a hashtable e recuperar a estrutura TextBoxInfo correspondente. Depois disso, retornar o background color a ser usado é o mais fácil. Quem está criando a entrada na hashtable, e quando? O método Set de uma das propriedades é responsável por inserir o controle na tabela. O controle não será adicionado se já existir uma entrada similar na hash table. A Listagem 5 mostra o código-fonte do método SetSelectedBackColor.

 

Listagem 5 Método SetSelectedBackColor

public void SetSelectedBackColor(Control control,

   Color selColor)

{

  TextBoxInfo info;

  TextBox t = (TextBox) control;

  if (!Extendees.ContainsKey(t))

      info = new TextBoxInfo();

  else

      info = (TextBoxInfo) Extendees[t];

 

  // Armazene o novo valor

  info.SelectedBackColor = selColor;

 

  // Se isso ainda não tiver sido feito,

  // invoque os eventos

  if (!info.EventWired)

  {

     t.GotFocus += new

       EventHandler(TextBox_GotFocus);

     t.LostFocus += new

       EventHandler(TextBox_LostFocus);

     info.EventWired = true;

  }

 

  // Adicione à tabela

  if (!Extendees.ContainsKey(t))

      Extendees[t] = info;

}

 

Estendendo o ToolTip Extender

A classe ToolTip é sealed e não pode ser herdada para adicionar mais recursos, tais como o estilo ballon. No Windows XP e nos sistemas operacionais mais recentes, a janela ToolTip recebeu o estilo TTS_BALLOON para exibir o caption em uma janela pop-up com estilo de balão. À primeira vista, derivar uma nova classe extender a partir da classe da ToolTip existente e adicionar suporte aos estilos de balão parecem uma tarefa relativamente fácil. O primeiro problema com o qual você se deparará é que a classe ToolTip é sealed, ou seja, ela não pode ser novamente herdada. No entanto, se usar agrupamento no lugar da herança, você poderá desenvolver um custom wrapper extender que explore as capacidades subjacentes da classe ToolTip.

A idéia é criar uma nova instância da classe ToolTip no construtor do extender e chamar os métodos GetToolTip e SetToolTip nos get e set da nova propriedade ToolTip.

 

[ProvideProperty("MyText", typeof(TextBox))]

public class MyToolTip : Component, IExtenderProvider

{

  private ToolTip _toolTip;

  public MyToolTip()

  {

     toolTip = new ToolTip();

  }

  •••

}

 

A classe MyToolTip incorpora uma instância da classe ToolTip criada automaticamente como uma propriedade private e expõe uma propriedade MyText estendida. A implementação dessa propriedade passa pela implementação da propriedade de texto (text) na classe ToolTip original:

 

public string GetMyText(Control control)

{

  return _toolTip.GetToolTip(control);

}

 

public void SetMyText(Control control,

  string caption)

{

  // TODO :: Insira suas alterações...

 

  toolTip.SetToolTip(control, caption);

}

 

Implementando esse código, você termina tendo seu próprio controle extender personalizado e faz com que ele trabalhe acima do componente ToolTip existente. Uma boa razão para estender a ToolTip consiste em usar um controle ToolTip personalizado. Os estilos multiline e balloon não são suportados nas ToolTips do Windows Forms, e a única maneira de adicionar esse recursos é através de um extender ToolTip personalizado. Infelizmente, dentre os três extenders predefinidos oferecidos, aquele de que você mais necessita – o componente ToolTip — é sealed e não pode ser herdado. É por isso que você precisa recorrer ao truque do agrupamento (agregação) de controles.

No entanto, o principal obstáculo no caminho à ampla personalização das ToolTips do Windows Forms reside em outro lugar. O controle ToolTip herda de Component, e não de Control. Existem controvérsias sobre qual dessas maneiras é a melhor. Se você herda uma classe de Component, a classe se comporta como um componente do controle, o que seria ótimo para extenders. Se você deriva a classe de Control, o componente resultante não poderá ser colocado na área do IDE. No entanto, ele herdará a propriedade Handle da classe base.

A propriedade Handle representa o HWND handle do controle Win32 subjacente. Como os Windows Forms são baseados amplamente em controles Win32, eles terminam por criar uma janela sempre que um objeto Control é instanciado. Para personalizar a aparência e o comportamento de um controle, você precisa recorrer às mensagens e estilos do Win32. E como defini-las se você não pode acessar o handle da janela subjacente?
  O controle ToolTip gerenciado possui uma propriedade Handle definida como um membro IntPtr. Lembre-se que o IntPtr é o tipo .NET Framework que mapeia um handle Win32, como HWND, HGLOBAL ou HKEY. A dificuldade reside no fato de que a propriedade Handle da ToolTip é declarada como private e, por esse motivo, ela é inacessível devido a seu nível de proteção. A classe ToolTip poderia ser expandida ainda mais por meio de agregação mas, na prática, as extensões que você realmente poderá adicionar estão limitadas ao que você pode fazer sem conhecer o HWND da janela ToolTip subjacente.

 

Conclusão

Os extenders controls representam uma alternativa à herança de controles. Ao escreve-los, você pode atingir duas metas. Primeiro, você pode implementar um nível mais fino de granularidade e decidir quais recursos adicionar a uma classe, sem precisar criar uma nova classe e herdando características em run-time. Segundo, você pode usar uma única classe de extensão para adicionar as mesmas características a muitos tipos de controles. Você poderia adicionar propriedades a textboxes e comboboxes usando um único extender; se você usasse a herança, seriam necessárias duas classes distintas. Além disso, enquanto a herança representa uma abordagem de codificação pura, os extenders tentam desviar a carga exigida em design time. Os extenders reduzem a quantidade de código necessária para escrever e empreender um modelo de programação que seja mais informativo e baseado em atributos.

Lembre-se, os extenders não substituem a herança, apenas a complementam e contribuem para enriquecer ainda mais o seu conjunto de ferramentas de programação.