when i press tab the panel clears - c#

Using DrawImageUnscaled my program draws a parobola or a line ...
If I press tab after the program draws, the panel in whith the image clears..
For my ox and oy axes i use ? and they disappear too...
System.Drawing.Pen linepen;
linepen = new System.Drawing.Pen(System.Drawing.Color.Green);
System.Drawing.Graphics g = drawingboard.CreateGraphics();
g.DrawLine(linepen, 0, drawingboard.Height / 2, drawingboard.Width, drawingboard.Height / 2);
g.DrawLine(linepen, drawingboard.Width / 2, 0, drawingboard.Width / 2, drawingboard.Height);
timer1.Enabled = false;
How can I fix this ?

Typically in DOT.NET you don't just paint what you want the user to see in a random place. Instead each panel (and control) has an OnPaint function which is called when the control displays itself. You want to modify this function to draw your new lines.
You will notice as you work with .NET that this function will get called multiple times when certain events happen in the application -- for example re-size. This allows the programmer to change what is displayed when the window changes size.
Add your code above to the OnPaint function of the control and it should work much better.
I'm not sure what you mean by ox and oy -- you don't seem to have any code with them

Related

How should I buffer drawn rectangles to improve performance (C#/.NET/WinForms/GDI+)

What I'm doing
I am working on a C#/.NET 4.7.2/WinForms app that draws a significant number of filled rectangles on a form using Graphics.FillRectangle.
Currently, the rectangles are drawn in the form's Paint event. After all the rectangles are drawn, a crosshair is drawn based on mouse position.
Whenever the mouse moves, Invalidate is called on the form to force a repaint so that the crosshair appears in its new position.
The problem
This is inefficient because the rectangles don't change, only the crosshair position, yet the rectangles are being redrawn every time. The CPU usage during mouse move is significant.
What next
I believe that the solution to this problem is to draw the rectangles to a buffer first (outside of the Paint event). Then, the Paint event only needs to render the buffer plus draw a crosshair on top.
Since I am new to GDI+ and manual buffering, I am wary of going down the wrong track. Google searches reveal plenty of articles on manual buffering, but each article seems to take a different approach which adds to my confusion.
I would be grateful for suggested approaches that favour simplicity and efficiency. If there is an idiomatic .NET way of doing this — the way it's meant to be done — I'd love to know.
Here's a quick and easy solution that doesn't require any buffering. To replicate this, start with a fresh Windows Forms project. I only draw two rectangles, but you can have as many as you want.
If you create a new WinForms project with these two member variables and these two handlers, you will get a working sample.
First, a couple of member variables for your form:
private bool _started = false;
private Point _lastPoint;
The started flag will turn to true after the first mouse move. The _lastPoint field will track the point at which the last cross-hairs was drawn (that's mostly why _started exists).
The Paint handler will draw the cross hairs every time it's called (you'll see why this is ok with the MouseMove handler):
private void Form1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
var clientRectangle = this.ClientRectangle;
//draw a couple of rectangles
var firstRectangle = clientRectangle;
firstRectangle.Inflate(-20, -40);
graphics.FillRectangle(Brushes.Aqua, firstRectangle);
var secondRectangle = clientRectangle;
secondRectangle.Inflate(-100, -4);
graphics.FillRectangle(Brushes.Red, secondRectangle);
//draw Cross-Hairs
if (_started)
{
//horizontal
graphics.DrawLine(Pens.LightGray, new Point(clientRectangle.X, _lastPoint.Y),
new Point(ClientRectangle.Width + clientRectangle.X, _lastPoint.Y));
//vertical
graphics.DrawLine(Pens.LightGray, new Point(_lastPoint.X, clientRectangle.Y),
new Point(_lastPoint.X, ClientRectangle.Height + clientRectangle.Y));
}
}
Now comes the MouseMove handler. It's where the magic happens.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var clientRectangle = this.ClientRectangle;
var position = e.Location;
if (clientRectangle.Contains(position))
{
Rectangle horizontalInvalidationRect;
Rectangle verticalInvalidationRect;
if (_started)
{
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X - 1, clientRectangle.X),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
_started = true;
_lastPoint = position;
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X, clientRectangle.X - 1),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
}
If the cursor is within the form, I do a bunch of work. First I declare two rectangles that I will be using for invalidate. The horizontal one will be a rectangle that fills the width of the client rectangle, but is only 3 pixels high, centered on the Y coordinate of the area that I want to invalidate. The vertical one is as high as the client rectangle, but only 3 pixels wide. It's centered on the X coordinate of the area that I want to invalidate.
When the Paint handler runs, it virtually paints the entire client area, but only the pixels in the total invalidated area actually get drawn on the screen. Anything outside the invalidate area is left alone.
So, when the mouse moves, I create two rectangles (one vertical, one horizontal) that surround where the last set of cross-hairs were (so that when the pixels in those rectangles are drawn (including the background), the old cross-hairs are effectively erased) and then I create two new rectangles surrounding where the current cross-hairs should go (causing the background and the new cross-hairs to be drawn).
You are going to want to learn about invalidation rectangles if you have a complicated drawing app. For example, when the form is resized, what you want to do is invalidate only the newly unveiled rectangle(s), so that the whole drawing doesn't need to be rendered.
This works, but picking a color (or a brush) for the cross-hairs so that they always show can be difficult. Using my other suggestion (that you draw the lines twice (one to erase, one to draw) using an INVERT (i.e. XOR) brush is faster, and it always shows.

base.OnPaint order changes drawing location

I have a strange problem. I made my own user control deriving from UserControl. I override OnPaint. Now I draw something in OnPaint. Let's say at position 0, 0.
If I call base.OnPaint after my custom drawing everything is fine. But if I call base.OnPaint before the stuff I'm drawing, it seems to ignore the containing control and the location is relative to the form instead of relative to the client area of the parent control. So when I draw at position (0, 0) it will effectively be drawn at negative x and y and I will only see a part of it. The base.OnPaint is UserControl.OnPaint. So I don't call my code there.
Here is an example:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var rect = new System.Drawing.Rectangle(this.ClientSize.Width - 16,
this.ClientSize.Height - 16, 16, 16);
e.Graphics.FillRectangle(new System.Drawing.SolidBrush(System.Drawing.Color.Red), rect);
//base.OnPaint(e);
}
In this case the red rectangle is displayed somewhere inside the client area but not at the lower right corner. If I uncomment the last line and comment the first line the red rectangle is displayed at the lower right corner as expected.
I don't get it. I did this many times and it always worked. So I tried to find any differences. The only I found is that I don't add my control in the designer but add it programmatically to another control with theContainingControl.Controls.Add(myMessedUpControl);.
This also happens for every parent-child-level I add. So if I create another control (another class) and also override OnPaint the behavior is the same if I add it to another user control.
Does anyone had this behavior before? How can I fix this? The problem is that I want to call base.OnPaint first and also everyone suggest this. But as I said I can't without messing the coordinates up.
One note: The coordinates are really 0, 0 in the debugger at the draw calls like DrawLine, DrawImage oder DrawString. But the result is displayed at negative coordinates (relative to the client area). It looks like the client coordinates are interpreted as client coordinates of the form. But I don't know why.
Found the problem
In my project there is a graphical overlay class which connects Paint event handlers to all controls in my form (the whole hierarchy). In this handler a transformation is performed. This graphical overlay kept me sleepless so many times. I guess I will remove it.
The Graphics object has a lot of mutable state. The order of operations matters if you mess with this mutable state - for example, you can use the Transform matrix to change the offset of everything rendered on the surface.
It sounds like your ascendant changes one of those during its own OnPaint handler without resetting it back. Try doing a e.Graphics.ResetTransform(); before you start your own painting. Make sure all the other state is also the way you want it (clip, DPI, ...).

