Controles com transparência em C#
Autor: _ivan | Publicado: 2011-03-30T17:31:00-03:00

Blog meio largado às traças.
Ao menos é isso que deve parecer pra quem olha, mas não é isso não. Só não andava sabendo o que postar, ou como postar. E também andei meio empenhado por aqui aí acabei deixando o blog de lado um pouco, mas resolvi postar aqui uma coisa que considero interessante.


Quem já lidou na plataforma .Net e precisou fazer alguma coisa de interface um pouco complexa, já deve ter se deparado com o mesmo problema. A plataforma não suporta transparência, pelo menos não uma transparência de fato.
Se tu, por exemplo, selecionares um controle qualquer e mudares a propriedade BackColor para Color.Transparent o que ele faz na verdade é pintar o background do controle da mesma cor do background do container em que o controle se encontra, ou (o que efetivamente acontece) da mesma cor do que estiver selecionado como Parent.
Explicação rápida: Parent é uma propriedade do tipo Control, que armazena um controle que é "superior" em relação ao controle com o qual se está trabalhando. Ao menos é esse meu entendimento, a partir do que eu vi até hoje em relação a essa propriedade. Ela é bem comum em controles dentro de containers (Nota: container também é um controle), onde o container, geralmente, é o Parent dos controles dentro dele. Os controles dentro do container são chamados de Childs.
Voltando ao assunto, se tu pegares um controle que tem um canto arredondado, por exemplo, e colocá-lo por cima de outro controle, perceberás que o canto está pintado da cor do background do container, o que não é efetivamente transparente.
Isso não será um problema, se teus controles forem estáticos e não forem se sobrepor.
Mas e se eles forem?
Bom, a boa notícia é que há uma solução, a ruim é que, dependendo do que tu fores desenvolver tu podes não gostar muito do resultado, ou achar apenas razoável. É o meu caso, achei razoável.

Agora por partes.
Pesquisando na internet por transparência é possível encontrar coisas que não funcionam, como é o caso da Color.Transparent, ou coisas que não estão relacionadas com esse assunto, mas sim com uma transparência em relação às outras coisas na tela do computador, como a propriedade TransparencyKey. Usando essa propriedade, você pode escolher uma cor que fique completamente transparente no software, mostrando o que estiver atrás dele na tela do seu computador. Mas isso também não é o que eu quero.

Aí vem a questão. Dependendo do tipo de aplicação que tu estiveres desenvolvendo tu precisas de mais ou menos recursos. Por exemplo, tu simplesmente tens um controle com cantos arredondados, que está por cima de uma imagem e tu precisas que os cantos dele fiquem transparentes pra imagem aparecer corretamente. Ou tu estás criando controles que precisam se movimentar e são mais dinâmicos.
No meu caso, por exemplo, meus controles são espécies de quebra-cabeças, eles precisam se encaixar, e para isso há partes deles que precisarão ficar transparentes.
Basicamente, a implementação da transparência é uma só, mas se eles serão dinâmicos (o usuário poderá movimentá-los) há mais algumas coisas para prestar atenção além da implementação da transparência.

Primeiro uma explicação escrita, depois o código de fato.



Basicamente, o que eu fiz foi:
Criar uma classe que herda a classe Control. Na verdade eu criei um Component (clicar com o botão direito do mouse na pasta do projeto e então ir em Add &gt; Component) e substituí a classe herdada por Control.
No construtor usar o método SetStyle(ControlStyles flag, bool value) para habilitar a transparência e definir BackColor como Color.Transparent (sim, ainda tive que fazer isso, inicialmente achei que não fosse necessário, mas foi como funcionou melhor).
Sobrescrever CreateParams para habilitar transparência.
Sobrescrever OnPaintBackground(PaintEventArgs pevent) para não pintar o Background.
Sobrescrever OnPaint(PaintEventArgs e) para desenhar o controle como tu queres (explico melhor depois).



using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics;using System.Linq;using System.Text;using System.Windows.Forms;using System.Drawing;

