I am trying to extend the System.Windows.Forms.Label class to support vertically drawn text. I do this by creating a new property called MyLabelOrientation that the user can set to Horizontal or Vertical. When the user changes this setting, the values for width and height are swapped to resize the control to its new orientation. Finally, I override the OnPaint function to draw my Label.
I would like to extend the AutoSize property for this control as well so that my Label will auto-size to the text it contains. For the horizontal orientation, the base functionality implements this for me. For the vertical orientation, I create a Graphics object and set the height of the control to the width of the SizeF object returned from Graphics.MeasureString(Text, Font). You can see an example of the code I'm using below.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
public class MyLabel : Label
{
public enum MyLabelOrientation {Horizontal, Vertical};
protected MyLabelOrientation m_orientation = MyLabelOrientation.Horizontal;
[Category("Appearance")]
public virtual MyLabelOrientation Orientation
{
get { return m_orientation; }
set
{
m_orientation = value;
int temp = Height;
Width = Height;
Height = temp;
Refresh();
}
}
private Size ResizeLabel()
{
Graphics g = Graphics.FromHwnd(this.Handle);
SizeF newSize = g.MeasureString(Text, Font);
if (m_orientation == MyLabelOrientation.Horizontal)
Width = (int)newSize.Width;
else
Height = (int)newSize.Width;
}
protected override void OnPaint(PaintEventArgs e)
{
Brush textBrush = new SolidBrush(this.ForeColor);
if (m_orientation == LabelOrientation.Vertical)
{
e.Graphics.TranslateTransform(Width, 0);
e.Graphics.RotateTransform(90);
e.Graphics.DrawString(Text, Font, textBrush, Padding.Left, Padding.Top);
}
else
{
base.OnPaint(e);
}
}
}
However, setting AutoSize to true seems to prevent and/or override any changes to the size of the control. This means that I can't change the width or height when I want to change the Label's orientation. I'm wondering if this behavior can be overridden, so that I can test whether AutoSize is set, and then adjust the size of the control according to it's orientation.
I know this a a pretty old question, but i stumbled across it today and was wondering how to do the same thing.
My solution to the problem was overriding the GetPreferredSize(Size proposedSize) method. I used a button class that houses an arrow in addition to the text which, of course, was not taken into account using the AutoSize property so i added additional space and it works fine for me.
Given the problem of changing orientation or switching width and height, you could completely change the way the preferred size is calculated.
public override Size GetPreferredSize(Size proposedSize)
{
Size s = base.GetPreferredSize(proposedSize);
if (AutoSize)
{
s.Width += 15;
}
return s;
}
I have not done this before, I believe you can theoretically override a property declaration (via the new keyword) and check the orientation before proceeding:
override public bool AutoSize
{
set
{
if( /* orientation is horizontal */ )
{
base.AutoSize = value;
}
else
{
// do what you need to do
}
}
}
If think a solution is to override OnResize itself :
protected override void OnResize(EventArgs e)
{
if (AutoSize)
{
// Perform your own resizing logic
}
else
OnResize(e);
}
Related
I am adding a new way to distinguish the user privileges in my program.
It is a small circular panel that appears after the username and that changes color depending on its privileges and that is shown after the user nick leaving a spacing of 5 pixels
:
private void SetNick(string nick)
{
this.NickLabel.Text = nick;
this.NickLabel.Left = ((this.ProfilePicturePanel.ClientSize.Width - this.NickLabel.Width) / 2) - 5;
Hector.Framework.Utils.Ellipse.Apply(this.BadgePanel, 6);
this.BadgePanel.Top = this.NickLabel.Top + 3;
this.BadgePanel.Left = this.NickLabel.Width + this.BadgePanel.Width + 5;
}
The nick of the user has a minimum of 3 characters and a maximum of 6 characters, then when the nickname has 6 characters (example: Jhon S), the panel is aligned correctly:
But if the nickname have 3 characters (example: Ben), then this happens:
It is assumed that the panel should always be shown near the label leaving a space of 5 pixels even if the label changes its content.
Could you tell me what I'm doing wrong?
You can override the Label Control and write your own implementation that draws your badge directly in the Label. Here's a simple example.
public class LabelWithBadge : Label
{
public Color BadgeColor { get; set; }
private Size BadgeSize { get; set; }
public LabelWithBadge()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
if (BadgeColor == null)
BadgeColor = Color.Red;
if (BadgeSize == null)
BadgeSize = new Size(20, 20);
}
protected override Size SizeFromClientSize(Size clientSize)
{
var textSize = TextRenderer.MeasureText("doesn't matter", this.Font);
this.BadgeSize = new Size(textSize.Height, textSize.Height);
var baseSize = base.SizeFromClientSize(clientSize);
return new Size(baseSize.Width + BadgeSize.Width, baseSize.Height);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(this.BadgeColor), this.ClientSize.Width - this.BadgeSize.Width, 0, this.BadgeSize.Width, this.BadgeSize.Height);
}
}
By overriding SizeFromClientSize you can control the AutoSize ability of the label, and pad it to make room for your badge.
If you want to support manual sizing for the badge, then you'll need to tweak this to work with AutoSize off.
Then I set Styles on the control to handle painting. Overriding OnPaint allows you to draw in the extra padded on in the SizeFromClientSize override.
I added a property for the Badge Color. The Badge Size is determined by the font on the control using TextRenderer.MeasureText. So if you make the font bigger, the badge get's bigger with it.
This control will show up in your Toolbox when you build. Then you can use it like any other label but this one has a badge in it.
I have created a custom control for having the scrolling support. This control consist of two scrollbars namely HScollBar and VScrollBar.
When i drag and leave the thumb at the particular location, it moves to the starting location(0).
How can i prevent the thumb moving to the default position and is there any easy way to achieve the custom scroll control using the scrollbars?
You don't need to use scrollbars for scrolling. You can set AutoScroll to true. Also if your control is a custom paint control, set AutoScrollMinSize to a suitable value. For a normal container control, you don't need to manipulate AutoScrollMinSize yourself, it will be calculated based on locations of child controls. For example:
using System.Drawing;
using System.Windows.Forms;
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.AutoScroll = true;
}
protected override void OnPaint(PaintEventArgs e)
{
//for a custom paint control, calculate the minimum size which needs scrollbars
//for a normal container control you don't need to calculate minimum size
this.AutoScrollMinSize = new Size(300, 500);
base.OnPaint(e);
var r = this.DisplayRectangle;
r.Width--; r.Height--;
e.Graphics.DrawRectangle(Pens.Red, r);
TextRenderer.DrawText(e.Graphics, "Top-Left", Font, r, ForeColor,
TextFormatFlags.Top | TextFormatFlags.Left);
TextRenderer.DrawText(e.Graphics, "Bottom-Right", Font, r, ForeColor,
TextFormatFlags.Bottom | TextFormatFlags.Right);
}
}
I have created a custom control derived from Panel.
Everything works fine, except that in designer my control is only redrawn when it loses focus.
What am I missing?
Here is the code of CustomControl.cs
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Picwing
{
public partial class xPanel : Panel
{
private SizeF textSize;
public xPanel() {
InitializeComponent();
}
[Browsable(true)]
public override string Text {
get { return base.Text; }
set { base.Text = value; }
}
protected override void OnPaint(PaintEventArgs pe) {
base.OnPaint(pe);
textSize = pe.Graphics.MeasureString(Text, Font);
pe.Graphics.DrawRectangle(new Pen(ForeColor, 1), pe.ClipRectangle.X, pe.ClipRectangle.Y + textSize.Height / 2, pe.ClipRectangle.Width - 1, pe.ClipRectangle.Height - textSize.Height / 2 - 1);
pe.Graphics.FillRectangle(new SolidBrush(BackColor), 5, 0, textSize.Width, textSize.Height);
pe.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), pe.ClipRectangle.X + 6, pe.ClipRectangle.Y);
}
}
}
This is a print screen taken after moving label1 inside xPanel1, before it loses focus.
Your problem is because of using pe.ClipRectangle while painting on control.
Don't use pe.ClipRectangle. Use DisplayRectangle or ClientRactangle instead, based on your requirement.
When you draw in pe.ClipRectangle bounds, as you can see, the drawing will be done on the smallest invalidated part of your control.
Note:
You don't need to have InitializeComponent(); in constructor unless you use designer of your panel component to add some other components to it.
If you used DisplayRectangle, then in the FillRectangle method, instead of 0,5 use DisplayRectangle.X + 5, DisplayRectangle.Y.
If you are trying you draw a custom GroupBox, you can take a look at Custom GroupBox BackColor to Transparent
Basically, I've created an extension of the panel class that adds draws multiple bitmaps onto itself In order to create multiple musical staves. I've tried adding a vertical scroll bar onto the panel but that hasn't worked. My Paint procedure is similar to this
private void StavePanel_Paint(object sender, PaintEventArgs e)
{
for(int i = 0; i < linenumber; i++)
{
Bitmap bmp = new Bitmap(Width, 200);
//edit bmp to resemble stave
e.Graphics.DrawImage(bmp,new Point(0,200*i);
}
}
Just set the AutoScrollMinSize property:
panel1.AutoScrollMinSize = new Size(0, 1000);
During the paint event, you need to translate the positions of your drawing by using the TranslateTransform method. Also, you need to dispose your bitmaps after you draw them:
e.Graphics.TranslateTransform(panel1.AutoScrollPosition.X, panel1.AutoScrollPosition.Y);
using (Bitmap bmp = new Bitmap(Width, 200)) {
//edit bmp to resemble stave
e.Graphics.DrawImage(bmp,new Point(0,200*i);
}
or create and store them ahead of time to avoid that cost during the paint event.
Set the AutoScroll property to true.
You might also consider alternatives:
FlowLayoutPanel and add PictureBoxes dynamically instead of painting.
TableLayoutPanel and add PictureBoxes dynamically instead of painting.
extend ListBox and set the DrawMode property to OwnerDrawFixed or OwnerDrawVariable and then override the methods OnPaint and OnMeasureItem (only for OwnerDrawVariable).
If you want to continue using your existing pattern of calling GDI code to paint your control you should add a scrollbar control and add an event handler to its change event. The change handler doesn't need to do anything other than call .Invalidate on the panel. .Invalidate is a signal to the control that it is "dirty" and needs to be redrawn. You will need to modify your painting code to offset the drawing in the inverse direction of the scrollbar value.
So if your scrollbar is at position 50, you should draw everything at Y - 50.
If you are using pure GDI drawing code there is no need to mess with the AutoScroll property at all. That is only used if your panel hosts an actual control that is larger than the panel.
As others mentioned, you need to set AutoScroll to true. But then, anytime you add or remove a bitmap (or at the beginning if they are fixed), you need to set the AutoScrollMinSize height using the formula bitmapCount * bitmapHeight. Also in your paint handler you need to consider the AutoScrollPosition.Y property.
Here is a small example of the concept in action:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var panel = new Panel { Dock = DockStyle.Fill, Parent = form };
// Setting the AutoScrollMinSize
int bitmapCount = 10;
int bitmapHeight = 200;
panel.AutoScrollMinSize = new Size(0, bitmapCount * bitmapHeight);
panel.Paint += (sender, e) =>
{
// Considering the AutoScrollPosition.Y
int offsetY = panel.AutoScrollPosition.Y;
var state = offsetY != 0 ? e.Graphics.Save() : null;
if (offsetY != 0) e.Graphics.TranslateTransform(0, offsetY);
var rect = new Rectangle(0, 0, panel.ClientSize.Width, bitmapHeight);
var sf = new StringFormat(StringFormat.GenericTypographic) { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
for (int i = 0; i < bitmapCount; i++)
{
// Your bitmap drawing goes here
e.Graphics.FillRectangle(Brushes.Yellow, rect);
e.Graphics.DrawRectangle(Pens.Red, rect);
e.Graphics.DrawString("Bitmap #" + (i + 1), panel.Font, Brushes.Blue, rect, sf);
rect.Y += bitmapHeight;
}
if (state != null) e.Graphics.Restore(state);
};
Application.Run(form);
}
}
}
EDIT: As LarsTech correctly mentioned in the comments, you don't really need to set AutoScroll property in this case. All other remains the same.
I have created a simple custom panel using ContainerControl as my base. I've added custom properties to create borders and gradient backgrounds. If I override OnPaint and OnPaintBackground all child controls of the parent inherit the gradient and border styles. As a work around I have used the parents BackgroundImage property which works fine but has a few random quirks. There has to be a better way of approaching this issue but I have found no solution. Are there any Window API functions via Interop or other C# methods to fix this? If so please provide an example.
EDIT! Here is the style being copied (ugly example but makes the point):
EDIT 2! Here is a simple hard-coded ContainerControl without all the properties, designer attributes, etc.
public class Container : ContainerControl
{
protected override void OnPaintBackground( PaintEventArgs e )
{
using ( var brush = new LinearGradientBrush( e.ClipRectangle, Color.Red, Color.Blue, LinearGradientMode.Vertical ) )
{
e.Graphics.FillRectangle( brush, e.ClipRectangle );
}
}
}
If a Label control is created with its BackColor property set to Color.Transparent, it will end up calling its parent's OnPaintBackground() implementation.
If you modify Jon's example like this:
var label = new Label {
Text = "Label",
Location = new Point(20, 50),
BackColor = Color.Transparent
};
Then you will reproduce the issue.
There is an easy workaround, however. The problem comes from the way you're creating the linear gradient brush. Since you're passing e.ClipRectangle to its constructor, the shape of the gradient will vary depending on the control being rendered (container or label). On the other hand, if you pass the ClientRectangle of the container, then the gradient will always have the same shape and the result should be what you're looking for:
protected override void OnPaintBackground(PaintEventArgs e)
{
using (var brush = new LinearGradientBrush(ClientRectangle,
Color.Red, Color.Blue, LinearGradientMode.Vertical)) {
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
The result is:
Initialize the properties on control create/load
Then "INVALIDATE" the control to force a redraw of the control
I can't reproduce this simply on my Windows 7 machine - which suggests it may be one of the properties you've set in the designer. Short but complete program:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class GradientContainer : ContainerControl
{
protected override void OnPaintBackground(PaintEventArgs e)
{
using (var brush = new LinearGradientBrush(e.ClipRectangle,
Color.Red, Color.Blue, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
}
class Test
{
static void Main()
{
var label = new Label {
Text = "Label",
Location = new Point(20, 50)
};
var container = new GradientContainer {
Size = new Size(200, 200),
Location = new Point(0, 0),
Controls = { label }
};
Form form = new Form {
Controls = { container },
Size = new Size(300, 300)
};
Application.Run(form);
}
}
And the result: