Moving Label inside parent's MouseMove-events causes trailing redraw.
I am (mis)using System.Windows.Forms.Label objects to realize
A more flexible tooltip,
Displaying a cross hair "cursor" extending to the borders of a parent control.
To that end I create the labels as members of the parent control (btw. it's a custom graphics control with mouse wheel zoom, panning etc.), add them to the parent's Controls-list, and move them inside the MouseMove-event of the parent. In order to ensure crisp movement behavior, I even call Label.Refresh when assigning the Location property of the Labels.
void GraphViewMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (lastLocation != e.Location)
{
HasMoved = true;
if (IsPanning && e.Button == MouseButtons.Left)
{
if (isSelecting) PerformSelection(e.Location);
else PerformMove(e.Location,lastLocation);
}
lastLocation = e.Location;
if (Width > 0 && Height > 0)
{
PointD pos = CursorPosition;
// nothing interesting, basically like what is done with the crossHair's below
coordinateToolTip.Locate(lastLocation, pos.X, pos.Y);
crossHairX.Location = new Point(lastLocation.X, 0);
crossHairY.Location = new Point(0, lastLocation.Y);
crossHairX.Refresh();
crossHairY.Refresh();
}
}
}
Everything works almost fine (the labels move parallel to the mouse cursor) except that when I move the mouse a little faster, several copies of the Label(s) remain in the client area of the parent for some time like a visual echo.
That is, whereas the redraw of the labels at their new location appears to occur immediately, it seems to take some fraction of a second until the background of the parent (some complicated graphics, the complete redraw of which takes several seconds, but is definitely not redrawn during said simple mouse move) is restored to what has been behind the moving Label(s) originally. This is of course functionally irrelevant, but it simply looks ugly because it raises the impression that the code is inefficient.
Is there a way to enforce (faster) redraw of the cached background graphics? What determines the delay before the screen content behind a control is restored?
Edit: as to the suggestion of setting DoubleBuffered=true: according to MSDN this should already be true by default. Indeed it doesn't make a difference if I set it true for the Label. On the other hand, setting it false introduces additional flickering as expected. In either case the restoration of the background remains delayed, like described above.
#Hans Passant: Thanks for telling us "When you move a control, its parent's OnPaintBackground() method needs to run to over-draw the pixels that were left behind by the control in its previous position."
For me the cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED did not work.
I am creating movable, Resizable Panel and when I moved it fast from left to right it was not displayed right "the half of the panel was often gone". I did this and now it works exactly like when I move a Windows Window.
This is in the MyPanel Class which I made and Inherits from Panel
protected override void OnMove(EventArgs e)
{
this.Parent.Invalidate();
base.OnMove(e);
}
So when I move the Movable panel with the mouse it wil Invalidate the "Parrent" in my case the Form and now it moves fine.
Related
I have created a custom panel that draws a border (that comprises two rectangles). The problem is that if I resize the window, there remain residues... much like the animation of Windows XP's Solitaire. See the screenshot below. I resized the window to the right.
What is wrong, and how can I fix this?
protected override void OnPaint(PaintEventArgs e)
{
var outer = ClientRectangle;
outer.Width -= 1;
outer.Height -= 1;
var inner = outer;
inner.X++;
inner.Y++;
inner.Width -= 2;
inner.Height -= 2;
e.Graphics.DrawRectangle(DarkPen, outer);
e.Graphics.DrawRectangle(WhitePen, inner);
base.OnPaint(e);
}
UPDATE
At first I thought this was because I did not delete the previously drawn rectangles. So I called FillRectangle to delete the inside.
var contentArea = inner;
contentArea.X++;
contentArea.Y++;
contentArea.Width -= 2;
contentArea.Height -= 2;
e.Graphics.FillRectangle(SystemBrushes.Control, contentArea);
But it did not work either. It seems that the control paints only the newly expanded portion, not the entire area.
The Form class was optimized to be a container control. Containers normally just have a background color to paint. So, as you noticed, indeed does "only paint newly expanded portion". Or more accurately, it does not overpaint the previous pixels, easy to recognize from the smearing effect that produces. It is a somewhat feeble attempt at making Winforms designs a bit more lively when you resize the window, painting gets to be a bottleneck when a form has a lot of controls and that is pretty noticeable.
You'll have to set its ResizeRedraw property to true so it redraws its entire surface when necessary. Setting the DoubleBuffered property to true might not hurt either when the drawing code gets elaborate and you start noticing flicker.
So, roughly:
public Form1() {
InitializeComponent();
this.ResizeRedraw = true;
this.DoubleBuffered = true;
}
Optimizing OnPaint() is generally not a strategy that pays off, Windows itself already clips what you draw against the dirty region.
When scrolling an image in a browser using the scrollbars - the image scrolls quickly and smoothly. On the other hand, making a tight loop with Graphics.DrawImage, incrementing the location's X-coordinate by 1 each iteration - returns a slow motion. (It's also somewhat jerky even after making the Control DoubleBuffered.)
How can I get fast rendering like a browser's?
EDIT
void DoNow()
{
Rectangle rec1 = new Rectangle(Point.Empty, panel1.BackgroundImage.Size);
Rectangle rec2 = new Rectangle(Point.Empty, panel1.BackgroundImage.Size);
using (Graphics g = Graphics.FromImage(panel1.BackgroundImage))
{
for (int i = 0; i < 100; i++)
{
rec2.Location = new Point(rec2.Location.X + 1, rec2.Location.Y);
g.DrawImage(image, rec1, rec2, GraphicsUnit.Pixel);
panel1.Refresh();
}
}
}
It seems from the comments like the answer to my question is that browsers use hardware acceleration which is unavailable to Winforms. (Please feel free to correct me if I'm wrong.)
The easiest solution is to use a Panel object with the scroll bars set to Auto. Place a picturebox as a child object with the size mode set to Auto. The picture box will expand to the size of the image that you assign to it. Since the picture box will expand to be larger than the panel, the panel's scroll bars will appear. When you scroll the image using the panel, it will be smooth.
If you still want to manually provide the drawing yourself, you are getting the "flicker" because the Invalidate method automatically performs background erasing which is not performed during the OnPaint event. You need to subclass the parent control that you are drawing on and override the OnPaintBackground event. Like below:
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// base.OnPaintBackground(pevent);
}
Also, remember to perform ALL drawing during the OnPaint event and use the e.Graphics object.
I've a simple user control that actually is just a panel, when I click in that panel, there will be a child user control added. that child is just another user control where I set width = 150px & height = 100px and the background color to transparent. also it has a textbox in the center that is 100 x 100 px.
this base structure will be a Node Based Interface in the future, where each box will have connection anchors and logic btn's or something like that in it.
my problem is that if i click a few times in the panel and the added box overlapy another, the transparency wont take effect.
here's a screenshot
how can i fix that issue ? is there a allowTransparency or something like that ?
there is also an issue with the order of the drawing, the new added blocks are always behind the other one's.
if you wish to see the code for this, let me know , but i don't think that there is anything relevant for this.
also, if you know a better way to implement a node graph, please feel free to tell me.
EDIT
following code was the first thing I've tried before I've even thought of posting a question in StackOverFlow.
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
MakeTransparentControls(this);
so please don't take this as a duplicate question or posting that code as answer
This is in general a very fundamental restriction in Windows. A window overlaps another window and obstructs whatever is behind it. Everybody's favorite answer, use ControlStyles.SupportsTransparentBackColor, is no workaround, it is already turned on for UserControl so setting it again makes no difference.
The style flag simulates window transparency. It does so by altering the way the window paints itself. It first asks the Parent to draw itself inside the window to provide the background, then draws itself on top of it. So by setting the BackColor property to Color.Transparent, you can expect to see the Parent window's pixels as the background.
And you do. The Parent of your user control is the form. It dutifully draws itself first and that produces the gray edges on the left and the right. What you hoped for was to also see the pixels of the other user controls. But that doesn't work, it only asks the Parent to draw, not any other control that happens to be overlapped as well. That certainly isn't impossible, it is just ugly and slow. This KB article shows the approach. Rather emphasizing the "ugly" moniker.
Clearly you can improve your UserControl, there's on the face of it no point in having these transparent edges at the left and right of the Panel. So set the panel's Location property to (0, 0) and the Dock property to Fill. Now every user control is nothing but a "node" and you don't need nor want to see pieces of any overlapped other "node". Drawing any lines between the nodes is going to require implementing the Paint event of the form.
If you truly need this kind of transparency then you are going to have to do this differently. You have to give up on the idea of using a window for each node. One way to do so is to just paint them. If you do that in the right order then you'll have no problem simulating true transparency, created by layering paint and just not draw where you need to see the "background". It will also be a lot faster, controls are expensive. You however will to give up on the convenience of using controls, you'll have to write code instead. Things like mouse hit testing become more complicated. And the text box will certainly be a hangup if it isn't actually supposed to be a Label (surely it does?) Or use a GUI class library that has given up on using a window as a control. Like WPF. And don't forget that there are plenty of libraries that already do this for you, connected nodes are a very common user interface paradigm. Visio for example.
try this in the constructor of your UserControl:
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
You also can try this :
public partial class UCTransparent : UserControl
{
public UCTransparent()
{
InitializeComponent();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
}
}
In one of my projects I had to set transparent background to almost every child control on the Form, so I created this method:
private void MakeTransparentControls(Control parent)
{
if (parent.Controls != null && parent.Controls.Count > 0)
{
foreach (Control control in parent.Controls)
{
if ((control is PictureBox) || (control is Label) || (control is GroupBox) || (control is CheckBox))
control.BackColor = Color.Transparent;
if (control.Controls != null && control.Controls.Count > 0)
MakeTransparentControls(control);
}
}
}
And in the form's constructor I added those lines:
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
MakeTransparentControls(this);
You can try something like this in your own context.
I'm making a program where I have a lot of panels and panels in panels.
I have a few custom drawn controls in these panels.
The resize function of 1 panel contains code to adjust the size and position of all controls in that panel.
Now as soon as I resize the program, the resize of this panel gets actived. This results in a lot of flickering of the components in this panel.
All user drawn controls are double buffered.
Can some one help me solve this problem?
To get rid of the flicker while resizing the win form, suspend the layout while resizing. Override the forms resizebegin/resizeend methods as below.
protected override void OnResizeBegin(EventArgs e) {
SuspendLayout();
base.OnResizeBegin(e);
}
protected override void OnResizeEnd(EventArgs e) {
ResumeLayout();
base.OnResizeEnd(e);
}
This will leave the controls intact (as they where before resizing) and force a redraw when the resize operation is completed.
Looking at the project you posted, the flickering is really bad when you have the first tab selected, with the gradient-filled group boxes. With the second or third tab showing, there's hardly any flicker, if at all.
So clearly the problem has something to do with the controls you're showing on that tab page. A quick glance at the code for the custom gradient-filled group box class gives away the more specific cause. You're doing a lot of really expensive processing each time you draw one of the groupbox controls. Because each groupbox control has to repaint itself each time the form is resized, that code is getting executed an unbelievable number of times.
Plus, you have the controls' background set to "Transparent", which has to be faked in WinForms by asking the parent window to draw itself first inside the control window to produce the background pixels. The control then draws itself on top of that. This is also more work than filling the control's background with a solid color like SystemColors.Control, and it's causing you to see the form's pixels being drawn while you resize the form, before the groupboxes have a chance to paint themselves.
Here's the specific code I'm talking about from your custom gradient-filled groupbox control class:
protected override void OnPaint(PaintEventArgs e)
{
if (Visible)
{
Graphics gr = e.Graphics;
Rectangle clipRectangle = new Rectangle(new Point(0, 0), this.Size);
Size tSize = TextRenderer.MeasureText(Text, this.Font);
Rectangle r1 = new Rectangle(0, (tSize.Height / 2), Width - 2, Height - tSize.Height / 2 - 2);
Rectangle r2 = new Rectangle(0, 0, Width, Height);
Rectangle textRect = new Rectangle(6, 0, tSize.Width, tSize.Height);
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(r2);
gp.AddRectangle(r1);
gp.FillMode = FillMode.Alternate;
gr.FillRectangle(new SolidBrush(Parent.BackColor), clipRectangle);
LinearGradientBrush gradBrush;
gradBrush = new LinearGradientBrush(clipRectangle, SystemColors.GradientInactiveCaption, SystemColors.InactiveCaptionText, LinearGradientMode.BackwardDiagonal);
gr.FillPath(gradBrush, RoundedRectangle.Create(r1, 7));
Pen borderPen = new Pen(BorderColor);
gr.DrawPath(borderPen, RoundedRectangle.Create(r1, 7));
gr.FillRectangle(gradBrush, textRect);
gr.DrawRectangle(borderPen, textRect);
gr.DrawString(Text, base.Font, new SolidBrush(ForeColor), 6, 0);
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (this.BackColor == Color.Transparent)
base.OnPaintBackground(pevent);
}
And now that you've seen the code, red warning flags ought to go up. You're creating a bunch of GDI+ objects (brushes, pens, regions, etc.), but failing to Dispose any of them! Almost all of that code should be wrapped in using statements. That's just sloppy coding.
Doing all of that work costs something. When the computer is forced to devote so much time to rendering controls, other things lag behind. You see a flicker as it strains to keep up with the resize. It's no different than anything else that overloads a computer (like a computing the value of pi), it's just really easy to do so when you use as many custom drawn controls like you do here. Transparency is hard in Win32, and so is a lot of custom 3D painting. It makes the UI look and feel clunky to the user. Yet another reason that I don't understand the rush away from native controls.
You really only have three options:
Deal with the flicker. (I agree, this is not a good option.)
Use different controls, like the standard, built-in ones. Sure, they may not have a fancy gradient effect, but that's going to look broken half of the time anyway if the user has customized their Windows theme. It's also reasonably hard to read black text on a dark gray background.
Change the painting code within your custom controls to do less work. You may be able to get by with some simple "optimizations" that don't cost you any of the visual effects, but I suspect this is unlikely. It's a tradeoff between speed and eye candy. Doing nothing is always faster.
I successfully eliminate flicker when form resize using this code. Thanks.
VB.NET
Public Class Form1
Public Sub New()
Me.SetStyle(ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.SupportsTransparentBackColor, True)
End Sub
Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
Me.Update()
End Sub
End Class
C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Resize += Form1_Resize;
this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor, true);
}
private void Form1_Resize(object sender, System.EventArgs e)
{
this.Update();
}
}
So I ran into this same problem - my control with a transparent background was repainting like 34 times, and what worked for me was:
On my form that contained the control
protected override void OnResize(EventArgs e)
{
myControl.Visible = false;
base.OnResize(e);
myControl.Visible = true;
}
And the same in the control:
protected override void OnResize(EventArgs e)
{
this.Visible = false;
base.OnResize(e);
this.Visible = true;
}
This reduced the amount of repainting to 4, which effectively eliminated any flicker when the control was being resized.
Maybe a good solution for you will be to use Form.ResizeBegin and Form.ResizeEnd events.
On ResizeBegin set main panel visibility to false, on ResizeEnd set main panel visibility to true.
This way panels will not be redrawn while someone is resizing your form.
While hooking into ResizeBegin and ResizeEnd is the right idea, instead of hiding the main panel's visibility I'd instead delay any resize calculations until ResizeEnd. In this case, you don't even need to hook into ResizeBegin or Resize - all the logic goes into ResizeEnd.
I say this for two reasons. One, even though the panel is hidden, the resize operation will likely still be expensive and so the form won't feel as responsive as it should unless the resize calculations are delayed. Two, hiding the pane's contents while resizing can be jarring to the user.
I had the same problem.
It seams that this is happening because you are using rounded corners. When I set CornerRadius property to 0, the flickering was gone.
So far I have only found the following workaround. Not the nicest one, but it stops the flickering.
private void Form_ResizeBegin(object sender, EventArgs e)
{
rectangleShape.CornerRadius = 0;
}
private void Form_ResizeEnd(object sender, EventArgs e)
{
rectangleShape.CornerRadius = 15;
}
I Had A Similar Issue Like This. My Entire Form Was Resizing Slowly And The Controls Painted Them In An Ugly Manner. So This Helped Me:
//I Added This To The Designer File, You Can Still Change The WindowState In Designer View If You Want. This Helped Me Though.
this.WindowState = FormWindowState.Maximized;
And In The Resize Event, Add This Code To The Beginning
this.Refresh();
happy holidays!
i have a tablelayoutpanel (10x10). within each cell i have a picturebox which are disabled (enabled = false).
i am trapping mouse move over the table to catch mouse movement. here is the code:
private void tableLayoutPanelTest_MouseMove(object sender, MouseEventArgs e)
{
if (!placeShip)
{
c = tableLayoutPanelTest.GetControlFromPosition(homeLastPosition.Column, homeLastPosition.Row);
if (c.GetType() == typeof(PictureBox))
{
PictureBox hover = new PictureBox();
hover = (PictureBox)(c);
hover.Image = Properties.Resources.water;
}
Point p = tableLayoutPanelTest.PointToClient(Control.MousePosition);
Control picControl = tableLayoutPanelTest.GetChildAtPoint(p);
if (picControl != null)
{
TableLayoutPanelCellPosition me = tableLayoutPanelTest.GetCellPosition(picControl);
if (picControl.GetType() == typeof(PictureBox))
{
PictureBox thisLocation = new PictureBox();
thisLocation = (PictureBox)(picControl);
thisLocation.Image = Properties.Resources.scan;
homeLastPosition = me;
}
}
}
toolTipApp.SetToolTip(tableLayoutPanelTest, tableLayoutPanelTest.GetCellPosition(c).ToString());
}
when i run this the tooTipApp starts consuming upto 56% of the CPU. so there is something wrong.
also the picturebox image changing code stops working for some reason.
any help is very welcome!
thank you.
A few thoughts:
You're creating another PictureBox called hover - why? This code doesn't seem to do anything and it's almost certainly going to slow the loop down. I think you meant to just declare hover and cast it from c, but you're actually creating a new PictureBox instance and just throwing it away.
You're also never disposing of hover, as far as I can tell - so you end up allocating tons of memory and window handles. In general you should avoid creating new objects at all inside a MouseMove handler (small ones like hit tests are sometimes OK). As with the previous point - you probably didn't mean to write the new PictureBox().
You use PointToClient(Control.MousePosition) when the MouseMove event already gives you the control-specific mouse position (e.X and e.Y). This is costing you more time than it should.
Probably the most important, you're invoking SetToolTip on every MouseMove. You should only be invoking this when the tooltip has actually changed. You need to set a flag on which cell or control the tooltip was last displayed for, check for changes, then call SetToolTip.
You will get a lot of performance back if you avoid setting the tooltip text when it hasn't changed.
Other than that I want to echo the comment. This is a lot of processing for a mouse handler.
You should be trying to do an early test to see if the mouse is still over the same thing it was on the last move, and skipping most of the code in that case.