How to control the order in which individual child controls are rendered

B

Bruce

Hello All,

I am creating an image browsing function within an application. As such, I
created a Thumbpanel control (shown below) which displays a collection of
thumbnails. Each thumbnail on that panel is also a custom control ( of a
class called Thumbnail) placed at a calculated location on the Thumbpanel.
It turns out to take 1+ seconds to construct and render each Thumbnail, so
in turn it takes MANY seconds to render the whole panel. Therefore I need
to force each Thumbnail to be rendered individually as they are constructed,
so that the user will not be confused by the frozen screen awaiting the
whole thing to render. I expected that the line:
thumb.Refresh();

.... would force the thumb object (of type Thumbnail) to be rendered upon
that call. But that didn't do the job.

Can someone give my guidance on how to force rendering of the individual
child controls (Thumbnails) in the order I specify?

Thanks,
-- Bruce

-------------------------------------------

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace WTMWClient
{
/// <summary>
/// Summary description for ThumbPanel.
/// </summary>
public class ThumbPanel : System.Windows.Forms.Control
{
Assets m_Assets;
ArrayList m_Thumbnails;
bool m_Scrollable = false;

public delegate void ThumbPanelDoubleClickedHandler(object sender,
ThumbnailDoubleClickedEventArgs e);
public event ThumbPanelDoubleClickedHandler
ThumbPanelDoubleClickedEvent;

private System.ComponentModel.Container components = null;

public ThumbPanel()
{
InitializeComponent();

m_Assets = new Assets(); // Simply a default value so it will not
be null.
m_Thumbnails = new ArrayList();
}

protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}
base.Dispose( disposing );
}

~ThumbPanel()
{
// ~base();
foreach (Thumbnail thumb in m_Thumbnails)
{
thumb.Dispose();
}
}


#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//
// ThumbPanel
//
this.Dock = System.Windows.Forms.DockStyle.Fill;

}
#endregion

protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here

// Calling the base class OnPaint
base.OnPaint(pe);
}


protected override void OnResize(EventArgs e)
{
base.OnResize (e);
PlaceThumbnails( false );
}

/// <summary>
/// Place each thumbnail in its proper position. Assumption is that
/// each thumbnail has already be constructed and placed in the
/// m_Thumbnails collection before this call.
/// </summary>
protected void PlaceThumbnails( bool isFirstDrawOfThisSetOfThumnails )
{
// Remove all the old ones. (But, dont "dispose" them, since they
// are still instantiated and held in m_Thumbnails.)
this.Controls.Clear();
// Place each one in proper position....
int x, y;
x = y = 0;
if ( isFirstDrawOfThisSetOfThumnails )
{
m_Thumbnails.Clear();
Thumbnail thumb;
foreach (Asset asset in m_Assets)
{
thumb = new Thumbnail( asset );
m_Thumbnails.Add( thumb );
thumb.ThumbnailDoubleClickedEvent +=
new WTMWClient.Thumbnail.ThumbnailDoubleClickedHandler(
thumb_ThumbnailDoubleClickedEvent);

thumb.Location = new Point(x, y);
this.Controls.Add(thumb);
thumb.Refresh(); // I expected this to force rendering
here. No luck!!

x += thumb.Width;
if ( x > this.Width-thumb.Width )
{
x = 0;
y += thumb.Height;
}

}
}
else
{
// no need to instantiate individual Thumbnails in this case.
// Rather, just grab each from the m_Thumbnails collection.
foreach (Thumbnail thumb in m_Thumbnails)
{
thumb.Location = new Point(x, y);
this.Controls.Add(thumb);
x += thumb.Width;
if ( x > this.Width-thumb.Width )
{
x = 0;
y += thumb.Height;
}
thumb.Refresh();
}

}
}



/// <summary>
/// Assign an Assets collection to be spanned via a set of thumbnails
on the panel...
/// </summary>
public Assets AssetsSource
{
set
{
m_Assets = value;
if (m_Assets.Count == 0) return;

PlaceThumbnails( true );
}
} // Property: AssetsSource


protected void thumb_ThumbnailDoubleClickedEvent( object sender,
ThumbnailDoubleClickedEventArgs e)
{
// MessageBox.Show( "clicked on: " + e.Asset.FileName );
if ( ThumbPanelDoubleClickedEvent != null)
{
ThumbPanelDoubleClickedEvent( sender, e );
}
}
}
}
 
B

Bruce Wood

