Alexander Morou
Sponsor
Hello,
I figured I'd create a topic dedicated to this, since it's a project all on its own.
I apologize to Tdata in advance for delaying the character database, but I think in the end it's worth it, overall.
The biggest worry I have with the entire project is ensuring that it's fast. So the first thing I did was set up a series of regions dedicated to each drawing area: Columns, List Items and the non-client space in between.
So far I have little visually, the data's mostly there, but I don't have hardly any of the control logic laid out. Though I figured this would be a good example to show others how I go about making controls. Since there was some interest in other members on how to create visually interactive controls.
First off, there's a few things you need to keep in mind about a List View Control:
For now that's all I can think of, I'll revise this list as I continue. I'd be more scientific about it, but since I haven't had to make a control, like this, from scratch for a few years, it'll take some guess work, debugging and review, et cetera.
http://alexandermorou.com/images/screen ... review.png[/img]
It isn't much, but every journey begins with that first step.
For those interested, here's what I'm going to use to segment the drawing process:
Edit (December 6th, 2008 - 2:24 p.m.):
After looking closer at the purpose of the ScrollableControl, the original base of the ImageListView, I've decided to use Control as the base, and implement the ScrollBar handling myself via interception of Windows Messages. The reason for this is the lack of the ScrollableControl to scroll without controls, in a way that's easy to manipulate (it kept screwing up the drawing mechanisms).
Edit 2 (December 6th, 2008 - 11:18 p.m.):
After reading up on the proper API calls, I decided to reference the Essential COM (Component Object Model) Interoperability project I'm writing. While it's larger than the entire Control library and character database, it'll provide necessary information for handling COM errors and it's a good time to expand upon the library for Scroll Bar management. The reason for the size is part of the feedback is a result of COM constructs mined from MSDN. Each message is contained within the HResult struct (a simple Int32-sized struct). Within this is the Documentation comments, a property for each element (with a backing field), the HResult struct contains two enormous switch statements to pull the Description, and Name of the HResult. I'll probably be replacing the Result Description switch statement with a call to FormatMessage, though this depends on how many messages are supported, since they vary system to system based upon drivers, if I read the documentation properly.
Size note: The ComErrors contains 52,795 lines of generated code, the HResult contains 12,022 lines. There's also various other dll imports, COM interfaces, and general structures used to interact with the most frequently used COM.
I figured I'd create a topic dedicated to this, since it's a project all on its own.
I apologize to Tdata in advance for delaying the character database, but I think in the end it's worth it, overall.
The biggest worry I have with the entire project is ensuring that it's fast. So the first thing I did was set up a series of regions dedicated to each drawing area: Columns, List Items and the non-client space in between.
So far I have little visually, the data's mostly there, but I don't have hardly any of the control logic laid out. Though I figured this would be a good example to show others how I go about making controls. Since there was some interest in other members on how to create visually interactive controls.
First off, there's a few things you need to keep in mind about a List View Control:
- The Vertical ScrollBar Affects only the ListItem client area.
- The Horizontal ScrollBar's maximum value is dependent upon the sum of the widths of the ColumnHeaders.
- Cached Data is important - In order to expedite drawing, it's important to know a few things about the display order of the Groups, Columns and then the items within the Groups (if applicable).
- I'll be doing experiments to determine the best case drawing habits. I might cache the visible items on screen, should be small lists of 10-70 items at most. In series of 60,000 items (likely scenario in file system displays), those lists will be very important in making drawing as fast as possible.
- When drawing in .NET, make use of the [System]Brushes, [System]Fonts, [System]Pens, [System]Colors
- If you use Regions, Fonts, Pens, Brushes, or anything else, that isn't part of the CLR's defaults, don't forget to dispose them when you're done with them, even if you cache them.
- Your entire system is allotted 2^16 (16,384) GDI Objects, each Pen, Region, Font, Brush or whatever takes a single object handle. If you exceed this maximum, your system becomes unstable.
For now that's all I can think of, I'll revise this list as I continue. I'd be more scientific about it, but since I haven't had to make a control, like this, from scratch for a few years, it'll take some guess work, debugging and review, et cetera.
http://alexandermorou.com/images/screen ... review.png[/img]
It isn't much, but every journey begins with that first step.
For those interested, here's what I'm going to use to segment the drawing process:
(First three lines from OnPaint)
MergeDrawingAreasAndPaint(nonClientRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawNonClientArea);
MergeDrawingAreasAndPaint(columnHeaderRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawColumnHeaders);
MergeDrawingAreasAndPaint(listItemRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawListItems);
private static void MergeDrawingAreasAndPaint(Region finalRegion, Region originalClippingRegion,
Rectangle originalClippingBounds, Graphics targetGraphics,
PreEventHandler<PaintEventArgs> furtheredDrawing)
{
if (finalRegion == null)
throw new ArgumentNullException("finalRegion");
if (originalClippingRegion == null)
throw new ArgumentNullException("originalClippingRegion");
if (targetGraphics == null)
throw new ArgumentNullException("targetGraphics");
if (furtheredDrawing == null)
throw new ArgumentNullException("furtheredDrawing");
//Make the region with the bounding box of the original.
Region outputRegion = new Region(originalClippingBounds);
//Intersect in the intended drawing area.
outputRegion.Intersect(finalRegion);
//Include complexity of original clipping region.
outputRegion.Intersect(originalClippingRegion);
if (!outputRegion.IsEmpty(targetGraphics))
{
targetGraphics.Clip = outputRegion;
furtheredDrawing(new PaintEventArgs(targetGraphics,
ConvertFromF(outputRegion.GetBounds(targetGraphics))));
}
targetGraphics.Clip = originalClippingRegion;
outputRegion.Dispose();
}
MergeDrawingAreasAndPaint(nonClientRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawNonClientArea);
MergeDrawingAreasAndPaint(columnHeaderRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawColumnHeaders);
MergeDrawingAreasAndPaint(listItemRegion, e.Graphics.Clip, e.ClipRectangle, e.Graphics,
this.OnDrawListItems);
private static void MergeDrawingAreasAndPaint(Region finalRegion, Region originalClippingRegion,
Rectangle originalClippingBounds, Graphics targetGraphics,
PreEventHandler<PaintEventArgs> furtheredDrawing)
{
if (finalRegion == null)
throw new ArgumentNullException("finalRegion");
if (originalClippingRegion == null)
throw new ArgumentNullException("originalClippingRegion");
if (targetGraphics == null)
throw new ArgumentNullException("targetGraphics");
if (furtheredDrawing == null)
throw new ArgumentNullException("furtheredDrawing");
//Make the region with the bounding box of the original.
Region outputRegion = new Region(originalClippingBounds);
//Intersect in the intended drawing area.
outputRegion.Intersect(finalRegion);
//Include complexity of original clipping region.
outputRegion.Intersect(originalClippingRegion);
if (!outputRegion.IsEmpty(targetGraphics))
{
targetGraphics.Clip = outputRegion;
furtheredDrawing(new PaintEventArgs(targetGraphics,
ConvertFromF(outputRegion.GetBounds(targetGraphics))));
}
targetGraphics.Clip = originalClippingRegion;
outputRegion.Dispose();
}
Edit (December 6th, 2008 - 2:24 p.m.):
After looking closer at the purpose of the ScrollableControl, the original base of the ImageListView, I've decided to use Control as the base, and implement the ScrollBar handling myself via interception of Windows Messages. The reason for this is the lack of the ScrollableControl to scroll without controls, in a way that's easy to manipulate (it kept screwing up the drawing mechanisms).
Edit 2 (December 6th, 2008 - 11:18 p.m.):
After reading up on the proper API calls, I decided to reference the Essential COM (Component Object Model) Interoperability project I'm writing. While it's larger than the entire Control library and character database, it'll provide necessary information for handling COM errors and it's a good time to expand upon the library for Scroll Bar management. The reason for the size is part of the feedback is a result of COM constructs mined from MSDN. Each message is contained within the HResult struct (a simple Int32-sized struct). Within this is the Documentation comments, a property for each element (with a backing field), the HResult struct contains two enormous switch statements to pull the Description, and Name of the HResult. I'll probably be replacing the Result Description switch statement with a call to FormatMessage, though this depends on how many messages are supported, since they vary system to system based upon drivers, if I read the documentation properly.
Size note: The ComErrors contains 52,795 lines of generated code, the HResult contains 12,022 lines. There's also various other dll imports, COM interfaces, and general structures used to interact with the most frequently used COM.