Custom CheckBox in C# - c#

I want to have a custom CheckBox in C# that has a gradiant backgronud on it. I overrided OnPaint(PaintEventArgs e) as below:
Graphics g = e.Graphics;
base.OnPaint(e);
//// Fill the background
//SetControlSizes();
// Paint the outer rounded rectangle
g.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath outerPath = GeneralUtilities.RoundedRectangle(mLabelRect, 1, 0))
{
using (LinearGradientBrush outerBrush = new LinearGradientBrush(mLabelRect,
mGradientTop, mGradientBottom, LinearGradientMode.Vertical))
{
g.FillPath(outerBrush, outerPath);
}
using (Pen outlinePen = new Pen(mGradientTop, mRectOutlineWidth))
{
outlinePen.Alignment = PenAlignment.Inset;
g.DrawPath(outlinePen, outerPath);
}
}
//// Paint the gel highlight
using (GraphicsPath innerPath = GeneralUtilities.RoundedRectangle(mHighlightRect, mRectCornerRadius, mHighlightRectOffset))
{
using (LinearGradientBrush innerBrush = new LinearGradientBrush(mHighlightRect,
Color.FromArgb(mHighlightAlphaTop, Color.White),
Color.FromArgb(mHighlightAlphaBottom, Color.White), LinearGradientMode.Vertical))
{
g.FillPath(innerBrush, innerPath);
}
}
// Paint the text
TextRenderer.DrawText(g, Text, Font, mLabelRect, Color.White, Color.Transparent,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
It works and make a gradiant for background, but checkbox disappear under the gradiant and can't access. Now, what shall I do ??? please help me as soon as possible

EDIT:
OK I know what's wrong. The checkbox draws an underlaying background automatically that covers anything previously drawn.
In this case, you must draw the appearance of the checkbox (i.e. the checked state, etc.) by yourself.
You should override the OnPaintBackground function for drawing the background, instead of OnPaint.
Another option is to call base.OnPaint(e) after you've drawn the background.
The checkbox doesn't "disappear" under the gradient and you can still access it. You just drawn the "background" above the "foreground".
The base control draw the appearance of the checkbox in the base.OnPaint(e) function. If you draw anything after calling it, those things will be drawn as an "overlay" in front of the drawn checkbox, that's why you can't see the appearance of the checkbox.
If you are going to draw the Text by yourself also, you would not want the internally-drawn checkbox text to appear. In this case you will need to draw the appearance of the checkbox by yourself also.
As I already mentioned, if you are only going to draw a custom background, use OnPaintBackground instead.

Related

Overloading control drawing in c# has text rendering problems

I've sub-classed a control (ToolStripStatusLabel) to try and override the way it paints. At the moment I would expect this code to effectively do nothing, but it leads to a strange output:
protected override void OnPaint(PaintEventArgs e)
{
// Create a temp image to draw to and then put that onto the control transparently
using (Bitmap bmp = new Bitmap(this.Width, this.Height))
{
using (Graphics newGraphics = Graphics.FromImage(bmp))
{
// Paint the control to the temp graphics
PaintEventArgs newEvent = new PaintEventArgs(newGraphics, e.ClipRectangle);
base.OnPaint(newEvent);
// Copy the temp image to the control
e.Graphics.Clear(this.BackColor);
e.Graphics.DrawImage(bmp, new Rectangle(0, 0, this.Width, this.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);//, imgAttr);
}
}
}
When I run this code the text on the control comes out very strangely, the expected image is at the top, the actual output is at the bottom:
It looks like when the control is drawing the text the alpha blending with the antialiased text is going wrong.
Things that I've tried:
Setting the CompositingMode of e.graphics and newGraphics
Setting the TextRenderingHint.
setting the pixel format of newGraphics to 32Bpp ARGB and Premultiplied ARGB
Clearing the newGraphics with the controls background colour before asking the base class to render.
TL;DR: You'll need to render the text yourself with a custom renderer that re-implements OnRenderItemText, probably using graphics.DrawString() to do the drawing ultimately.
Another option would be to use a label in the status bar (as mentioned by #Reza Aghaei). Then you can set UseCompatibleTextRendering to true to make it use GDI+ rather than GDI
This seems to be an inherent problem in the way that the text is rendered at the lowest level. If you add a normal ToolStripStatusLabel and set its TextDirection to be Vertical90 then you get the same result, where the anti-aliasing of the text appears to have no alpha with the background.
Looking at the source you'll see that a very similar bit of code is called where the text is rendered to a bitmap and then in this case rotated:
using (Bitmap textBmp = new Bitmap(textSize.Width, textSize.Height,PixelFormat.Format32bppPArgb)) {
using (Graphics textGraphics = Graphics.FromImage(textBmp)) {
// now draw the text..
textGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
TextRenderer.DrawText(textGraphics, text, textFont, new Rectangle(Point.Empty, textSize), textColor, textFormat);
textBmp.RotateFlip((e.TextDirection == ToolStripTextDirection.Vertical90) ? RotateFlipType.Rotate90FlipNone : RotateFlipType.Rotate270FlipNone);
g.DrawImage(textBmp, textRect);
}
}
So this seems like a fundamental problem when text is rendered to a bitmaps graphics context (as opposed to the control's graphics context). Ultimately the code that is called is:
using( WindowsGraphicsWrapper wgr = new WindowsGraphicsWrapper( dc, flags ))
{
using (WindowsFont wf = WindowsGraphicsCacheManager.GetWindowsFont( font, fontQuality )) {
wgr.WindowsGraphics.DrawText( text, wf, bounds, foreColor, GetIntTextFormatFlags( flags ) );
}
}
Which I think is wading off into GDI (as opposed to GDI+) which has trouble with alpha on text.
Your best bet is to write a custom renderer that re-implements OnRenderItemText, probably with some 'inspiration' from the source from the default implementation.

Draw an antialias ellipse region for label

I drew an ellipse-like region for a label but i don't know how to set the antialias for it.
Snippet:
Rectangle circle = new Rectangle(0, 0, labelVoto.Width,labelVoto.Height);
var path = new GraphicsPath();
path.AddEllipse(circle);
labelVoto.Region = new Region(path);
and this is the result:
Can anybody help me?
Set the SmoothingMode of the Graphics object. Override OnPaintBackground instead of changing the Region. Regions do not support anti-aliasing. This example creates a customized label by deriving it from Label.
public class EllipticLabel : Label
{
protected override void OnPaintBackground(PaintEventArgs e)
{
// This ensures that the corners of the label will have the same color as the
// container control or form. They would be black otherwise.
e.Graphics.Clear(Parent.BackColor);
// This does the trick
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var rect = ClientRectangle;
rect.Width--;
rect.Height--;
using (var brush = new SolidBrush(BackColor)) {
e.Graphics.FillEllipse(brush, rect);
}
}
}
If you set the paint rectangle size equal to ClientRectangle. The ellipse will be clipped by one pixel to the right and at the bottom. Therefore I reduce its size by one pixel.
You set the desired background color of the ellipse by setting the BackColor property of the label in code or in the properties window.
Result:
Once you have compiled the code, the customized label automatically appears in the Toolbox in the current project.

How to create transparent controls in Windows Forms when controls are layered

I am trying to implement a "Fillable Form" in which editable text fields appear over top of an image of a pre-preprinted form for a dot matrix printer. (using c# and Windows Forms and targeting .Net 2.0) My first idea was to use the image as the Windows Form background, but it looked horrible when scrolling and also did not scroll properly with the content.
My next attempt was to create a fixed-size window with a panel that overflows the bounds of the window (for scrolling purposes.) I added a PictureBox to the panel, and added my textboxes on top of it. This works fine, except that TextBoxes do not support transparency, so I tried several methods to make the TextBoxes transparent. One approach was to use an odd background color and a transparency key. Another, described in the following links, was to create a derived class that allows transparency:
Transparency for windows forms textbox
TextBox with a Transparent Background
Neither method works, because as I have come to find out, "transparency" in Windows Forms just means that the background of the window is painted onto the control background. Since the PictureBox is positioned between the Window background and the TextBox, it gives the appearance that the TextBox is not transparent, but simply has a background color equal to the background color of the Window. With the transparency key approach, the entire application becomes transparent so that you can see Visual Studio in the background, which is not what I want. So now I am trying to implement a class that derives from TextBox and overrides either OnPaint or OnPaintBackground to paint the appropriate part of the PictureBox image onto the control background to give the illusion of transparency as described in the following link:
How to create a transparent control which works when on top of other controls?
First of all, I can't get it working (I have tried various things, and either get a completely black control, or just a standard label background), and second of all, I get intermittent ArgumentExceptions from the DrawToBitmap method that have the cryptic message "Additional information: targetBounds." Based on the following link from MSDN, I believe that this is because the bitmap is too large - in either event it seems inefficient to capture the whole form image here because I really just want a tiny piece of it.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.drawtobitmap(v=vs.100).aspx
Here is my latest attempt. Can somebody please help me with the OnPaintBackground implementation or suggest a different approach? Thanks in advance!
public partial class TransparentTextbox : TextBox
{
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e); // not sure whether I need this
if (Parent != null)
{
foreach (Control c in Parent.Controls)
{
if (c.GetType() == typeof(PictureBox))
{
PictureBox formImg = (PictureBox)c;
Bitmap bitmap = new Bitmap(formImg.Width, formImg.Height);
formImg.DrawToBitmap(bitmap, formImg.Bounds);
e.Graphics.DrawImage(bitmap, -Left, -Top);
break;
}
}
Debug.WriteLine(Name + " didn't find the PictureBox.");
}
}
}
NOTE: This has been tagged as a duplicate, but I referenced the "duplicate question" in my original post, and explained why it was not working. That solution only works if the TextBox sits directly over the Window - if another control (such as my Panel and PictureBox) sit between the window and the TextBox, then .Net draws the Window background onto the TextBox background, effectively making its background look gray, not transparent.
I think I have finally gotten to the bottom of this. I added a Bitmap variable to my class, and when I instantiate the textboxes, I am setting it to contain just the portion of the form image that sits behind the control. Then I overload OnPaintBackground to display the Bitmap, and I overload OnPaint to manually draw the text string. Here is the updated version of my TransparentTextbox class:
public partial class TransparentTextbox : TextBox
{
public Bitmap BgBitmap { get; set; }
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, new PointF(0.0F, 0.0F));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.DrawImage(BgBitmap, 0, 0);
}
}
... and here is the relevant part of how I instantiate:
Bitmap bgImage = (Bitmap)Bitmap.FromStream(Document.FormImage);
PictureBox pb = new PictureBox();
pb.Image = bgImage;
pb.Size = pb.Image.Size;
pb.Top = 0;
pb.Left = 0;
panel1.Controls.Add(pb);
foreach (FormField field in Document.FormFields)
{
TransparentTextbox tb = new TransparentTextbox();
tb.Width = (int)Math.Ceiling(field.MaxLineWidth * 96.0);
tb.Height = 22;
tb.Font = new Font("Courier", 12);
tb.BorderStyle = BorderStyle.None;
tb.Text = "Super Neat!";
tb.TextChanged += tb_TextChanged;
tb.Left = (int)Math.Ceiling(field.XValue * 96.0);
tb.Top = (int)Math.Ceiling(field.YValue * 96.0);
tb.Visible = true;
Bitmap b = new Bitmap(tb.Width, tb.Height);
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(bgImage, new Rectangle(0, 0, b.Width, b.Height), tb.Bounds, GraphicsUnit.Pixel);
tb.BgBitmap = b;
}
panel1.Controls.Add(tb);
}
I still need to work on how the text looks when I highlight it, and other things like that, but I feel like I am on the right track. +1 to Reza Aghaei and Mangist for commenting with other viable solutions!