namespace ControleTransparente{&nbsp;&nbsp;&nbsp; public partial class MeuControle : Control&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*Define um atributo do tipo Image e recebe uma imagem que faz parte dos recursos que eu carreguei para o meu software*/&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; private Image arqImagem = global::ControleTransparente.Properties.Resources.Imagem1;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; /*Ponto do meu controle onde eu desenharei meu texto.*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; private Point textLocation = new Point();
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public MeuControle() : base()&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; InitializeComponent();

&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; this.Name = "MeuControle";&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Usando método SetStyle para habilitar transparência.*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.SetStyle(
ControlStyles.SupportsTransparentBackColor, true);
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; this.BackgroundImage = new Bitmap(arqImagem);
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; this.Font = new Font("Arial Rounded MT Bold", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; this.ForeColor = Color.White;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; this.Text = "MeuControle";&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.BackColor = Color.Transparent;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Uma propriedade para alterar a localização do texto no controle.*/&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public Point TextLocation&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; get&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; retutn this.textLocation;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; set
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.textLocation = value;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Sobrescrevendo a propriedade CreateParams*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; protected override CreateParams CreateParams&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CreateParams createParams = base.CreateParams;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; createParams.ExStyle |= 0x00000020;&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return createParams;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Sobrescrevendo o OnPaintBackground*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; protected override void OnPaintBackground(PaintEventArgs pevent)
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*Aqui o background seria pintado. A ideia é justamente sobrescrever e deixar sem código para que nada seja pintado no plano de fundo.*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Sobrescrevendo o OnPaint*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; protected override void OnPaint(PaintEventArgs e)
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; base.OnPaint(e);
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; /*Aqui deve ser feita a parte visual do controle. Inserir o código que gera a aparência desejada.*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; /*No meu caso eu vou colocar uma imagem que é a imagem do meu controle e depois desenhar o texto, apenas para exemplificar.*/&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Nota: Para desenhar a imagem, é preciso que ela já esteja instanciada inicialmente, ou será gerada uma exceção.*/
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; e.Graphics.DrawImage(BackgroundImage, new Rectangle(0, 0, this.Size.Width, this.Size.Height));
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; /*Mede tamanho do texto.*/&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SizeF tamanhoTexto =&nbsp; e.Graphics.MeasureString(this.Text, this.Font);&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; /*Define a localização do texto de forma que ele fique centralizado no controle.*/&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.TextLocation = new Point((int)(this.Size.Width / 2 - tamanhoTexto.Width / 2), (int)(this.Size.Height / 2 - tamanhoTexto.Height / 2));&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), this.TextLocation);&nbsp;
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; }}

Esse é o código em si, mas eu gostaria de fazer algumas observações.

IMPORTANTE!

Esse é o código necessário para que o controle fique com aspectos de transparência em cantos arredondados e afins, mas no caso de controles que o usuário pode mover é preciso ter um cuidado.
Por exemplo, se dois controles estiverem sobrepostos e um deles for movido, as partes transparentes vão, diagamos assim, "armazenar lixo" do que estava sobre elas, ou seja, nem sempre elas vão ganhar o aspecto do fundo no novo local onde o controle movido foi posicionado. Pode ser necessário forçar o controle a se redesenhar (por completo ou parcialmente). Existe um método para isso chamado Invalidate().
Esse método possui 5 sobrecargas.
Eu não vou me deter em explicar cada uma delas, apenas explicarei a lógica rapidamente.
Quando o método Invalidate é chamado sem parâmetros ele força todo o controle a se redesenhar, chamando o OnPaint que foi sobrescrito no método acima.
É possível redesenhar apenas uma parte do controle passando um objeto da classe Rectangle ou uma Region como parâmetro, por exemplo.
Às vezes o Invalidate que tu vais precisar chamar não é o do controle movido em si, mas o do container.
Nesse caso, se você chamar Invalidate() apenas, todo o container será forçado a se redesenhar assim como os seus Childs, desnecessariamente. Nesses casos pode ser uma boa opção fazer com que apenas a região do controle movido seja redesenhada. Isso pode ser feito, por exemplo:

Tendo um Panel chamado panel1 e um objeto da classe acima chamado meuControle, chamar&nbsp; panel1.Invalidate(meuControle.Region) dentro de algum evento do controle ou do painel (qual evento você vai usar vai depender de como sua aplicação está estruturada).

Eu estou usando essa opção.
Seria possível ainda invalidar e redesenhar apenas a parte específica do controle que precisa ser transparente, se o resto dele for permanecer da mesma forma.


Por hoje era isso. Comentários ou dúvidas, é só postar.