I want to create Rounded button using class instead of XAML style
This code work in WinForms app, how i can convert it to WPF code?
public class RoundButton : Button
{
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(0, 0, ClientSize.Width, ClientSize.Height);
this.Region = new System.Drawing.Region(grPath);
base.OnPaint(e);
}
}
Assuming you have a really good reason for doing this (like if you really want to custom draw in more complex scenarios like drawing diagrams or similar), you could do something like:
public class RoundButton : Button
{
public RoundButton()
{
DefaultStyleKey = typeof(RoundButton);
}
protected override void OnRender(DrawingContext dc)
{
double radius = 10;
double borderThickness = 1; // Could get this value from any of the this.BorderThickness values
dc.DrawRoundedRectangle(Background, new Pen(BorderBrush, borderThickness), new Rect(0, 0, Width, Height), radius, radius);
}
}
But I really, really recommend going down the XAML route instead in this case. Custom drawing does not make sense here at all.
One obvious issue with the above code, for instance, is that for it to work, you have to disable the default button style or a button will be drawn on top of your drawing.
In this case a style for RoundButton doesn't exist and the control does not define a placeholder for where text or other content should go. If you want this, you are better off defining this style with a control template and could just as well put in the visuals there.
Related
I've been asked to carry out some modernisation work on a C# WinForms application, and I'm using VS2019 with C#.Net 4.7.2.
The project owner wants to change the border colour of all the legacy Windows controls that were used originally. The initial idea was - using the MetroIT framework - to keep the original Windows controls in the Form Designer, but override them by defining new classes which extend the MetroIT equivalents but changing the class type of the declarations in the Designer's InitializeComponent() method.
That approach doesn't really work as a "drop-in" replacement because the MetroIT controls tend to subclass Control whereas existing code expects the legacy Windows properties/methods to be available.
Instead, therefore, I went down the route of trying to override the OnPaint method. That worked fine for CheckBox and RadioButton, but I'm now struggling to get it to work for a ListBox. The following is as far as I've got; it certainly isn't correct, as I'll explain, but it feels sort of close ?
public class MyListBox : ListBox
{
public MyListBox()
{
SetStyle(ControlStyles.UserPaint, true);
this.DrawMode = DrawMode.OwnerDrawVariable;
BorderStyle = BorderStyle.None;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush textBrush = new SolidBrush(Color.Black);
float y = 1;
foreach (String strItem in Items)
{
e.Graphics.DrawString(strItem, DefaultFont, textBrush, 1, y);
y += DefaultFont.Height;
}
Rectangle borderRectangle = this.ClientRectangle;
ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Blue, ButtonBorderStyle.Solid);
}
}
Initially, with no data in the ListBox, the control is drawn correctly.
However, as soon as a scroll bar appears, the base Windows code draws the scroll bar over the top of my border instead of just inside it (whereas an unmodified ListBox re-draws the border around the top, bottom and right-hand side of the scroll bar):
Is there a way to change my code so that the border draws itself around the edges of the scroll bar instead of excluding it ?
The other way in which my code is wrong is that, as soon as I start to scroll, the border painting code is applying itself to some of the ListBox items:
Is there a way I can complete this, or am I wasting my time because the base scroll bar cannot be modified ?
Thanks
EDIT 1:
Further to the suggestions of #GuidoG:
Yes, to be clear, I really only want to change the border to blue. Happy to leave the listbox items as they would ordinarily be painted WITHOUT any border.
So, to achieve that, I have removed your suggested code to do DrawItem, and now I only have the code to paint the border - but clearly I must be doing something wrong, because the listbox has now reverted to looking like the standard one with the black border.
So here's what I have now:
In my Dialog.Designer.cs InitializeComponent():
this.FieldsListBox = new System.Windows.Forms.ListBox();
this.FieldsListBox.FormattingEnabled = true;
this.FieldsListBox.Location = new System.Drawing.Point(7, 98);
this.FieldsListBox.Name = "FieldsListBox";
this.FieldsListBox.Size = new System.Drawing.Size(188, 121);
this.FieldsListBox.Sorted = true;
this.FieldsListBox.TabIndex = 11;
this.FieldsListBox.SelectedIndexChanged += new System.EventHandler(this.FieldsListBox_SelectedIndexChanged);
In my Dialog.cs Form declaration:
public SelectByAttributesDialog()
{
InitializeComponent();
BlueThemAll(this);
}
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
private void Blue(Control control)
{
Debug.WriteLine("Blue: " + control.Name.ToString());
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
I know that Blue() is being called for all the controls in this dialog, in the sense that the Debug.WriteLine() is outputting every control name, but no borders are being changed to blue (and certainly not the listbox).
I've been away from Windows Form programming for a very long time so I apologise for that, and I'm clearly doing something wrong, but have no idea what.
Solution 1: let the form drawn blue borders around everything:
This means you dont have to subclass any controls, but you put some code on the form that will draw the borders for you around each control.
Here is an example that works for me
// method that draws a blue border around a control
private void Blue(Control control)
{
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
// recursive method that finds all controls and call the method Blue on each found control
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
where to call this ?
// draw the blue borders when the form is resized
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
panel3.Invalidate(); // on panel3 there is a control that moves position because it is anchored to the bottom
Update();
BlueThemAll(this);
}
// draw the borders when the form is moved, this is needed when the form moved off the screen and back
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
BlueThemAll(this);
}
// draw the borders for the first time
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
BlueThemAll(this);
}
If you also want blue borders around each item of the listbox:
To get some border around each item in your listbox use the DrawItem event in stead of the paint event, maybe this link could help.
I modified the code to get it drawing blue borders
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += ListBox1_DrawItem;
private void ListBox1_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
Rectangle r = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width - 1, e.Bounds.Height);
using (Pen selPen = new Pen(Color.Blue))
{
e.Graphics.DrawRectangle(selPen, r);
}
e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
}
catch { }
}
and it looks like this
Solution 2: Subclassing the listbox:
If you need this in a subclassed listbox, then you can do this.
However, this will only work if the listbox has at least one pixel space left around it, because to get around the scrollbar you have to draw on the parent of the listbox, not on the listbox itself. The scrollbar will always draw itself over anything you draw there.
public class myListBox : ListBox
{
public myListBox(): base()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
BorderStyle = BorderStyle.None;
this.DrawItem += MyListBox_DrawItem;
}
private void MyListBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
Rectangle r = new Rectangle(this.Left - 1, this.Top - 1, this.Width + 2, this.Height + 2);
using (Graphics g = this.Parent.CreateGraphics())
{
ControlPaint.DrawBorder(g, r, Color.Blue, ButtonBorderStyle.Solid);
}
}
}
and this will look like so
I have created a customized textbox which has border in red color. I then launch my application but this OnPaint never gets called.
My code is this:
public partial class UserControl1 : TextBox
{
public string disable, disableFlag;
public string Disable
{
get
{
return disable;
}
set
{
disable = value;
disableFlag = disable;
//OnPaint(PaintEventArgs.Empty);
}
}
public UserControl1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
this.Text = "testing 1";
if (disable == "true")
{
Pen redPen = new Pen(Color.Red);
Graphics graphics = this.CreateGraphics();
graphics.DrawRectangle(redPen, this.Location.X,
this.Location.Y, this.Width, this.Height);
// base.DrawFrame(e.Graphics);
}
}
}
Please let me know what is the problem (Here is the snapshot of the winform http://prntscr.com/ceq7x5 )?
You shouldn't create a new Graphics object. Use e.Graphics.DrawRectangle to be able to draw on the control using the already existing Graphics object like this:
Pen redPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(redPen, this.Location.X,
this.Location.Y, this.Width, this.Height);
Also, to repeat my comment here about the Disabled flag. There's no point adding a custom Disable flag. Use the Enabled property that is already provided by the Windows Forms TextBox control.
Edit: Please notice that the above code doesn't work in TextBox's case, because it handles drawing differently. TextBox is basically just a wrapper around the native Win32 TextBox so you need to listen to quite a few messages that tell it to repaint itself. You also need to get the handle to the device context and transform it to a managed Graphics object to be able to draw.
Take a look at this article for code and explanations on how to draw on top of TextBox. Especially part 2. Drawing Onto the TextBox on the bottom of the page.
I have created a button using the code below. Adding the start.Text="Start"; statement does nothing. How do I add a label to my button?
public MainForm()
{
InitializeComponent();
myButtonObject start = new myButtonObject();
EventHandler myHandler = new EventHandler(start_Click);
start.Click += myHandler;
start.Location = new System.Drawing.Point(200, 500);
start.Size = new System.Drawing.Size(101, 101);
start.Text="Start";
// start.TextAlign = new System.Drawing.ContentAlignment.MiddleCenter;
this.Controls.Add(start);
}
public class myButtonObject : UserControl
{
// Draw the new button.
protected override void OnPaint(PaintEventArgs e)
{
Graphics graphics = e.Graphics;
Pen myPen = new Pen(Color.Black);
// Draw the button in the form of a circle
graphics.FillEllipse(Brushes.Goldenrod, 0, 0, 100, 100);
graphics.DrawEllipse(myPen, 0, 0, 100, 100);
myPen.Dispose();
}
}
You have implemented your own button as a user control. Since you are implementing OnPaint to provide your own draw functionality, you need to implement all the functionality like drawing the text too.
If you want to go down this route then you also need to add the logic to draw the text on the control in your OnPaint method. This can be done using the graphics.DrawString method.
See http://msdn.microsoft.com/en-us/library/system.drawing.graphics.drawstring.aspx
You also need to call graphics.dispose.
If you aren't familiar with this, then it might be simpler to use a UserControl and add a label to it, or something similar, and then to draw your circle shape on the top of that.
You should draw the button's text yourself in the OnPaint method:
TextRenderer.DrawText(graphics, Text, Font, new Point(5, 5), SystemColors.ControlText);
Where new Point(5, 5) - is a top left position of the text.
You paint the button ok but you don't draw the text.
A usercontrol doesn't do this by itself.
Just add something like this to the paint commands:
graphics.DrawString(Text, yourfont, yourBrush, x, y);
you must and may have to decide freely on x, y font and brush.
Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).
I want to make a panel have a thick border. Can I set this somehow?
PS, I am using C#. VS 2008.
Jim,
I've made a user control and given is a ParentControlDesigner. As I indicated in my comment it's not a perfect solution to what you're asking for. But it should be a good starting point. Oh any FYI, I've got it with a customizable border color too. I was inspired by another SO post to pursue this... It was trickier than I expected.
To get things to rearrange correctly when setting the border size a call to PerformLayout is made. The override to DisplayRectangle and the call to SetDisplayRectLocation in OnResize cause the proper repositioning of the child controls. As well the child controls don't have the expected "0,0" when in the upper left most... unless border width is set to 0... And OnPaint provides the custom drawing of the border.
Best of luck to ya! Making custom controls that are parents is tricky, but not impossible.
[Designer(typeof(ParentControlDesigner))]
public partial class CustomPanel : UserControl
{
Color _borderColor = Color.Blue;
int _borderWidth = 5;
public int BorderWidth
{
get { return _borderWidth; }
set { _borderWidth = value;
Invalidate();
PerformLayout();
}
}
public CustomPanel() { InitializeComponent(); }
public override Rectangle DisplayRectangle
{
get
{
return new Rectangle(_borderWidth, _borderWidth, Bounds.Width - _borderWidth * 2, Bounds.Height - _borderWidth * 2);
}
}
public Color BorderColor
{
get { return _borderColor; }
set { _borderColor = value; Invalidate(); }
}
new public BorderStyle BorderStyle
{
get { return _borderWidth == 0 ? BorderStyle.None : BorderStyle.FixedSingle; }
set { }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaintBackground(e);
if (this.BorderStyle == BorderStyle.FixedSingle)
{
using (Pen p = new Pen(_borderColor, _borderWidth))
{
Rectangle r = ClientRectangle;
// now for the funky stuff...
// to get the rectangle drawn correctly, we actually need to
// adjust the rectangle as .net centers the line, based on width,
// on the provided rectangle.
r.Inflate(-Convert.ToInt32(_borderWidth / 2.0 + .5), -Convert.ToInt32(_borderWidth / 2.0 + .5));
e.Graphics.DrawRectangle(p, r);
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetDisplayRectLocation(_borderWidth, _borderWidth);
}
}
Just implement the panel's Paint event and draw a border. For example:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
panel1.Paint += panel1_Paint;
}
VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal);
private void panel1_Paint(object sender, PaintEventArgs e) {
renderer.DrawEdge(e.Graphics, panel1.ClientRectangle,
Edges.Bottom | Edges.Left | Edges.Right | Edges.Top,
EdgeStyle.Raised, EdgeEffects.Flat);
}
}
}
Play around with the arguments to find something you like. You ought to add code to fallback to ControlPaint.DrawBorder if visual styles aren't enabled. Meh.
If this is just about presentation, put a panel that fills the form with a background color of the border color you want and a Dock style of Fill. Place another panel inside this one with the standard background color and a Dock style of Fill. Play with Padding and Margin of the two panels to get the border size you wish (I forget which param applies correclty to the inner panel and the outer panel). Place your controls on the interior panel. With both panels set to Dock=Fill, form resizing is automatically handled for you. You may need to experiment with some of the controls, but I have done this many times with no issues for both application main windows and popup forms.
This is an old post but I still find it useful. And I just found another way.
ControlPaint.DrawBorder(e.Graphics, control.ClientRectangle,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset);
This is kind of rigging it but I've always just used a label for each side border. You'll have to set the autosize property to false and dock one to each side (left, right, top, bottom). Then just set the width/height/background color to do what you want.
You could easily make this a user control and just expose some custom public properties to set the width/height for you and the background color of all the labels to change the color.