Overlay component with alpha, redraw when controls under it redraw - c#

I created a custom control which I want to use to overlay part of my form with status information on demand. It should display a text and have a background color depending on the type of information. Here is the still incomplete code.
public partial class StatusPanel : UserControl
{
public enum PanelStyle
{
Info,
Warning,
Error
}
public PanelStyle Style { get; set; }
public StatusPanel()
{
InitializeComponent();
this.imgGreen = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgGreen))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(200, Color.Green));
g.FillRectangle(bg, 0, 0, 256, 256);
}
this.imgYellow = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgYellow))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(10, Color.Yellow));
g.FillRectangle(bg, 0, 0, 256, 256);
}
}
protected readonly Font font = new Font("Arial", 12.0F);
protected readonly Brush textBrush = new SolidBrush(Color.Black);
protected readonly Image imgGreen;
protected readonly Image imgYellow;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do not draw background
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Image img = GetImage();
e.Graphics.DrawImage(img, 0, 0, this.Width, this.Height);
e.Graphics.DrawString("ABC", this.font, this.textBrush, 1.0F, 1.0F);
}
protected Image GetImage()
{
return (this.Style == PanelStyle.Info) ? this.imgGreen : this.imgYellow;
}
}
This works quite fine. But when I put some buttons on a form and one of this controls infront of them, they will "overdraw" when the mouse moves over the button and the highlight effects redraws them.
How will my component be notified that it needs to redraw because underlying control have redrawn?

register a paint-handler to all controls under Parent.Control and a handler for Parent.ControlAdded that registers your paint-handler
something like this:
private void myDummyUserControl_Load(object sender, EventArgs e)
{
var uc = (DummyUserControl)sender;
uc.Parent.ControlAdded += new ControlEventHandler(Parent_ControlAdded);
foreach (Control c in uc.Parent.Controls)
{
if (uc == c)
continue;
c.Paint += new PaintEventHandler(c_Paint);
}
}
void c_Paint(object sender, PaintEventArgs e)
{
//checks & paint stuff here
}
void Parent_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Paint += new PaintEventHandler(c_Paint);
}
//edit
not sure if you need to use recursion to go down the tree of child controls and need to add handlers on those too ...

Related

Windows forms button background transparent

I have WPF app with ContentControl where the content is WindowsFormsHost with a Child having custom panel, which renders SDL stream. Now I have added button to disable/enable audio of the stream. Everything works fine, but I cannot make the button icon transparent. How can I do that? Is it possible at all?
AudioButton = new System.Windows.Forms.Button()
{
Enabled = AudioButtonEnabled,
BackColor = Color.Transparent,
Image = Image.FromFile(#".\Images\audioDisabled.png"),
Width = 30,
Height = 30,
FlatStyle = System.Windows.Forms.FlatStyle.Flat
};
AudioButton.FlatAppearance.BorderSize = 0;
AudioButton.Click += (object sender, EventArgs e) =>
{
};
SDLRenderer.AddButton(AudioButton);
The image (icon) is transparent as well.
The workaround can be to create custom WinForms button, override OnPaint event and make the specified color transparent for bitmap by calling Bitmap.MakeTransparent()
public class CustomButton : Button
{
private Color TransparentColor;
public CustomButton() : base()
{
TransparentColor = Color.FromArgb(192, 192, 192);
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.Image != null)
{
Bitmap bmp = ((Bitmap)this.Image);
bmp.MakeTransparent(TransparentColor);
int x = (this.Width - bmp.Width) / 2;
int y = (this.Height - bmp.Height) / 2;
e.Graphics.DrawImage(bmp, x, y);
}
base.OnPaint(e);
}
}

Drawing circles on top of a form

I've got a WinForms app and I want to programatically draw circles on top of certain areas. I'm running into a couple problems and any insight would be appreciated!
1) I've got code for drawing and clearing the circles (see below), but the circles are being drawn behind all my controls. I want them to be drawn as "top-most" in every case. How do I do this?
2) When my app starts up, I'll have some circles that need to be drawn right away. I tried drawing them on the Form Load event to no avail. But as to here (Form graphics not set when form loads) I'm now drawing it on the Paint event. While this works reasonably well (with a bool to make sure it only does it the first time), It seems to have problems with the this.Invalidate(); (as no circles are getting drawn). Is there a better way? Here's my code (parseText runs on a the index change of a comboBox):
private void parseText()
{
this.Invalidate();
List<string> lines = new List<string>(richTextBoxRaw.Text.Split(new string[] { Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries));
foreach (string s in lines)
{
switch (s)
{
case "<draw1>":
drawCircle(107, 26, 25);
break;
default:
break;
}
}
}
private void drawCircle(int x, int y, int transparency)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255,0,0));
Graphics graphics = this.CreateGraphics();
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (starting)
parseText();
starting = false;
}
One of not so complicated and yet working approaches of accomplishing your requirement could be to create custom transparent panel and place it on top of the controls where the red circles will be drawn.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DrawCircle(int x, int y, int transparency, Graphics graphics)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255, 0, 0));
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void TransparentPanel1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(10, 10, 255, e.Graphics);
}
private void Form1_Load(object sender, EventArgs e)
{
transparentPanel1.Enabled = false;
transparentPanel1.Paint += TransparentPanel1_Paint;
transparentPanel1.BringToFront();
}
}
public class TransparentPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
}

