I'm trying to make a floor for a top down style game, where I used to use pictureboxes
I was told that instead of using a Picturebox, I should be using the Graphics.DrawImage(); method, which seems to slightly help but still is very laggy.
My paint function looks like this to draw the background looks like this:
How should I make the drawing less laggy? The image is 2256 by 1504
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(PeanutToTheDungeon.Properties.Resources.tutorialFloor, 0, 0);
}
There are two points that I would make here.
Firstly, DO NOT use Properties.Resources like that. Every time you use a property of that object, the data for that resource gets extracted from the assembly. That means that you are creating a new Image object every time the Paint event is raised, which is often. You should use that resource property once and once only, assign the value to a field and then use that field repeatedly. That means that you will save the time of extracting that data and creating the Image object every time.
The second point may or may not be applicable but, if you are forcing the Paint event to be raised by calling Invalidate or Refresh then you need to make sure that you are invalidating the minimum area possible. If you call Refresh or Invalidate with no argument then you are invalidating the whole for, which means that the whole form will be repainted. The repainting of pixels on screen is actually the slow part of the process, so that's the part that you need to keep to a minimum. That means that, when something changes on your game board, you need to calculate the smallest area possible that will contain all the possible changes and specify that when calling Invalidate. That will reduce the amount of repainting and speed up the process.
Note that you don't need to change your drawing code. You always DRAW everything, then the system will PAINT the part of the form that you previously invalidated.
I know how to work with object of type Graphics (at least I am able to render images) but I always do that by passing graphics object retrieved from OnPaint method.
I would like to display an image when the app is opened (ie in Form_Load method) but have no clue how to obtain the instance of Graphics object I could use?
Thanks
Using the e.Graphics object that OnPaint() supplies to you is the correct way of doing it. It will run right after the OnLoad() method. The form isn't visible yet in OnLoad.
Getting a Graphics object from Control.CreateGraphics() is supported. However, whatever you draw with this will be wiped out as soon as the form repaints itself. Which happens when the user moves another window across yours (pre-Aero) or when she minimizes and restores or otherwise resizes the window. Use CreateGraphics only ever when animating at a high rate.
If you're attempting to create a graphics object from the surface of your form, you can use this.CreateGraphics
If you are attempting to create a new Image, you can always initialize an Image and then call Graphics.CreateGraphics.FromImage(YourImage) e.g.
Bitmap b = new Bitmap(100,100);
var g = Graphics.CreateGraphics.FromImage(b);
At this point, any drawing performed to your Graphics object will be drawn onto your image.
None of the preceding answers worked for me. I found Rajnikant Rajwadi solution effective (see https://social.msdn.microsoft.com/Forums/vstudio/en-US/ce90eb80-3faf-4266-b6e3-0082191793f7/creation-of-graphics-object-in-wpf-user-control?forum=wpf)
Here is a horribly condensed call to Graphics.MeasureString(). (please code more responsibly)
SizeF sf = System.Drawing.Graphics.FromHwnd(new System.Windows.Interop.WindowInteropHelper(this).Handle).MeasureString("w", new Font(TheControl.FontFamily.ToString(), (float)TheControl.FontSize));
And how do you plan to use the Graphics object you got in the Load event?
If you want to paint something on the screen, you have to be in the Paint event, or it will be cleared on the next paint.
What you can do: load another (simple) form, with just a picture, and hide it when your main form is loaded.
Since your Load event will probably run on the UI thread. Call DoEvents to make the other form appear.
form.CreateGraphics();
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.creategraphics.aspx
http://msdn.microsoft.com/en-us/library/5y289054.aspx
I have a form in an application developed using C#. In that form I have created a graphic shape (a circle). At run-time I want my form also to be of that shape only. That is, I want to display only that graphic and not the form back ground or title bar or anything. I want to display only that graphic. But the thing is I'm not able to shape my form. I have that graphic control as a User-Control which I have added to my form.
I suspect you're trying to make a splash-screen like effect. This isn't terribly hard to do. Here's a good tutorial to get you started.
The trick essentially is to set the transparency key of the form to the color you wish to be transparent (in this case, everything except your circle. Additionally, you need to set the form to be borderless.
As an aside, you might edit your question to add some information about why you want to do this - I am curious what your goal is, in terms of user-experience.
You could also check MSDN for the Region property. You can use System.Drawing objects to draw whatever shape you want then set the forms Region property before its shown and it will take whatever shape you give it...heres a short example:
http://www.vcskicks.com/custom_shape_form_region.php
If you want a circular form you can put the following code in the form load event handler:
System.Drawing.Drawing2D.GraphicsPath myPath = new System.Drawing.Drawing2D.GraphicsPath();
//this line of code adds an ellipse to the graphics path that inscribes
//the rectangle defined by the form's width and height
myPath.AddEllipse(0,0,this.Width,this.Height);
//creates a new region from the GraphicsPath
Region myRegion = new Region(myPath);
this.Region = myRegion;
and then set the FormBorderStyle property of the form to None.
I'm making a .NET 3.5 app with a form that draws a partially transparent black background. I'm overriding OnPaintBackground to accomplish this:
protected override void OnPaintBackground( PaintEventArgs e )
{
using ( Brush brush = new SolidBrush( Color.FromArgb( 155, Color.Black ) ) )
{
e.Graphics.FillRectangle( brush, e.ClipRectangle );
}
}
It works, but occasionally the form draws over itself without clearing the screen, making the transparency darker than it should be. I've tried playing with Graphics.Flush() and Graphics.Clear(), but it either doesn't help or completely removes transparency. Any suggestions?
Edit:
Here's what it looks like, after starting the app on the left, and after the form redraws itself a few times (in response to tabbing from one control to another) on the right:
Transparency Issue http://www.quicksnapper.com/files/5085/17725729384A10347269148_m.png
Edit 2:
I was trying a few things out this morning and noticed that when the desktop behind the transparent portions change, it's not actually being redrawn. For example, if I open Task Manager and put it behind the window, you don't see it refreshing itself. This makes sense with what I've been seeing with the transparency levels. Is there a function to make Windows redraw the area behind your window?
Edit 3:
I've tried changing a few properties on the form, but they all result in the form drawing non-transparent black:
this.AllowTransparency = true;
this.DoubleBuffered = true;
this.Opacity = .99;
I'm going to try creating a separate window for the transparent portion as overslacked mentioned, but any other ideas are still welcome.
I think I would call this expected behavior, actually. What I would do is render my background to an in-memory bitmap and, in the paint event, copy that to the form (basic double-buffering).
If I'm way off base, could you post a screenshot? I don't know that I'm imagining what you're describing correctly.
EDIT:
I'm wondering about your use of OnPaintBackground... pre-.NET, if you were doing double-buffering you'd catch and ignore the WM_ERASKBKGND message (to prevent flicker), render your image to an offscreen buffer, and copy from the buffer to the screen on WM_PAINT. So, try changing from the OnPaintBackground to OnPaint.
I haven't done too much of this kind of thing in .NET, but I had a pretty good handle on it before; I just don't know if it'll translate well or not!
EDIT 2:
Marc, the more I think about what you're trying to do, the more problems appear. I was going to suggest creating a background thread dedicated to capturing the screen and rendering it darkened; however, in order to remove your own form you'd have to set the visibility to false which would create other problems....
If you're unwilling to give up, I would suggest creating two windows and "binding" them together. Create a semi-opaque window (by setting opacity) for your background window, and create a second "normal" window for the foreground. Use SetWindowRgn on the foreground window to cut away the background and position them on top of each other.
Good luck!
Is Graphics.CompositingMode set to CompositingMode.SourceCopy? That should cause painting the background twice to be equivalent to painting it once, since it will replace the existing alpha/color data instead of compositing over it.
I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.