If I were you I would design some sort of background thread that
renders the images to thumbnail size (which is what is taking the time,
I imagine). Use a queue to stack up requests for the background thread
(of what images to scale) and then have it send back events to the UI
thread as each image is successfully scaled. When the UI thread gets an
event, it figures out which Thumbnail object it belongs to and
populates the object with the picture.

That way the user won't be left with a frozen screen... he'll see the
images being drawn in random order, but will still have full control
while it's happening.
 
J

Jeffrey Tan[MSFT]

Hi Bruce,

Thanks for your post!!

Based on my understanding, you have a image thumbnail view panel, which
contains many thumbnail custom control, each displaying one thumbnail
image. Now, you find the rendering of thumbnail custom control costs some
time, so the entire UI will freezing for painting. So you want to get a way
to force each thumbnail custom control to paint with UI responsible(without
freezing), yes?
If I misunderstand you, please feel free to tell me, thanks

Normally, a Winform application is a single-thread app, which there is only
one message-queue. Now, when we are processing WM_SIZE message in OnResize
method, we called PlaceThumbnails() method, which do the thumbnail custom
control construction one-by-one. This is a long processing function, which
suspend all the message processing in current thread queue(be remember that
we are single-threaded, with only one thread-queue). So when we are moving
the application window(or other cause window re-painting operation), a
WM_PAINT message will be posted to the thread message queue for
processing(so that the window can re-paint itself correctly). However we
are still doing the long construction procesing, so the WM_PAINT message
has no way to be processed, then the UI will not be processed, the result
is a frozen screen seen by the end user.

Now, we know why the frozen screen went out. So the simplest way to give
the customer a responsible UI is explicit foring the application to process
the WM_PAINT message in the message queue in the long processing. In win32
world, we should use GetMessage API to get the message and then use
DispatchMessage Api to dispatch it to the window procedure. Now, in .Net,
life is much easier, we can just call Application.DoEvents method, which
will process all Windows messages currently in the message queue(actually,
Application.DoEvents method encapsulate the Win32 message processing logic
internally).

Note: in PlaceThumbnails() method, we should call Application.DoEvents in
the foreach loop, so that we can process the pending messages with a period
of time. Like this:

protected void PlaceThumbnails( bool isFirstDrawOfThisSetOfThumnails )
{
// Remove all the old ones. (But, dont "dispose" them, since they
// are still instantiated and held in m_Thumbnails.)
this.Controls.Clear();
// Place each one in proper position....
int x, y;
x = y = 0;
if ( isFirstDrawOfThisSetOfThumnails )
{
m_Thumbnails.Clear();
Thumbnail thumb;
foreach (Asset asset in m_Assets)
{
thumb = new Thumbnail( asset );
m_Thumbnails.Add( thumb );
thumb.ThumbnailDoubleClickedEvent +=
new WTMWClient.Thumbnail.ThumbnailDoubleClickedHandler(
thumb_ThumbnailDoubleClickedEvent);

Application.DoEvents(); //-------------this will cause
the re-painting of UI
thumb.Location = new Point(x, y);
this.Controls.Add(thumb);

x += thumb.Width;
if ( x > this.Width-thumb.Width )
{
x = 0;
y += thumb.Height;
}

}
}
else
{
// no need to instantiate individual Thumbnails in this case.
// Rather, just grab each from the m_Thumbnails collection.
foreach (Thumbnail thumb in m_Thumbnails)
{
thumb.Location = new Point(x, y);
this.Controls.Add(thumb);
x += thumb.Width;
if ( x > this.Width-thumb.Width )
{
x = 0;
y += thumb.Height;
}
thumb.Refresh();
}

}
}

Hope this helps.
=====================================================================
Thank you for your patience and cooperation. If you have any questions or
concerns, please feel free to post it in the group. I am standing by to be
of assistance.

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
B

Bruce

Jeffrey,
Thanks for your detailed answer. I was not actually able to get it to work
as yet, but your explaination was never-the-less helpful for giving me an
understanding of how things are supposed to work. Meanwhile, I recently
called in a formal incident at Microsoft Tech Support on a related topic, so
I'll follow up there.
Thanks,
Bruce
 
J

Jeffrey Tan[MSFT]

Hi Bruce,

Thanks for your feedback.

I am not sure why you can not get it to work. Have you tried my suggestion
to place a call in the foreach loop?

Anyway, I see that you have contacted the Microsoft PSS support, I think it
will provide you more close support for this issue. Thanks

Best regards,
Jeffrey Tan
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top