I'm trying to draw a text on a panel(The panel has a background picture).
It works brilliant,but when I minimize and then maximize the application the text is gone.
My code:
using (Graphics gfx = Panel1.CreateGraphics())
{
gfx.DrawString("a", new Font("Tahoma", 5), Brushes.White, new PointF(1, 1));
}
How do I keep it static so it doesn't get lost?
Inherit from Panel, add a property that represents the text you need to write, and override the OnPaintMethod():
public class MyPanel : Panel
{
public string TextToRender
{
get;
set;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawString(this.TextToRender, new Font("Tahoma", 5), Brushes.White, new PointF(1, 1));
}
}
This way, each Panel will know what it needs to render, and will know how to paint itself.
Just add a handler for the Paint event:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawString("a", new Font("Tahoma", 5), Brushes.White, new PointF(1, 1));
}
If you don't use a Paint event, you are just drawing on the screen where the control happens to be. The control is not aware of this, so it has no idea that you intended for the text to stay there...
If you put the value that you want drawn on the panel in it's Tag property, you can use the same paint event handler for all the panels.
Also, you need to dispose of the Font object properly, or you will be having a lot of them waiting to be finalized before they return their resources to the system.
private void panel1_Paint(object sender, PaintEventArgs e) {
Control c = sender as Control;
using (Font f = new Font("Tahoma", 5)) {
e.Graphics.DrawString(c.Tag.ToString(), f, Brushes.White, new PointF(1, 1));
}
}
When you draw something, it only remains until the next time the form is refreshed.
When the form is refreshed, the Paint event is called. So if you want to ensure your text doesn't disappear, you need to include the code that draws it in the Paint event.
You can trigger a repaint using Control.Invalidate, but you otherwise cannot predict when they will happen.
Related
I have a panel's Paint event which draws a rectangle around the panel to highlight it.
Paint event-
private void deal_details_panel_Paint(object sender, PaintEventArgs e)
{
int thickness = 2;//it's up to you
int halfThickness = thickness / 2;
using (Pen p = new Pen(Color.GreenYellow, thickness))
{
e.Graphics.DrawRectangle(p, new Rectangle(halfThickness,
halfThickness,
deal_details_panel.ClientSize.Width - thickness,
deal_details_panel.ClientSize.Height - thickness));
}
}
Initially i dont want this rectangle to be shown, I have a button on which if i hover, only then i want to show this rectangle and on MouseLeave event i want to make it invisible again.
Button_Hover-
private void add_MouseHover(object sender, EventArgs e)
{
deal_details_panel.Invalidate();
}
I saw some answers which were to use Invalidate(), but, Invalidate() is not working for me.
Use Refresh() instead of Invalidate().
The Invalidate() method does not force repaint, it simply marks the control as "need repainting", leaving it up to the OS to repaint when needed.
The Refresh() method will force repainting immediately.
For more information, read Whats the difference between Control.Invalidate, Control.Update and Control.Refresh? blog post, where it clearly states:
Control.Invalidate( ) / Control.Invalidate(bool) / Control.Invalidate(Rectangle) / Control.Invalidate(Rectangle, bool) / Control.Invalidate(Region) / Control.Invalidate(Region, bool)
....
The important thing to note here is that these functions only “invalidate” or “dirty” the client area by adding it to the current update region of the window of the control. This invalidated region, along with all other areas in the update region, is marked for painting when the next WM_PAINT message is received. As a result you may not see your control refreshing (and showing the invalidation) immediately (or synchronously).
Update
In order for the panel to show the YellowGreen border only when the mouse is hovering over the button, you need to change your code a little bit.
First, add a bool field to your form, to indicate whether to draw the border or not, initialized to false:
private bool _drawBorder = false;
Then, change the paint method to take that value into consideration:
private void deal_details_panel_Paint(object sender, PaintEventArgs e)
{
if(_drawBorder)
{
int thickness = 2;//it's up to you
int halfThickness = thickness / 2;
using (Pen p = new Pen(Color.GreenYellow, thickness))
{
e.Graphics.DrawRectangle(p, new Rectangle(halfThickness,
halfThickness,
deal_details_panel.ClientSize.Width - thickness,
deal_details_panel.ClientSize.Height - thickness));
}
}
}
Finally, don't use the mouse_Hover event on the button, use MouseEnter and MouseLeave instead.
This is for two reasons - 1: Mouse_Hover have a built in delay and 2: using Mouse_Hover you don't get an indication when the mouse stops hovering over the button.
In the MouseEnter and MouseLeave event handlers, all you need to do is update the value of _drawBorder and call deal_details_panel.Refresh():
private void add_MouseEnter(object sender, EventArgs e)
{
_drawBorder = true;
deal_details_panel.Refresh();
}
private void add_MouseLeave(object sender, EventArgs e)
{
_drawBorder = false;
deal_details_panel.Refresh();
}
Zohar pointed correct solution but I would add a suggestion to change:
using (Pen p = new Pen(Color.GreenYellow, thickness))
to:
using (Pen p = new Pen(Color.Transparent, thickness))
This will set correct state from the start so no need to repaint.
My form has a background image. I have some pictureBoxes i am using as buttons. I am attempting to have a MouseEnter/MouseLeave event to display label or pictureBox.
I have been trying various approaches. I am getting similar results- The label or picturebox appears fine, but on MouseLeave, label1.Visible = false; causes a very temporary blank box over the background image of the form. While it is fully functional, it just seems like a very slight lag, but makes the program look bad.
I experimented with the DrawString method. This seems like it could be a good option, but i cannot figure out how to remove the object on a MouseLeave event.
Is this possible? If not, is there a better option to accomplish what i am trying to accomplish?
Here is how I am drawing my string (in buttonClick event for testing):
Graphics g = this.CreateGraphics();
string letter = "Yo Dawg!";
g.DrawString(letter, new Font(FontFamily.GenericSansSerif, 20, FontStyle.Regular),
new SolidBrush(Color.Black), 100, 100);
You would draw in the paint event, in MouseLeave set a flag, cause a paint with Invalidate() then within paint if the flag is not set don't draw anything.
public partial class TheForm : Form
{
private Font _font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Regular);
private bool _hovering = false;
public TheForm() {
InitializeComponent();
picBox.Paint += new PaintEventHandler(picBox_Paint);
picBox.MouseEnter += (sender, e) => UpdateText(true);
picBox.MouseLeave += (sender, e) => UpdateText(false);
}
private void picBox_Paint(object sender, PaintEventArgs e) {
if (_hovering)
e.Graphics.DrawString("Yo Dawg!", _font, Brushes.Black, 100, 100);
}
private void UpdateText(bool show) {
_hovering = show;
picBox.Invalidate();
}
}
I have a project in c# with many forms. Imagine this scenario:
Every form has a (let's say green) rectangle in it. when I call a function, i need every rectangle to change it's color (to red). I have created this function in order to draw a rectangle:
private void drawBorder(System.Drawing.Color c)
{
System.Drawing.Graphics g = this.CreateGraphics();
System.Drawing.Brush br = new System.Drawing.SolidBrush(c);
System.Drawing.Pen p = new System.Drawing.Pen(br, 4);
System.Drawing.Rectangle r = new Rectangle(0, 0, 50, 50);
g.DrawRectangle(p, r);
g.Dispose();
br.Dispose();
p.Dispose();
}
I have realized that I cannot call this function on load (is this true?) This just was my first question needing an answer
So, if i had just one form, I would first call this function on paint:
private void fLogin_Paint(object sender, PaintEventArgs e)
{
drawBorder(System.Drawing.Color.Red);
}
and then call it again whenever I need (eg on a button click) with a different color argument. This works on my single form. But I need this in every form.
So my second question is do i need to create a _paint event in every form for my solution to work?
I have also thought that i could create a new type of Form, inheriting the default form, add the _paint event in that form and define all my forms as this type. Which is the best approach?
My last question is: When i call my function more than once, what I really do is drawing a new rectangle on top of the previous one. Should i use
this.Invalidate();
before changing the color of my border?
A base form is a solid approach - add to that a static method to trigger a static event when you want to change the colour.
Make your base form handle that event and force a repaint and you should have a single point from where all rectangles can changed.
Well I would suggest you to create a BaseForm with protected override OnPaint().
And whenever you create a new Form, inherit the BaseForm instead of the default Form
Here is how you do BaseForm
public class BaseForm : Form
{
protected void DrawBorder(Color c)
{
var g = CreateGraphics();
var br = new SolidBrush(c);
var p = new Pen(br, 4);
var r = new Rectangle(0, 0, 50, 50);
g.DrawRectangle(p, r);
p.Dispose();
br.Dispose();
g.Dispose();
}
protected override void OnPaint(PaintEventArgs e)
{
DrawBorder(Color.Red);
base.OnPaint(e);
}
}
How to apply it on new created form
public partial class Form1 : BaseForm // Use the BaseForm instead of default 'Form'
{
public Form1()
{
InitializeComponent();
}
}
Question:
How do you properly draw on a winform from a method other than the OnPaint() method?
Additional Information:
The code I have now draws some background lines for a TicTacToe game in the OnPaint() method. Then I use the Mouse_Click event and am running this code which apparently is not proper:
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
Graphics g = this.CreateGraphics();
g.DrawEllipse(this.penRed, this.Rectangle);
For reasons I do not understand, it does draw the circle, but when minimizing or moving the form off screen it erases the circles but not the lines from the OnPaint() method.
You are doing a lot of "view" but no "model".
When you want to create a shape, when the mouse button goes down/up, create some DATA representing the shape.
Your data structures represent the persistent information (it is the data that allows you to save and load this information between sessions).
All your paint function needs to do is look at the DATA structures and paint it. This will therefore persist between sizing/hiding/showing.
The problem is that Windows windows (that includes WinForms) have no graphical memory of their own unless their creator provides such memory and it is only a matter of time before that particular window wilk get overwritten or hidden and eventually need to be repainted. You're painting to the screen ( you might say ) and others can do the same. The only convention you can rely on is that the OnPaint will get called when needed. Basically it's alright to use your philosophy and draw whenever you need to (not on some misterious and unpredictable schedule). For that check out my solution.
You should use a "backbuffer bitmap" like:
private Bitmap bb;
protected override void OnResize(EventArgs e) {
this.bb = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
this.InitBackBuffer();
}
private void InitBackBuffer() {
using (var g = Graphics.FromImage(this.bb)) {
// do any of the "non dissapearing line" drawing here
}
}
private void TicTacToe_MouseClick(object sender, MouseEventArgs e)
using (Graphics g = Graphics.FromImage(this.bb))
g.DrawEllipse(this.penRed, this.Rectangle);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.DrawImageUnscaled(this.bb);
}
Try that. That should do it :)
What you are doing is drawing on the form "asynchronously" (from the OnPaint method). You see, the OnPaint method is what Windows Forms relies on to draw your entire form. When something happens to your From, it is invalidated and OnPaint is called again. If something isn't drawn in that method, then it will not be there after that happens.
If you want a button to trigger something to appear permanently then what you need to do is Add that object to a collection somewhere, or set a variable related to it. Then call Refresh() which calls Invalidate() and Update() Then, during OnPaint, draw that object (ellipis).
If you want it to still be there after something happens to your form, such as minimize, you have to draw it during OnPaint.
Here's my suggestion:
public partial class Form1 : Form
{
Rectangle r = Rectangle.Empty;
Pen redPen = new Pen(Color.Red);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
r = new Rectangle(50, 50, 100, 100);
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (r != Rectangle.Empty)
{
e.Graphics.DrawRectangle(redPen, r);
}
}
}
I want to display graphic file in PictureBox I have:
private void btnLoad_Click(object sender, EventArgs e)
{
if (dgOpenFile.ShowDialog() == DialogResult.OK)
{
Bitmap img = new Bitmap(dgOpenFile.FileName);
picture.Width = img.Height;
picture.Height = img.Height;
g.DrawImage(img, 0f, 0f);
}
}
That's g
private void Form1_Load(object sender, EventArgs e)
{
g = picture.CreateGraphics();
}
But when I move my Form outside the window my picture disappears. How can I prevent that?
You should do any custom drawing in the OnPaint event of the control to make it persistent. This causes your drawing to be redrawn every time the control is painted.
However, in this case it would be easier to use the picture box as it was designed:
picture.Image = img;
Windows uses a Paint-on-Request principle.
So when it sends a WM_PAINT message to your Control, it's OnPaint() is called. You should be ready to draw the image (again) in an overridden OnPaint() or in a Paint event handler.
But a Picturebox will do all this for you.