I've made a class GradientButton which suppose to be a Button which is filled with gradient background.
I draw gradient filling in the OnPaintBackground() method. Unfortunately it is never invoked, of course I added a GradientButton to a Form via toolbox:
public class GradientButton : Button {
public Color Color1 { get; set; }
public Color Color2 { get; set; }
public float Angle { get; set; }
public GradientButton() {
Color1 = Color.YellowGreen;
Color2 = Color.LightGreen;
Angle = 30;
}
protected override void OnPaintBackground(PaintEventArgs e) {
base.OnPaintBackground(e);
Debug.WriteLine("This never prints");
using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle,
Color1,
Color2,
Angle)) {
e.Graphics.FillRectangle(brush, this.ClientRectangle);
}
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
Invalidate();
}
}
Question: How fill the button's background with the gradient? Why OnPaintBackground is not invoked? As far as I know it should be calledbefore OnPaint method.
This is because the Button class has ControlStyles.Opaque flag set, which according to the documentation:
If true, the control is drawn opaque and the background is not painted.
You can turn it off in your class constructor
SetStyle(ControlStyles.Opaque, false);
and your OnPaintBackground override will be invoked.
However, it would not help a lot - there is a reason the flag to be set to true - the OnPaint draws both background and face of the button, so whatever you do in OnPaintBackground will not have any affect of the button appearance. Unfortunately there is no option to paint just the background, so you need to override the OnPaint and actually draw everything yourself.
You need to set the style of the form in the constructor ...
this.SetStyle(ControlStyles.UserPaint, true);
to ensure the OnPaint method is overridden. There are many settings for the ControlStyle which you can combine
I would do this instead.
Firstly, change your constructor to this:
public GradientButton()
{
Color1 = Color.YellowGreen;
Color2 = Color.LightGreen;
Angle = 30;
Paint += new PaintEventHandler(GradientButton_Paint);
}
And then add the below procedure:
private void GradientButton_Paint(object sender,PaintEventArgs e)
{
Debug.WriteLine("This never prints");
using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle,Color1,Color2,Angle))
{
e.Graphics.FillRectangle(brush, this.ClientRectangle);
}
}
I'm not entirely sure why your code doesn't work, but the way I've described always works for me. Hope that's good enough.
Related
I'm developing a Windows Desktop Application using C# in VS 2022 on Windows 10. I'm developing for a touch screen and want the UI to be very intuitive and give good feedback because the user(s) will likely be tech-averse. On several of the forms I'm using a PictureBox as a button because I like the visual effects better. I can get a nice "button pressed" effect by using the MouseDown and MouseUp events to change the border style of the PictureBox to Fixed3D (on mouse down) and back to None (on mouse up). The only issue is that the PictureBox image "blinks" when I do this, like the control is clearing the image out and reloading it or something.
My code is rather trivial, but I'll post it here anyway just in case:
private void Button_Down(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
pb.BorderStyle = BorderStyle.Fixed3D;
}
private void Button_Up(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
pb.BorderStyle = BorderStyle.None;
}
If you're open to solving your blinking problem another way, consider this reusable CustomButton class that lets you use your own custom image to depict the 3D pressed state. The icons are superimposed using the Text property and a custom font containing glyphs (making it easy to change their size and color). When the button is not pressed, system theme takes over or you could unset the UseVisualStyleBackColor property to additionally customize things like OnMouseHover.
CustomButton inherits from Buttonand has a PrivateFontCollection giving it access to a .ttf file containing glyphs. This particular flashlight-filter-history-favorite-search.ttf is one I designed for my own project using the Fontello open-source icon font generator.
public CustomButton()
{
UseCompatibleTextRendering = true;
TextAlign = ContentAlignment.MiddleCenter;
refCount++;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode) initFont();
}
private void initFont()
{
if (privateFontCollection == null)
{
privateFontCollection = new PrivateFontCollection();
var path = Path.Combine(Path.GetDirectoryName(
Assembly.GetEntryAssembly().Location),
"Fonts",
"flashlight-filter-history-favorite-search.ttf");
privateFontCollection.AddFontFile(path);
var fontFamily = privateFontCollection.Families[0];
GlyphFontUp = new Font(fontFamily, 16F);
GlyphFontDown = new Font(fontFamily, 15F);
}
Font = GlyphFontUp;
ForeColor = GlyphColorUp;
}
PrivateFontCollection privateFontCollection = null;
public static Font GlyphFontUp { get; private set; } = null;
public static Font GlyphFontDown { get; private set; } = null;
public static Color GlyphColorUp { get; } = Color.Teal;
public static Color GlyphColorDown { get; } = Color.DarkCyan;
private static int refCount = 0;
protected override void Dispose(bool disposing)
{
if (disposing)
{
refCount--;
if (refCount == 0)
{
GlyphFontUp?.Dispose();
privateFontCollection?.Dispose();
}
}
base.Dispose(disposing);
}
When the mouse is down the button has an image, the icon is smaller, and the icon color changes. When the mouse comes up the image is removed and everything goes back to normal.
partial class CustomButton : Button
{
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Image = new Bitmap(Resources.buttonDown, Size);
Font = GlyphFontDown;
ForeColor = GlyphColorDown;
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Font = GlyphFontUp;
ForeColor = GlyphColorUp;
Image = null;
}
}
The buttonDown image is just something I drew myself. I'm pretty sure you could do better!
The CustomButton class can be swapped out in the MainForm.Designer.cs file.
// private System.Windows.Forms.Button customButton0;
private intuitive_buttons.CustomButton customButton0;
This code assigns the various icons to the buttons:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// Assign the icons to the buttons
customButton0.Text = "\uE800";
customButton1.Text = "\uE801";
customButton2.Text = "\uE802";
customButton3.Text = "\uE803";
customButton4.Text = "\uE804";
}
}
Hope this at least gives you a few ideas to try.
I'm creating a custom PictureBox.
As you can see, it's a PictureBox designed for profile photos
Well, this is the class of the CircularPictureBox
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Hector.Framework.Controls
{
public class CircularPictureBox : PictureBox
{
private Color _idlecolor = Color.White;
public CircularPictureBox()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
this.DoubleBuffered = true;
this.BackColor = Color.White;
this.SizeMode = PictureBoxSizeMode.StretchImage;
this.Size = new Size(100, 100);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
using (var gpath = new GraphicsPath())
{
var brush = new SolidBrush(this.IdleBorderColor);
var pen = new Pen(brush, 5);
var outrect = new Rectangle(-1, -1, this.Width + 5, this.Height + 5);
gpath.AddEllipse(outrect);
pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
pe.Graphics.DrawPath(pen, gpath);
brush.Dispose();
pen.Dispose();
gpath.Dispose();
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
using (var gpath = new GraphicsPath())
{
var rect = new Rectangle(1, 1, this.Width - 1, this.Height - 1);
gpath.AddEllipse(rect);
this.Region = new Region(gpath);
gpath.Dispose();
}
}
public Color IdleBorderColor
{
get => this._idlecolor;
set => this._idlecolor = value;
}
}
}
My problem is that since it is a control that can be used from the designer, I want it to have properties such as edge width or border color.
I started testing with the color, but it is that whenever I change the color,
Visual Studio shows me an error message saying that The value of the property is not valid
I made a few modifications to your code, to highlight some features that can be useful in the design of Custom Control.
The modifications I've made I think are self-explanatory.
However, take a look at the OnPaint event. The e.Graphics.Clip Region lets you hide all graphics parts that are not in the selected region. This implies that when you drag the control in Design Mode, the Image will be clipped and won't be seen outside the region area.
The PixelOffsetMode.HighQuality and SmoothingMode.AntiAlias contributes to the overall quality of the rendering (there are commented out options that can useful in other situations).
The calculation of the Border offset must reference the BorderSize width, and scaled accordingly. The Pen object draws starting from the middle of its size. If a Pen has a size of 3 pixels, 1 Pixel is drawn on the border, one outside the area and one inside (weird? Maybe).
The transparency settings is just a "fake" here.
It might be used effectively in other situations (it should read "Platforms").
public class CircularPictureBox : PictureBox
{
private Bitmap bitmap;
private Color borderColor;
private int penSize;
private Color alphaColor = Color.FromArgb(0, 255,255,255);
private bool enhancedBuffering;
public CircularPictureBox()
{
InitializeComponent();
this.SetStyle(ControlStyles.SupportsTransparentBackColor |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
private void InitializeComponent()
{
this.enhancedBuffering = true;
this.bitmap = null;
this.borderColor = Color.Silver;
this.penSize = 7;
this.BackColor = alphaColor;
this.SizeMode = PictureBoxSizeMode.StretchImage;
this.Size = new Size(100, 100);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.CompositingMode = CompositingMode.SourceOver;
//e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
//e.Graphics.InterpolationMode = InterpolationMode.Bicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
if (this.Region != null) e.Graphics.Clip = this.Region;
var rect = this.ClientRectangle;
if (bitmap != null) {
e.Graphics.DrawImage(bitmap, rect);
}
rect.Inflate(-penSize / 2 + 1, -penSize / 2 + 1);
using (var pen = new Pen(borderColor, penSize)) {
e.Graphics.DrawEllipse(pen, rect);
}
}
protected override void OnResize(EventArgs e)
{
using (var path = new GraphicsPath()) {
path.AddEllipse(this.ClientRectangle);
path.CloseFigure();
using (Region region = new Region(path)) {
this.Region = region.Clone();
}
}
}
[Description("Gets or Sets the Image displayed by the control"), Category("Appearance")]
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public Bitmap Bitmap
{
get { return bitmap; }
set { bitmap = value; Invalidate(); }
}
[Description("Gets or Sets the size of the Border"), Category("Behavior")]
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public int BorderSize
{
get { return penSize; }
set { penSize = value; Invalidate(); }
}
[Description("Gets or Sets the Color of Border drawn around the Image.")]
[Category("Appearance")]
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public Color BorderColor
{
get { return borderColor; }
set { borderColor = value; Invalidate(); }
}
[Description("Enables or disables the control OptimizedDoubleBuffering feature")]
[Category("Useful Features")] //<= "Useful feature" is a custom category
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public bool EnhancedBuffering
{
get { return enhancedBuffering; }
set { enhancedBuffering = value;
SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
UpdateStyles();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public new Image ErrorImage
{
get { return null; }
set { base.ErrorImage = null; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public new Image InitialImage
{
get { return null; }
set { base.InitialImage = null; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public new Image BackgroundImage
{
get { return null; }
set { base.BackgroundImage = null; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never), BrowsableAttribute(false)]
public new Image Image {
get { return null; }
set { base.Image = null; }
}
}
Some System.ComponentModel Attributes that can help shaping the Control.
For example, Description and Category attributes:
(These have been inserted in the custom Property BorderColor of your control).
[Description("Gets or Sets the Color of the Border drawn around the Image.")
[Category("Appearance")]
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
Description of course explains the user what the Property is for.
Category is used to give the Properties an organic disposition inside the PropertyGrid. You can use standard names (Appearance, Behavior etc.) or specify anything else.
Give the Category a custom name and it will be listed among the others, when the Categorized view is in use.
The Image property of the Custom Control has been hidden and substituted with a Bitmap Property:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never), BrowsableAttribute(false)]
The EditorBrowsable Attribute is a hint to Intellisense that lets you determine whether to show a property or method in the Popup menu. It can be Never, Always or Advanced (for those who know how to reach VS Options). Properties and Methods will be Hidden when the Custom Control is deployed (as a dll), not while you are designing it.
The BrowsableAttribute Attribute (or just [Browsable]) allows to specify whether that Property should be shown in the PropertyGrid.
The DesignerSerializationVisibility
With the DesignerSerializationVisibility Attribute, you can indicate
whether the value for a property is Visible, and should be persisted
in initialization code, Hidden, and should not be persisted in
initialization code, or consists of Content, which should have
initialization code generated for each public, not hidden property of
the object assigned to the property.
Also interesting:
TypeConverter(typeof(System.ComponentModel.ExpandableObjectConverter))
With this Attribute, you can instruct to list the Public Properties of a Class Object in the PropertyGrid.
This Class Object can be an internal Class that serializes a complex Property of a Control.
The TypeConverter Class is very interesting itself.
I want my ToolStrip Background to change when an Item is not saved.
To render the background of my toolstrip I use my own renderer:
class ToolStripRenderer : ToolStripProfessionalRenderer
{
private MenuBarForm parent;
public ToolStripRenderer(MenuBarForm Parent)
{
parent = Parent;
}
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
{
if (parent.controlItems.Last().Unsaved)
e.Graphics.FillRectangle(new System.Drawing.Drawing2D.LinearGradientBrush(e.ToolStrip.ClientRectangle, SystemColors.ControlLightLight, Color.Red, 90, true), e.AffectedBounds);
else
e.Graphics.FillRectangle(new System.Drawing.Drawing2D.LinearGradientBrush(e.ToolStrip.ClientRectangle, SystemColors.ControlLightLight, SystemColors.ControlDark, 90, true), e.AffectedBounds);
}
}
The first time the toolstrip renders it renders correctly with a grey to dark grey design:
But when the bar should become red, only the buttons which the mouse hovers over become red:
I would like the whole toolstrip the be red-colored at once.
I already tried changing e.AffectedBounds to e.ToolStrip.Bounds, to no avail.
You can create a custom color table inheriting ProfessionalColorTable and override relevant properties to change background color:
public class CustomColorTable : ProfessionalColorTable
{
public override Color ToolStripGradientBegin
{
get { return Color.Red; }
}
public override Color ToolStripGradientMiddle
{
get { return Color.Red; }
}
public override Color ToolStripGradientEnd
{
get { return SystemColors.ControlLightLight; }
}
}
To change your ToolStrip background, assign a new ToolStripProfessionalRenderer which uses your custom color table to ToolStripManager.Renderer:
ToolStripManager.Renderer = new ToolStripProfessionalRenderer(new CustomColorTable());
To set the original professional renderer:
ToolStripManager.Renderer = new ToolStripProfessionalRenderer();
I Found this solution Thanks to FSDaniel comment:
By adding Invalidate() to the end of the OnRenderToolStripBackground the toolstrip did indeed become fully red but also caused the application to go into a infinite loop. I solved this by creating an event that was triggered by changing the UnSaved property. The form that has the toolstrip then subscribed a method to this event which called toolstrip.Invalidate(). This way Invalidate() is only used when necessary.
Is is possible to disable Application.EnableVisualStyles(); programmatically? I wanted to turn off the visual styles for a certain part of my application where I could have a colored progress bar. I know you can use System.Drawing to draw it on, but this is much more simpler if I could turn it off temporarily. Is this possible or am I going to have to draw it?
Credits go to GreatJobBob for linking me the MSDN page, the following achieved what I was looking for.
using System.Windows.Forms.VisualStyles;
Application.VisualStyleState = VisualStyleState.NonClientAreaEnabled;
This let me change the color of my progress bar without changing the rest of my controls and form.
Create your own progress bar class. Disabling Application.EnableVisualStyles will cause problems in other UIs such as the MessageBox. Here's a basic class to get you started, just change the forecolor to what you want it to be.
using System;
using System.Drawing;
using System.Windows.Forms;
class MyProgressBar : Control
{
public MyProgressBar()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.Selectable, false);
Maximum = 100;
this.ForeColor = Color.Red; //This is where you choose your color
this.BackColor = Color.White;
}
public decimal Minimum { get; set; }
public decimal Maximum { get; set; }
private decimal mValue;
public decimal Value
{
get { return mValue; }
set { mValue = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
var rc = new RectangleF(0, 0, (float)(this.Width * (Value - Minimum) / Maximum), this.Height);
using (var br = new SolidBrush(this.ForeColor))
{
e.Graphics.FillRectangle(br, rc);
}
base.OnPaint(e);
}
}
I have a form with a menu and a toolstrip at the top. The menuStrip has a nice looking gradient background, how can I get the same effect on the toolStrip control? I know about the RenderMode property but changing this doesn't have the desired result.
You can achieve this with a custom renderer.
public class CustomToolStripRenderer : ToolStripProfessionalRenderer
{
public CustomToolStripRenderer() { }
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
{
//you may want to change this based on the toolstrip's dock or layout style
LinearGradientMode mode = LinearGradientMode.Horizontal;
using (LinearGradientBrush b = new LinearGradientBrush(e.AffectedBounds, ColorTable.MenuStripGradientBegin, ColorTable.MenuStripGradientEnd, mode))
{
e.Graphics.FillRectangle(b, e.AffectedBounds);
}
}
}
Then set your toolstrip to use an instance of this renderer.
public Form1()
{
InitializeComponent();
CustomToolStripRenderer r = new CustomToolStripRenderer();
r.RoundedEdges = false;
toolStrip1.Renderer = r;
}