Set DoubleBuffered on child control
-
Donnerstag, 12. April 2012 11:29
Hello,
I would like to try setting the doublebuffered property of a child control of a form (named Canv). However, it tells me it is protected and therefore I cannot change it, unless I override it.
I have had experiences with overriding properties in the past, but only with entire classes. Is it possible to do the same with this particular instance of the Panel, instead of creating a whole new custom class and using that instead?
What I am ultimately trying to achieve is to draw ~100 controls onto the panel without having them flicker singularly into view. If there's any better way you know of, I'm happy to hear it! Just keep it in VB.NET please :P
Thanks, Silvisoft.
Just call me Silvi or LS... My site: here
Alle Antworten
-
Donnerstag, 12. April 2012 12:14
Bracket the code that creates and displays the controls with a .SuspendLayout and .ResumeLayout.
See (especially the Note):
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout.aspx -
Donnerstag, 12. April 2012 12:20Acamar, that didn't work very well unfortunately. It still takes a long time to create, and every control appears individually on screen. I'm using a For Each loop to add the controls based on data from an array -- could that have any effect on the performance?
Just call me Silvi or LS... My site: here
-
Donnerstag, 12. April 2012 12:29Is the problem when adding them (one time) or when painting them (each time)? If possible, call Controls.AddRange instead of .Add
Armin
-
Donnerstag, 12. April 2012 12:32
Is the problem when adding them (one time) or when painting them (each time)? If possible, call Controls.AddRange instead of .Add
Adding and painting, really. However if I could at least get a smooth adding it would be nice! Will AddRange make a difference? Because Controls.Clear() didn't exactly work smoothly either until I hid the control.
Armin
Just call me Silvi or LS... My site: here
-
Donnerstag, 12. April 2012 12:42
I just tried it with 100 textboxes and it works just fine. Perhaps your controls are more complex.
For adding, create the controls, add them to a list of controls, then add them to the container's control collection in one statement using AddRange as described above. This eliminates any delay as a result of creating and initialsing the controls, but the drawing delay will still be there.
For drawing there is not much you can do. But 100 controls seems excessive. Is it possible to redesign the application so that it doesn't need that amount of controls?
-
Donnerstag, 12. April 2012 12:49
Actually I just went back and remembered there are 263 controls to be created on a page.. but the Labels work just fine. The problem are the PictureBoxes which I cannot remove, and those are 132. However only about 60 have to render at one time on an average-height screen.
I will try using AddRange and see if it makes a small difference. As for redesigning the whole application, I have a couple options.
Either I just don't use controls and manually paint the images (they are more or less geometric figures), but that would pose problems with catching the clicks, and it would be a pain to redraw every time the user moves the window off screen or scrolls up and down.
The second option is to use colour-filled squares instead of images, which will definitely take less time, but it will look really ugly and unprofessional.
Just call me Silvi or LS... My site: here
-
Donnerstag, 12. April 2012 12:50Oh, also the PictureBoxes aren't actually picture boxes. They are UserControls with a BackgroundImage set to the image, and some added functionality (but nothing that displays on screen, just more code)
Just call me Silvi or LS... My site: here
-
Donnerstag, 12. April 2012 12:56
Was about to suggest replacing the PictureBoxes by painting in OnPaint, but you did it yourself. So... Capturing clicks just requires subtracting an offset, or is there another issue? Sure, you have to use a loop to find the "control"/figure that has been clicked. Painting when scrolling: Take e.Cliprectangle in OnPaint into account to avoid redrawing all areas.
EDIT: Just saw your latest message so this mesage of mine may have become inapproparite.
Armin
- Bearbeitet Armin Zingler Donnerstag, 12. April 2012 12:57
-
Donnerstag, 12. April 2012 14:04
Yeah, in general I'd rather not use the first option because I'd have to adapt a lot of code and a lot of math. It took me ages to figure it all out and I'd end up redoing it!Was about to suggest replacing the PictureBoxes by painting in OnPaint, but you did it yourself. So... Capturing clicks just requires subtracting an offset, or is there another issue? Sure, you have to use a loop to find the "control"/figure that has been clicked. Painting when scrolling: Take e.Cliprectangle in OnPaint into account to avoid redrawing all areas.
EDIT: Just saw your latest message so this mesage of mine may have become inappropriate.
Armin
Just call me Silvi or LS... My site: here
-
Donnerstag, 12. April 2012 20:52
Yeah, in general I'd rather not use the first option because I'd have to adapt a lot of code and a lot of math. It took me ages to figure it all out and I'd end up redoing it!
I am sure that drawing directly to the form instead of using picture boxes would solve the display delay. A lot of the math is exactly the same. I'm not sure sure why Armin suggests a loop to discover the cliccked image - it is not a loop but simply the inverse of the calculation that was originally done to position the image.
If all the images are drawn to a single bitmap as they are added, removed or changed, then there is only one large drawing to be done in the paint event, and scrolling becomes trivial. You are trading RAM for CPU, which is a common way to speed things up.
-
Donnerstag, 12. April 2012 21:03
I'm not sure sure why Armin suggests a loop to discover the cliccked image - it is not a loop but simply the inverse of the calculation that was originally done to position the image.
If you have the click position and want to find out on which of the painted areas has been clicked, what else can you write but a loop? Maybe I misunderstood something. :-)
Armin
-
Donnerstag, 12. April 2012 21:59
If you have the click position and want to find out on which of the painted areas has been clicked, what else can you write but a loop? Maybe I misunderstood something. :-)
The calculation of the clicked item would be done using the inverse of the calculation that was used to determine the area to be painted - a loop is not required.
Public Class Form1 Dim BMP As Bitmap = New Bitmap(800, 600, System.Drawing.Imaging.PixelFormat.Format32bppArgb) Dim RND As New Random Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click BMP = New Bitmap(800, 600, System.Drawing.Imaging.PixelFormat.Format32bppArgb) Dim g As Graphics = Graphics.FromImage(BMP) For I As Integer = 0 To 9 Dim c As Color = Color.FromArgb(255, RND.Next(1, 255), RND.Next(1, 255), RND.Next(1, 255)) Dim Col As Integer = I Mod 4 Dim Row As Integer = I \ 4 g.FillRectangle(New SolidBrush(c), Col * 50, Row * 50, 50, 50) Next Me.Invalidate() End Sub Private Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click Dim X As Integer = Me.PointToClient(Cursor.Position).X - 100 Dim y As Integer = Me.PointToClient(Cursor.Position).Y - 100 X = X \ 50 y = y \ 50 Dim I As Integer = X + y * 4 Label1.Text = New Point(X, y).ToString & "=" & I Dim g As Graphics = Graphics.FromImage(BMP) Dim c As Color = Color.FromArgb(255, RND.Next(1, 255), RND.Next(1, 255), RND.Next(1, 255)) Dim Col As Integer = I Mod 4 Dim Row As Integer = I \ 4 g.FillRectangle(New SolidBrush(c), Col * 50, Row * 50, 50, 50) Me.Invalidate() End Sub Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint e.Graphics.DrawImage(BMP, New Point(100, 100)) End Sub End Class -
Donnerstag, 12. April 2012 22:14If you have pictureboxes within a container, you can place them anywhere. I read the thread again but I don't find where it's mentioned they are arranged in rows and columns.
Armin
-
Donnerstag, 12. April 2012 22:25
It may require some redesign, but OP has indicated that the images are currently being positioned using math - if a calculation is involved in placing the controls then the inverse of that calculation can be used to locate the control position. It doesn't have to be regular rows and columns.
But even if a loop is required, it can be optimised by creating a list of regions corresponding to the images - the loop is then an iteration through this list for a hittest, which is going to be fast. I have an application that does hittesting for thousands of irregular objects and the delay is only just noticeable on a typical business PC.
-
Donnerstag, 12. April 2012 23:21
It may require some redesign, but OP has indicated that the images are currently being positioned using math - if a calculation is involved in placing the controls then the inverse of that calculation can be used to locate the control position. It doesn't have to be regular rows and columns.
But even if a loop is required, it can be optimised by creating a list of regions corresponding to the images - the loop is then an iteration through this list for a hittest, which is going to be fast. I have an application that does hittesting for thousands of irregular objects and the delay is only just noticeable on a typical business PC.
Sometimes people make a big thing of nothing. If I make a suggestion based on the picture I have from the situation doesn't necessarily mean I contradict your point of view. Your message reads as if I said a loop was too slow, in fact I did not. The reason for mention a table-style layout was not my own limitation to regular rows and colors, but it was in reference to the example you wrote yourself doing just that.
Armin
-
Samstag, 14. April 2012 08:24
The images are indeed arranged in regular rows and columns -- in fact only four rows, and maximum 33 columns. I could get the position of the images with math as suggested, but it would be hard to transfer other functions of the picture to object-less functions.
I will experiment with a drawn version separately from my current product to see if there are any improvements. Will it be best to use lines and ellipses, or can I draw the images onto the Graphics element?
Just call me Silvi or LS... My site: here
-
Samstag, 14. April 2012 10:21
Will it be best to use lines and ellipses, or can I draw the images onto the Graphics element?
Drawing a line or ellipse is the same as drawing an image, so I am not sure what options you are referring to here.
-
Samstag, 14. April 2012 20:12using DrawLine and DrawEllipse as opposed to printing an image onto the graphics element is the difference.
Just call me Silvi or LS... My site: here
-
Samstag, 14. April 2012 21:47The only effective difference between DrawLine, DrawImage, DrawEllipse DrawString and the rest is the source of the pixels to be drawn. This decision has nothing to do with the design of the solution - you can defer any consideration of this until the solution is in place. You can even change the option on a per-item basis with no effect on the design. You should choose the one that best suits the visual effect that you need, and only consider a change after you are able to show that such a change might deliver an additional speed improvement.
-
Sonntag, 15. April 2012 11:22
You will not achieve what you want by adding multiple controls to the form. The flickering is caused by the fact that each child window not only paints itself, but also causes it's parent to repaint itself, this in turn causes each existing child of the parent to paint itself and so the more child controls that you add the worse the flicker becomes.
The solution is to paint directly to the form.
If this were my task I would create a simple class which will be responsible for it's own painting and hittesting.
Here is a simple class which does just that:
Imports System.ComponentModel Imports System.Drawing.Drawing2D Public Class LiteEllipse Private _parent As Control Private _location As Point Private _size As Size Private _borderColor As Color = Color.Red Private _fillColor As Color = Color.White Public Event Click As EventHandler Public Property Parent As Control Get Return _parent End Get Set(value As Control) _parent = value OnParentChanged(EventArgs.Empty) End Set End Property <Browsable(False)> _ Public ReadOnly Property Bounds As Rectangle Get Return New Rectangle(Me.Location, Me.Size) End Get End Property Public Property Location As Point Get Return _location End Get Set(value As Point) If _location <> value Then _location = value InvalidateParent() End If End Set End Property Public Property Size As Size Get Return _size End Get Set(value As Size) If _size <> value Then _size = value InvalidateParent() End If End Set End Property Public Property BorderColor As Color Get Return _borderColor End Get Set(value As Color) If _borderColor <> value Then _borderColor = value InvalidateParent() End If End Set End Property Public Property FillColor As Color Get Return _fillColor End Get Set(value As Color) If _fillColor <> value Then _fillColor = value InvalidateParent() End If End Set End Property Public Sub New(parent As Control) Me.Parent = parent End Sub Private Sub OnParentChanged(Empty As EventArgs) If (Parent IsNot Nothing) Then AddHandler Parent.Click, AddressOf OnClick AddHandler Parent.Paint, AddressOf OnPaint End If End Sub Private Sub OnClick(sender As Object, e As EventArgs) If Me.Size = Size.Empty Then Return End If Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds rc.Offset(dr.Left, dr.Top) Dim clickPath As GraphicsPath = New GraphicsPath() clickPath.AddEllipse(rc) If clickPath.IsVisible(Me.Parent.PointToClient(Cursor.Position)) Then RaiseEvent Click(Me, e) End If End Sub Private Sub OnPaint(sender As Object, e As PaintEventArgs) If (Me.Size <> Size.Empty) Then e.Graphics.SmoothingMode = SmoothingMode.AntiAlias Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds rc.Offset(dr.Left, dr.Top) Using fillBrush As New SolidBrush(Me.FillColor) e.Graphics.FillEllipse(fillBrush, rc) End Using Using borderPen As New Pen(Me.BorderColor) e.Graphics.DrawEllipse(borderPen, rc) End Using End If End Sub Private Sub InvalidateParent() If Me.Parent IsNot Nothing Then Me.Parent.Invalidate() End If End Sub End ClassThe problem here is that these classes cannot be added at design time because they are not designable classes. I would create a custom container control which had a collection property to add these classes if I required design time support, but that is something for another thread.
Another problem is that these controls will not controll scrolling, so you will need to set the parents AutoScroll and AutoScrollMinSize youself to account for the position of the controls.
Now you can easily add these classes at runtime as follows:
Public Class Form1 Private Ellipse1, Ellipse2 As LiteEllipse Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load Me.Ellipse1 = New LiteEllipse(Me) Me.Ellipse2 = New LiteEllipse(Me) Me.Ellipse1.Location = New Point(10, 10) Me.Ellipse2.Location = New Point(250, 350) Me.Ellipse1.Size = New Size(50, 50) Me.Ellipse2.Size = New Size(50, 50) AddHandler Me.Ellipse1.Click, AddressOf EllipseClick AddHandler Me.Ellipse2.Click, AddressOf EllipseClick Me.AutoScroll = True Me.AutoScrollMinSize = New Size(310, 410) End Sub Private Sub EllipseClick(sender As Object, e As EventArgs) Dim ellipse As LiteEllipse = CType(sender, LiteEllipse) If ellipse IsNot Nothing Then ellipse.BorderColor = RandomColor() ellipse.FillColor = RandomColor() End If End Sub Public Function RandomColor() As Color Static rnd As New Random() Return Color.FromArgb(255, rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256)) End Function Private Sub Form1_Scroll(sender As System.Object, e As System.Windows.Forms.ScrollEventArgs) Handles MyBase.Scroll Me.Invalidate() End Sub End ClassObviously, you will need to modify the class so as to suit your exact requirements and will likely need to add the class instances as an array rather than individually as I have done here (to keep the code a simple as possible for clarity).
I haven't fully tested this example so there may be some calculations that need to be corrected, but it should get you started.
Mick Doherty
http://dotnetrix.co.uk
http://glassui.codeplex.com- Als Antwort markiert SilviSoft Sonntag, 15. April 2012 11:25
-
Sonntag, 15. April 2012 11:25Thank you very much! I will try to adapt that code to fit my needs and I'm sure it will work. :)
Just call me Silvi or LS... My site: here
-
Sonntag, 15. April 2012 12:33
You're welcome.
Depending upon how complex your painting is (in fact it's probably best to do it anyway) you may want to optimise the InvalidateParent() method as follows:
Private Sub InvalidateParent() If Me.Parent IsNot Nothing Then Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds rc.Offset(dr.Left, dr.Top) Me.Parent.Invalidate(rc) End If End SubMick Doherty
http://dotnetrix.co.uk
http://glassui.codeplex.com- Als Antwort markiert SilviSoft Sonntag, 15. April 2012 16:47
-
Sonntag, 15. April 2012 13:48
You're welcome.
Depending upon how complex your painting is (in fact it's probably best to do it anyway) you may want to optimise the InvalidateParent() method as follows:
Private Sub InvalidateParent() If Me.Parent IsNot Nothing Then Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds rc.Offset(dr.Left, dr.Top) Me.Parent.Invalidate(rc) End If End Sub
Mick Doherty
http://dotnetrix.co.uk
http://glassui.codeplex.comIndeed, it works a lot better and doesn't flash as much!
However I still have a problem with your code: when painting for the first time, the ellipse is cut off at the bounds of the FillEllipse, so part of the border is not drawn at all. Then if I scroll or move the form out of view and back, it draws the ellipse fully (including the border). Why is this?
Just call me Silvi or LS... My site: here
-
Sonntag, 15. April 2012 19:26
I'm not seeing that effect here, but it'll be GDI+ clipping or else a result of Graphics AntiAiias (not really my speciality). Does this happen when you don't set the Graphics Smoothing mode?
I've played around a little with the code and have modified the OnPaint() method to only draw if the ClipRectangle (inflated by 1 pixel to allow for GDI+ clipping) intersects with the drawing bounds:
Private Sub OnPaint(sender As Object, e As PaintEventArgs) If (Me.Size <> Size.Empty) Then e.Graphics.SmoothingMode = SmoothingMode.AntiAlias Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds Dim clipRect As Rectangle = e.ClipRectangle clipRect.Inflate(1, 1) rc.Offset(dr.Left, dr.Top) If clipRect.IntersectsWith(rc) Then Using fillBrush As New SolidBrush(Me.FillColor) e.Graphics.FillEllipse(fillBrush, rc) End Using Using borderPen As New Pen(Me.BorderColor) e.Graphics.DrawEllipse(borderPen, rc) End Using End If End If End Sub...and have modifed the form to add 250 ellipses in a 50 x 50 grid:
Public Class Form1 Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load Dim scrollSize As Size For x As Int32 = 0 To 49 For y As Int32 = 0 To 49 Dim ellipse As New LiteEllipse(Me) ellipse.Size = New Size(40, 40) ellipse.Location = New Point(10 + (x * 50), 10 + (y * 50)) ellipse.BorderColor = Color.Red ellipse.FillColor = Color.White scrollSize = Size.Add(New Size(ellipse.Location), New Size(50, 50)) AddHandler ellipse.Click, AddressOf EllipseClick Next Next Me.DoubleBuffered = True Me.AutoScroll = True Me.AutoScrollMinSize = scrollSize End Sub Private Sub EllipseClick(sender As Object, e As MouseEventArgs) Dim ellipse As LiteEllipse = CType(sender, LiteEllipse) If ellipse IsNot Nothing Then If e.Button = Windows.Forms.MouseButtons.Right Then ellipse.FillColor = Color.White Return End If If ellipse.FillColor.Equals(Color.Blue) Then ellipse.FillColor = Color.Red Else ellipse.FillColor = Color.Blue End If End If End Sub End Class...oh yes, I also modified the LiteEllipse class to use a MouseEventHandler so that we can determin which button was clicked:
Public Event Click As MouseEventHandler Private Sub OnClick(sender As Object, e As MouseEventArgs) If Me.Size = Size.Empty Then Return End If Dim dr As Rectangle = Me.Parent.DisplayRectangle Dim rc As Rectangle = Me.Bounds rc.Offset(dr.Left, dr.Top) Dim clickPath As GraphicsPath = New GraphicsPath() clickPath.AddEllipse(rc) If clickPath.IsVisible(Me.Parent.PointToClient(Cursor.Position)) Then RaiseEvent Click(Me, e) End If End Sub
Mick Doherty
http://dotnetrix.co.uk
http://glassui.codeplex.com
- Bearbeitet Mick Doherty Sonntag, 15. April 2012 19:32