Windows form combobox custom form color

I have been playing around with customizing the WinForm combobox...So far I have the following:
Using this code:
public class ComboBoxWithBorder : ComboBox
{
private Color _borderColor = Color.Black;
private ButtonBorderStyle _borderStyle = ButtonBorderStyle.Solid;
private static int WM_PAINT = 0x000F;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics g = Graphics.FromHwnd(Handle);
Rectangle bounds = new Rectangle(0, 0, Width, Height);
ControlPaint.DrawBorder(g, bounds, _borderColor, _borderStyle);
}
}
[Category("Appearance")]
public Color BorderColor
{
get { return _borderColor; }
set
{
_borderColor = value;
Invalidate(); // causes control to be redrawn
}
}
[Category("Appearance")]
public ButtonBorderStyle BorderStyle
{
get { return _borderStyle; }
set
{
_borderStyle = value;
Invalidate();
}
}
}
However, I am trying to achieve something similar to this:
Is it possible to change the background color of the white dropdown box to a darker color?
Is it possible to change the dropdown list border from white to a different color?
Try This Class
Refer
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class FlatCombo : ComboBox
{
private Brush BorderBrush = new SolidBrush(SystemColors.Window);
private Brush ArrowBrush = new SolidBrush(SystemColors.ControlText);
private Brush DropButtonBrush = new SolidBrush(SystemColors.Control);
private Color _ButtonColor = SystemColors.Control;
public Color ButtonColor {
get { return _ButtonColor; }
set {
_ButtonColor = value;
DropButtonBrush = new SolidBrush(this.ButtonColor);
this.Invalidate();
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(m);
switch (m.Msg) {
case 0xf:
//Paint the background. Only the borders
//will show up because the edit
//box will be overlayed
Graphics g = this.CreateGraphics;
Pen p = new Pen(Color.White, 2);
g.FillRectangle(BorderBrush, this.ClientRectangle);
//Draw the background of the dropdown button
Rectangle rect = new Rectangle(this.Width - 15, 3, 12, this.Height - 6);
g.FillRectangle(DropButtonBrush, rect);
//Create the path for the arrow
Drawing2D.GraphicsPath pth = new Drawing2D.GraphicsPath();
PointF TopLeft = new PointF(this.Width - 13, (this.Height - 5) / 2);
PointF TopRight = new PointF(this.Width - 6, (this.Height - 5) / 2);
PointF Bottom = new PointF(this.Width - 9, (this.Height + 2) / 2);
pth.AddLine(TopLeft, TopRight);
pth.AddLine(TopRight, Bottom);
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
//Determine the arrow's color.
if (this.DroppedDown) {
ArrowBrush = new SolidBrush(SystemColors.HighlightText);
} else {
ArrowBrush = new SolidBrush(SystemColors.ControlText);
}
//Draw the arrow
g.FillPath(ArrowBrush, pth);
break;
default:
break; // TODO: might not be correct. Was : Exit Select
break;
}
}
//Override mouse and focus events to draw
//proper borders. Basically, set the color and Invalidate(),
//In general, Invalidate causes a control to redraw itself.
#region "Mouse and focus Overrides"
protected override void OnMouseEnter(System.EventArgs e)
{
base.OnMouseEnter(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseLeave(System.EventArgs e)
{
base.OnMouseLeave(e);
if (this.Focused)
return;
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
BorderBrush = new SolidBrush(SystemColors.Window);
this.Invalidate();
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
protected override void OnMouseHover(System.EventArgs e)
{
base.OnMouseHover(e);
BorderBrush = new SolidBrush(SystemColors.Highlight);
this.Invalidate();
}
#endregion
}

Fading control in C#

I'm tring to build a Control derived class which supports an Opcacity property.
This control could host both text and image and will beable to fade them out and in.
Here is my code:
internal class FadeControl : Control
{
private int opacity = 100;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
protected override void OnPaint(PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width - 1, Height - 1);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
g.FillRectangle(bckColor, bounds);
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
g.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
g.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
Parent.Invalidate(Bounds, true);
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
I've putted the control on a form which has a timer on it.
The timer set the control's opacity from 0 to 100 and back and its working well.
The problem I'm trying to solved is that the control flickers while changing its opacity.
Setting the control toControlStyles.DoubleBuffer will make the control invisible on the form.
Any advice will be welcome.
I was unable to use both a double buffer and WS_EX_TRANSPARENT (0x20) for the transparent background. So I decided to implement the transparent background by copying the content of the parent control and use double buffer to prevent flicker.
The following is the final source code, tested and working:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
internal class FadeControl : Control
{
private int opacity = 100;
private Bitmap backgroundBuffer;
private bool skipPaint;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothig
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
private void CreateBackgroundBuffer(Control parent)
{
int offsetX;
int offsetY;
GetOffsets(out offsetX, out offsetY, parent);
backgroundBuffer = new Bitmap(Width + offsetX, Height + offsetY);
}
protected override void OnResize(EventArgs e)
{
var parent = Parent;
if (parent != null)
{
CreateBackgroundBuffer(parent);
}
base.OnResize(e);
}
private void GetOffsets(out int offsetX, out int offsetY, Control parent)
{
var parentPosition = parent.PointToScreen(Point.Empty);
offsetY = Top + parentPosition.Y - parent.Top;
offsetX = Left + parentPosition.X - parent.Left;
}
private void UpdateBackgroundBuffer(int offsetX, int offsetY, Control parent)
{
if (backgroundBuffer == null)
{
CreateBackgroundBuffer(parent);
}
Rectangle parentBounds = new Rectangle(0, 0, Width + offsetX, Height + offsetY);
skipPaint = true;
parent.DrawToBitmap(backgroundBuffer, parentBounds);
skipPaint = false;
}
private void DrawBackground(Graphics graphics, Rectangle bounds)
{
int offsetX;
int offsetY;
var parent = Parent;
GetOffsets(out offsetX, out offsetY, parent);
UpdateBackgroundBuffer(offsetX, offsetY, parent);
graphics.DrawImage(backgroundBuffer, bounds, offsetX, offsetY, Width, Height, GraphicsUnit.Pixel);
}
private void Draw(Graphics graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width, Height);
DrawBackground(graphics, bounds);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
{
graphics.FillRectangle(bckColor, bounds);
}
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
{
graphics.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
}
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
graphics.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (!skipPaint)
{
Graphics graphics = e.Graphics;
Draw(graphics);
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
{
Parent.Invalidate(Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
Note that the method CreateParams is no longer present, also I've changed the contructor.
The field skipPaint is to know when not to paint in order to be able to able to tell the parent to draw itself to a bitmap during OnPaint without having infinite recursion.
backgroundBuffer is not to implement double buffering, but to keep a copy of the contents of the parent without the control rendered. It is updated each paint, I know there are more efficient solutions...* yet this approach keeps it simple and shouldn't be a bottleneck unless you have too many of these controls on the same container.
*: A better solution would be to update it each time the parent invalidates. Futhermore shared it among all the FadeControls in the same parent.

TabControl.DrawItem not firing on user painted TabControl

Hey, I've been trying to paint my own TabControl to get rid of the 3D Shadow but I am not having much success. The DrawItem event isn't firing at the moment. Do I have to shoot it myself? How do I do that?
Code:
namespace NCPad
{
public partial class NCE_TabControl : TabControl
{
Rectangle TabBoundary;
RectangleF TabTextBoundary;
public NCE_TabControl()
{
InitializeComponent();
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.Paint += new PaintEventHandler(this.OnPaint);
this.DrawItem += new DrawItemEventHandler(this.OnDrawItem);
}
protected void OnPaint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Red), e.ClipRectangle);
}
protected void OnDrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Blue), this.TabBoundary);
MessageBox.Show("hi");
}
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
this.TabBoundary = this.GetTabRect(0);
this.TabTextBoundary = (RectangleF)this.GetTabRect(0);
}
}
}
I'm not sure on this, but I believe if you specify the ControlStyles.UserPaint bit to true, then the DrawItem won't fire. The other ControlStyles (AllPaintingInWmPaint and DoubleBuffer) have dependencies on each other, though, so you would need to leave them off as well. However, not setting the UserPaint bit to true will result in the Paint event not getting fired. What I have been doing is overriding the OnPaintBackground method:
public partial class NCE_TabControl : TabControl
{
Rectangle TabBoundary;
RectangleF TabTextBoundary;
StringFormat format = new StringFormat(); //for tab header text
public NCE_TabControl()
{ InitializeComponent();
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.format.Alignment = StringAlignment.Center;
this.format.LineAlignment = StringAlignment.Center;
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.FillRectangle(new SolidBrush(Color.Red), 0, 0, this.Size.Width, this.Size.Height);
foreach (TabPage tp in this.TabPages)
{
//drawItem
int index = this.TabPages.IndexOf(tp);
this.TabBoundary = this.GetTabRect(index);
this.TabTextBoundary = (RectangleF)this.GetTabRect(index);
g.FillRectangle(new SolidBrush(Color.LightBlue), this.TabBoundary);
g.DrawString("tabPage " + index.ToString(), this.Font, new SolidBrush(Color.Black), this.TabTextBoundary, format);
}
}
}
I think this will work for you, but there may be other methods of doing it as well.
In order to get DrawItem event fired, set DrawMode = OwnerDrawFixed on the Tab Control
http://msdn.microsoft.com/en-us/library/system.windows.forms.tabcontrol.drawitem.aspx
One simple way is to change the TabControl's Appearance property to either Buttons or FlatButtons and set the DrawMode badk to Normal.

Categories

Resources