Unity3D draw GUI.Button over GUI.Window

I want to draw a GUI Button on top of a GUI window that I have in my game, but no matter what I try the button always appears behind.
This is my Code:
GUI.depth = -30;
Rect navBackButton = new Rect(10, 10, 10, 10);
// DRAW NAVIGATION BUTTONS
if (GUI.Button(navBackButton, navBackButtonTexture))
{
// DO LOGIC HERE
}
GUI.depth = 10;
topScrollRect = new Rect(0, 0, Screen.width, topScrollHeight);
topScrollListSize = new Vector2(topScrollRect.width - 2*listMargin.x, topScrollRect.height - 2*listMargin.y);
GUI.skin.window = topStyle;
GUI.Window(0, topScrollRect, (GUI.WindowFunction)DoTopScrollWindow, "");
I have tried to draw the buttons before drawing the window, and the other way round, but both have the same result.
In this forum post (http://forum.unity3d.com/threads/setting-depth-of-a-window.12554/), it says that any GUI control with a depth of less than 1 will appear in front of GUI Windows, but I have set my depth to -30 and still the button appears behind the Window.
Any suggestions would be greatly appreciated!
The way to do this is to draw the button in DoTopScrollWindow().
You shouldn't try to place the buttons "on top" of the window, you should put them "in" the window, as the window is your navigation button's parent, conceptually. Additionally, drawing them in the window means that you can take advantage of relative positioning and window resizing without having to write a lot of extra code.
According to the docs, the parameter func in GUI.Window() is a function designed to display content inside the window.
In this case you want your buttons "inside" that window, so draw them in the function you pass in as func.

C# Screenshot of Control is white

My question is related to this one: How to get a screen capture of a .Net WinForms control programmatically?
I want to take a screenshot of a System.Windows.Forms.Control in C#. I'm using the DrawToBitmap method suggested in the question linked above and that works most of the time. However there are a few problems.
Problem 1:
I have two tabpages, let's call them A and B. The Control I want to take a screenshot of is in tabpage B. I want to take the screenshot when a button in tabpage A is pressed. This works most of the time, except when tabpage B hasn't been accessed yet: then the screenshot is just white. If I first access tabpage B, then go back to tabpage A and click the button to take the screenshot then it works fine. I'm guessing this is because of some loading or building of the control in the tab that hasn't been done yet, but I'm not sure what exactly (or it could be something else entirely). I've been trying to force that loading or building using ResumeLayout, PerformLayout, Show, Update, Invalidate but that doesn't work.
EDIT: Managed to solve this by using DrawToBitmap on the containing tabpage control instead of the Control itself and doing a Show on that tabpage.
Problem 2:
When I take a screenshot of a certain custom Control (a subclass of UserControl) there is a small rectangular white area in the screenshot (where there shouldn't be one obviously). The rectangular area isn't on one particular part of the control like a button or textbox so I'm not sure what's causing this. On other custom Controls (also subclasses of UserControl) it works fine, so that couldn't be the problem itself.
EDIT: Solved it, there was an empty control there that was being drawn on top of it. Setting Visible to false for that control solved it.
You can do with this code, the rest of the work and finding the correct coordinates is for the reader to do the homework.
int screenWidth = Screen.GetBounds(new Point(0, 0)).Width;
int screenHeight = Screen.GetBounds(new Point(0, 0)).Height;
Bitmap bmpScreenShot = new Bitmap(screenWidth, screenHeight);
Graphics gfx = Graphics.FromImage((Image)bmpScreenShot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));
bmpScreenShot.Save("test.jpg", ImageFormat.Jpeg);
For problem 1: If "If I first access tabpage B, then go back to tabpage A and click the button to take the screenshot then it works fine" then add in formloadevent imitating of that actions:
tabControl.SelectedIndex = IndexOfTabB;
tabControl.SelectedIndex = IndexOfTabA;
That is trick but it will work.
For problem 2: Can you compare sizes of your control and screenshot of It and give results to us? If Control.Width is not equal wirdth of bitmap screen then It is a real problem.

Windows mobile autoresize with panels

I've got an app which will run on two different devices - one with a screen size of 240x320, the other 480x640.
For all forms bar one the VS generated code is fine:
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
For one form i'm capturing a signature. I'm doing this by a panel with a graphics handler; capturing mouse down and move events; this generates a list of vector points which I can draw lines with.
On the smaller res screen this is fine. On the higher res, I can't display my lines.. and I think this is because the panel is beyond the windows form size.
The form is created with a size of 240 x 268; a standard size I think - i've not manually set it, VS does this for me.
In order to get the panel in the right spot on the high res device, the co-ordinates are 3, 290; ie, 290 is past 268. Also the width of the panel is 448 which is somewhat larger than 240.
I'm using .net 2.0 (can't use later). I think I need to resize the form to make it larger but I do want to keep the existing re-sizing for the other controls on the form.
I'm not sure how to do this.
Make the form dock to fill, then use the Anchor properties to ensure controls inside the form resize as expected.
If you want the option of customizing how an individual control resizes, then DONT set the anchor properties on it, and instead handle the Resize event and perform custom resizing/repositioning within code there.
eg
private void form_Resize(object sender, EventArgs e)
{
// Center the control without changing width. Other controls are anchored.
this.control.Left = (this.Width - this.control.Width) / 2;
}
I'm writing this answer for the benefit of those who may have a similar problem in the future. PaulG pointed me in the right direction but I found the root cause to be something else.
The PDA project i've got uses "FormFactor WindowsMobile 6 Classic" which has a default size of 240 x 268.
Changing this to "Windows Mobile 6 Professional VGA" created a much larger form size.
This allowed me to get things positioned correctly for the larger size; then AutoScaleMode to DPI; and manually resizing the panel smaller made it all work.
IE, going from larger to smaller was easy; I didn't get smaller to larger working.

Categories

Resources