Introduction
Overview
The custom control
The control properties
Using the control
Conclusion
When I saw a Microsoft's sample for using SQLXML
that demonstrate a little project management application I said to myself, "It would be
cool to have a similar progress control functionality in the dataset
directly.". I work with data a lot and sometimes there are data items that are of a
percentage type, that represent some progress#. It is very effective to show
such data in a graphical way.
What we have here is a little sample of how to do such thing. It is a sample of
how to create a custom control derived from the System.Windows.Forms.DataGrid
object, and how to write custom property modify dialog. The Component allows
the consumer to specify which columns are of the progress type within the grid, and the
grid then renders the progress based upon specified attributes given to the grid
object (like the color or style of the progress control).
The project for the custom control itself is a standard C# Custom Control
project. When creating it I just set up the new project from the custom project
template.
The whole component implementation is done in one file - CProgressDataGrid.cs
in the CProgressControls
namespace (I thought that maybe it can be
usefull to create more similar controls and put them under the same
namespace.)
As you can see from the source, the class is separated into several areas, the
functional API of the component to access the attributes programatically (via
functionz or properties), - the region named 'accessor functions', and the main part,
implemented in the region named 'painting functions'. This region contains the
main OnPaint
handler. The logic is very straightforward, get the
list of columns that are marked as progress column types and then for every
cell in such column, get its value and draw the box there. Simple as that.
There are some properties you can set up into the control, via Visual Studio's
designer or via the component's API:
- Color of the progress bar
- Color of the progress bar percentage text
- Flag if the percentage text is set to be shown or not
- Style of the progress (can be 3D, flat, inset or with solid border)
- Table name within the data set to use for populating the data
- List of columns
There is the custom dialog used to select the columns for the progress control.
To implement your own property dialog you need to undertake several steps. First
you need to create your own new value editor class. In this case it is
CProgressDataGridColumnsValueEditor
,
as you can see it is derived from the UITypeEditor
class.
Basically you need to override two functions - GetEditStyle
and EditValue
.
The GetEditStyle
function is called from the VS.NET framework to
retrieve the information about the type of the editor used for the property,
there are 3 types defined - Modal
, DropDown
and None
,
for exact description see the documentation for the UITypeEditorEditStyle
class.
In our case we use UITypeEditorEditStyle.Modal
type. If you look
at the EditValue
function, you can see that this is very simple
too. You can get the instance of the container if you wish, just type the context.Instance
to your control's type. Here we have function call like:
CProgressDataGrid oGrid = (CProgressDataGrid)context.Instance;
On the next lines we create an instance of our dialog used to populate the list of
columns. I will not go into description of the form used to do that (called
CProgressDataGridColumnsValueEditorForm
), it is not important
for this sample, but the source is included so you can observe that part of code
as well.
Once the property dialog ends it returns control to the EditValue
function,
it is recommended to store the information if the data was changed in the
dialog and check it here, if this is the case then just copy the data to the
value
variable and return it so the value will be stored
in the property.
I had a collection of type ArrayList
that held the columns
selected as progress bar columns, but whenever the persistent data was loaded
from the resource file, the deserialization process went wrong. I tried to find
a solution, but finally gave up, once I have some more time I'll dig into that
again, of course if you have any ideas how to make a collection as a property
persistent and updatable via the VS.NET designer, drop me a message. Anyway, in the
end I decided to do it with a little trick. The list is stored in the string property,
where the column indexes are delimited by the comma (,) separator, there is a
little helper function that turns the string into the ArrayList
object.
So, "How can I use that" you migh ask. First place the built assembly, or the
source, onto your machine and build it (in the case of the source). Once this is
done, go into the Toolbox window and right click when you have some form
selected, choose the "Customize toolbox" menu item and add the reference to
this assembly. At the end of the component's list you will see the CProgressDataGrid
,
place it onto your form.
Then you need to specify a table style, and add some columns to the style according
to the your underlaying dataset. Then you can scroll down and find the property
group "Progress control", and specify the required color and type, whether you want to see the text
or not, and the Table name within the data set. Once you have done this then click
the "..." in the ProgressColumns attribute and use the dialog to select your
"progress" columns. Save the project and build it.
That's it, if you have proper dataset bound to the data grid with some numerical
data for the progress column you have chosen, then you should see the progress
bars in that column.
Advantages
- Simple
- Easy to use
- Several styles to choose from
- Compact code
Limitations
- No sorting support
- No column reorder support
- Selected columns property is not implemented as a collection
- Support for only one table within the data set
Future possible extensions, ideas
- Sorting support
- More styles
- In-place graphical editor (like the progress drag control in MS Project)
- Style based per cell (column) not per grid only (every cell can have
different progress)
- Turn it into the new grid cell object? (like
DataGridBoolColumn
)