Overriding DrawItem for ListBox - unselected items are not redrawn

This is a C# desktop application. The DrawStyle property of my ListBox is set to OwnerDrawFixed.
The problem: I override DrawItem to draw text in different fonts, and it works. But when I start resizing the form at the runtime, the selected item is drawn correctly, but the rest of them are not redrawn, causing text looking corrupt for unselected items.
Here's my code:
private void listDevices_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
string textDevice = ((ListBox)sender).Items[e.Index].ToString();
e.Graphics.DrawString(textDevice,
new Font("Ariel", 15, FontStyle.Bold), new SolidBrush(Color.Black),
e.Bounds, StringFormat.GenericDefault);
// Figure out where to draw IP
StringFormat copy = new StringFormat(
StringFormatFlags.NoWrap |
StringFormatFlags.MeasureTrailingSpaces
);
copy.SetMeasurableCharacterRanges(new CharacterRange[] {new CharacterRange(0, textDevice.Length)});
Region[] regions = e.Graphics.MeasureCharacterRanges(
textDevice, new Font("Ariel", 15, FontStyle.Bold), e.Bounds, copy);
int width = (int)(regions[0].GetBounds(e.Graphics).Width);
Rectangle rect = e.Bounds;
rect.X += width;
rect.Width -= width;
// draw IP
e.Graphics.DrawString(" 255.255.255.255",
new Font("Courier New", 10), new SolidBrush(Color.DarkBlue),
rect, copy);
e.DrawFocusRectangle();
}
listDevices.Items.Add("Device001");
listDevices.Items.Add("Device002");
Also, the item that is drawn correctly (the selected one) is flickering on form resizing. No biggie, but if anyone know why.... tnx
Put the following code in the Resize event:
private void listDevices_Resize(object sender, EventArgs e) {
listDevices.Invalidate();
}
This should cause everything to be redrawn.
To stop the flickering, you need double buffering.
To do this, make a new class, derived from ListBox, and put the following in the constructor:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Or just paste this into a code file:
using System.Windows.Forms;
namespace Whatever {
public class DBListBox : ListBox {
public DBListBox(): base() {
this.DoubleBuffered = true;
// OR
// this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
Replace "Whatever" with the namespace your project uses, or make it something more useful. AFter compiling, you should be able to add a DBListBox in the form designer.
I repro the problem. There are several mistakes in the code, the font name is "Arial", you should not adjust rect.Width, you forget to call Dispose() on the fonts, brushes and regions. But they don't explain the behavior. There's something wrong with the clipping area that prevents the text from being properly updated. I don't see where that occurs, the Graphics object state is okay.
Graphics.DrawString() is a very troubled method, you should really avoid it. All Windows Forms controls, including ListBox, use TextRenderer.DrawText(). That solves the problem when I use it. I know measuring is more difficult, you could work around that by displaying the IP address at a fixed offset. Looks better too, they'll line up in a column that way.
It flickers because you use e.DrawBackground(). That erases the existing text, you draw the text right back on it. I don't think double-buffering is going to fix that, you'd have to draw the entire item so you don't have to draw the background. Tricky if you can't get the exact size of the text with the large font, a workaround is to draw into a bitmap first.

Using DrawString to draw text with no border

I have this code;
public static Image AddText(this Image image, ImageText text)
{
Graphics surface = Graphics.FromImage(image);
Font font = new Font("Tahoma", 10);
System.Drawing.SolidBrush brush = new SolidBrush(Color.Red);
surface.DrawString(text.Text, font, brush, new PointF { X = 30, Y = 10 });
surface.Dispose();
return image;
}
However, when the text is drawn onto my image it's red with a black border or shadowing.
How can I write text to an image without any border or shadow?
EDIT
I solved this by adding;
surface.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
If someone can explain why I needed this that would be great.
When colored text was drawn on a newly created Bitmap object with its default transparent background, it had incomplete black border around it. Solved the problem by using graphics.Clear(Color.White) after initialization.
What you have there appears to be correct. See http://www.switchonthecode.com/tutorials/csharp-snippet-tutorial-how-to-draw-text-on-an-image
You could try TextRenderer.DrawText (though note that it's in the System.Windows.Forms namespace so this could possibly be inappropriate):
TextRenderer.DrawText(args.Graphics, text, drawFont, ClientRectangle, foreColor, backColor, TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter);
For the backColor try using Color.Transparent.
Also when working with your graphics, brushes, fonts, etc. be sure to wrap them in using statements so as to only keep them around as long as required...
e.g.
using (var surface = Graphics.FromImage(image))
{
var font = ... // Build font
using (var foreBrush = new SolidBrush(Color.Red))
{
... // Do stuff with brush
}
}
I think there should not any border.
Are it is border or shadow?
Just try with some another font and Font size + FontStyle.Regular and see is there any difference

Categories