How to make a custom control be updated in designer? - c#

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

Related

Why the scrollbar thumb moving to the starting position while leaving the thumb after dragging?

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);
}
}

How to add scrolling to Panel

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.

Children inherit parents appearance

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:

Draw rectangle on Panel C# Form

Question: How do i draw a rectangle on a Panel, rather than a form. Here is what my code looks like:
/*
* based on a some flags i determine which shape i want to draw.
* All shapes are stored in a list. I loop through the list
* and call each shape specific draw method - as shown below:.
*
*/
namespace myDrawProgram
{
private void panelArea_Paint(object sender, PaintEventArgs e)
{
if (drawWithPaint == true)
{
Pen p = new Pen(Color.Blue);
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
if (IsShapeRectangle == true)
{
e.Graphics.DrawRectangle(p, rect);
}
else if (IsShapeCircle == true)
{
e.Graphics.DrawEllipse(p, rect);
}
}
foreach (Shapes shape in listOfShapes)
{
shape.Draw(e.Graphics);
}
}
}
/*
* In another file i have my class which deals with
* drawing rectangles. It is as follows:
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using SETPaint;
namespace myDrawProgram
{
class TheRectangles : Shapes
{
public Rectangle MyRect { set; get; }
public TheRectangles(Rectangle rect, Color colour, Color boarderColour, Int32 brushThickness)
: base(colour, boarderColour, brushThickness)
{
MyRect = rect;
}
public override void Draw(Graphics g)
{
base.Draw(g);
g.FillRectangle(new SolidBrush(Shapes.c), MyRect);
g.DrawRectangle(new Pen(bc, MyBrushThickness), MyRect);
}
}
}
i'm assuming i need to do something like this:
using (Graphics g = this.panel1.CreateGraphics()) {}
I'm just not sure how to implement this with regards to my code...
It sounds like you haven't hooked up the paint event of the panel:
panelArea.Paint += new PaintEventHandler(panelArea_Paint);
If panelArea is the name of your form, then just change it to your panel:
panel1.Paint += new PaintEventHandler(panel1_Paint);
and then move your painting logic to that method:
private void panel1_Paint(object sender, PaintEventArgs e) {
// the rest of your drawing
}
It looks like the parent (form?) is responsible for drawing each of its controls.
I would not do it that way.
If you just need a control that draws shapes (and doesn't necessarily need other behavior of a Panel), I would just create a User Control that has a property indicating what shape to draw and make it responsible for its own rendering.
If you do need the behavior of a panel, you can subclass Panel and implement the drawing behavior in your subclassed control. Again, that makes the control responsible for its own rendering.
For info on user drawn controls see
http://msdn.microsoft.com/en-us/library/b818z6z6(v=vs.71).aspx

Override AutoSize for derived Label Control in C#

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);
}

Categories

Resources