Added history combo to 'Title' field of transform, print/preview dialogs

Added drag'n'drop support for re-ordering tabs

Added optional confirmation dialog to time tracking

Added close buttons to tasklist tabs

Added blank line to single-selection droplists

And much, much more.

Introduction

You know how it is - you start work on one project and halfway through, you find one or two side-projects crop up that have to be solved before you can continue on the original project.

This is one such project with the added twist that it too started its life as a side-project. Here's what happened:

<Cue wavy screen effect>

I can only imagine that the planets must have been in (mis-)alignment or something, because at one point a few months ago, I was suddenly fielding emails on four or five separate articles I had previously submitted to CodeProject, some asking for features and others for bug fixes.

Foolishly or otherwise, I largely agreed with all the points raised, and subsequently found myself with fourteen or fifteen separate issues to resolve.

The situation was also made worse because I was trying to use CodeProject to keep track of all the things I had agreed to do, meaning that I had to continuously trawl the comments section of each article to remind myself of what I was supposed to be working on.

It even got to the stage where I was worrying that I'd fail to deliver on something - silly I know, but there you are!

Keeping a list on paper was a definite step in the right direction, but since I do all my coding on the same machine, it seemed somewhat inelegant, and anyway, we all know what happens to crucial bits of paper left lying around on desks and such.

The next step was to hunt around on the web for a tool to meet the following requirements:

Simple interface

Support for hierarchical data

Numbered items/subitems

Open file format

Freeware

Simple, huh! not!

I will admit that I did not spend weeks searching, but I am still surprised at the general lack of software matching my needs.

On reflection, I think that the reason may be simple: people are so used to commercial software being 'feature-rich' that when they come to design software themselves, they (not unreasonably) think they too need to cram as much in as possible, often leading to software where a lot of essential functionality is hidden away in the menu bar.

So, surprise, surprise, I decided to write something myself.

However, it's fair to say that I did not originally intend to post it on CodeProject and am only really doing so because I had a heap of fun solving some very interesting problems and these are what I think make it worth it.

Using the Software

There's really very little I need to say here since every feature/function is explicitly visible in the interface.

Nevertheless, the following list of basic capabilities and omissions may go someway to answering any questions that arise:

Files are stored in XML format with .xml file extension.

Trying to load a non-tasklist file will generally fail (unless you read the code to see how to circumvent it).

The number of items/subitems is limited only by memory (although performance may be the deciding factor before you exhaust memory).

Marking a parent item as 'done' will also gray-out child items, but they are not disabled or automatically marked as 'done'.

An ellipsis (...) indicates that an item has sub-items.

All items can be expanded or collapsed (by double-clicking).

Top-level items and sub-items are created using different toolbar buttons.

There are task-specific context-menus.

The previously open tasklists are re-opened on startup.

The tasklist is automatically saved when closing the software or minimizing it to the system tray.

The priority of a task is shown as a grayscale box to the left of the item.

Points of Interest

Here's where we come to the side-projects I was talking about, the first two of which I intend to work up into follow-up articles.

They are:

The 'ordered' tree control, which incorporates a non-client gutter for displaying the item numbers.

The idea stemmed from research I did into alternative designs for a tree-list control, which did not solve it by creating a hybrid control incorporating a tree and a list.

The hybrid control seems such an obvious solution that I suspect few people have stopped to question it, but it has still always struck me as looking far too much like hard work to be truly elegant ('square pegs' and 'round holes' spring to mind).

One possible idea is to implement the 'list' portion entirely in the non-client area of the tree. I.e., shift the right hand client edge to the left and then render the list portion in the resulting non-client area.

Whilst I've yet to get round to building a proof of concept, it was nevertheless this ongoing mental debate which prompted me to try to solve the requirement for numbered items and subitems by rendering the item/subitem numbers in the non-client area.

Without going into too much detail (as this will subsequently be an article of its own), this is how I got it to work:

Handle TVM_INSERTITEM and TVM_DELETEITEM to know exactly when items are added and removed.

In these handlers recalculate the width of the gutter required to display the widest 'dotted' item/subitem number. (Note: this is not necessarily simply the deepest subitem.)

Handle WM_NCCALCSIZE when it does, and offset the left border by the required gutter width.

Handle WM_NCPAINT for painting the numbers.

This is necessarily an over-simplification, but it captures the essence of the solution, and all that essentially remains is lots of fiddling about to ensure the non-client area gets redrawn at the the right times to stay synchronized with the client area.

Embedding .RC control definition data directly in a .cpp file to break the dependency on binary resources (a.k.a. 'Runtime Dialogs').

This is an idea that has been floating about for quite some time and which has only recently gelled into a workable solution.

The problem, put simply, is that if you want to take advantage of the resource editor in Visual Studio (and who doesn't), then you very quickly find yourself stuck with having to load dialog templates from resources compiled into the binary file.

This further means that if you want to make use of a dialog across multiple projects, then either you need to copy and paste the dialog template between project .RC files, or you need to build the dialog into a DLL from which it can be accessed.

'Runtime Dialogs' (a snappy title I coined myself) is a solution that neatly sidesteps both the nuisance of copying dialog resources between resource files and the extra work (and maintenance) involved in packaging dialogs in DLLs.

And it works like this:

First, you design your dialog template in the resource editor, create a CDialog derived class using class wizard, and wire up all the controls just as you normally would.

Next, you #include "runtimedlg.h" and change all instances of CDialog to CRuntimeDlg.

Then, you cut and paste the control definition section from the appropriate section in the .RC file and embed it directly in the dialog's .cpp file as a static string (with a bit of tweaking to handle double quotes and such like).

Finally, in the constructor of your dialog, you simply call CRuntimeDlg::AddRCControls(...) passing the control definitions as a string.

And CRuntimeDlg takes care of the rest including, if required, auto-sizing the dialog to suit the control layout.

I'm certainly not suggesting that this is a 'win-win' solution for all situations but it certainly has merits in its closer coupling of dialog template to dialog code which makes sharing dialogs across multiple projects a breeze.

P.S.: In case it's not clear here, I used CRuntimeDlg to create CToDoCtrl which encapsulates the ordered tree together with the priority, date and comments controls as a single simple-to-instantiate control.

This is possibly the most satisfying aspect of the whole project because it was completely unexpected.

What I mean is that, until recently, my knowledge of DOM and XMLDOM was virtually non-existent, as it's only since I've become more interested in the presentation of AbstractSpoon that I've been forced to get to grips with the various implementations of DOM and XMLDOM out there.

I'm pleased to say that the code on my site works under IE 6.0, Netscape 7.1, and Mozilla, although custom code was required to achieve this.

Generic MFC Classes that may prove Useful to You

The following table lists a wide range of utility classes written for this project. They can all be included in any MFC project provided you include any class dependencies too. Feel free to ask any questions relating to these specific classes and how to use them.

Adds support for recognizing urls, clicking them and setting custom url callbacks

CWinClasses

Encapsulates the ::GetClassName Win32 functions

CXmlFile, CXmlItem

Non-Unicode class for reading and writing xml files

CXmlFileEx

Adds encryption capabilities to CXmlFile

CXmlFile, IEncryption

* CSubclassWnd was originally written by Paul DiLascia for MSJ magazine. The version I use has been heavily extended to suit my specific needs. The classes that depend on it here need this extended version.

Further Work

Whilst this tool was originally intended for my personal use only, it is now a 'community' project, so if you find it useful and want to make suggestions for enhancements or bug fixes, then post below.

I like this software very much, i have only 1 small request/idea, to make it even better
(maybe it came up before, i just dont know about it..)

Everyday usage:
When I wanna change the "% Complete" of a task, I immediately click on the little box in the task's row
in the % column. At this point i would expect for a popup, that shows the possible values
(eg. 5-10-15%..or smg. like that with some kind of resolution).
Similar thing is implemented in the utorrent for example, when someone wants to set a label for a torrent.

So i would suggest a small context aware popup-selection-menu implementation for the columns. And when someone wants to
set the values more precisely, she/he can use the row @ the bottom of the screen.

First of all.
I see that when TDL saves the Tasklist on a network server, it takes time during which the GUI is stuck. Checking in and out takes the same time as a save.
My guess is that saves are done in the thread that is running TDL. The save involve 2 phases, generating the file then saving it.

Technic 1: Multi threading
in main thread, generate the file in a big buffer, then in a second thread, save the buffer.
This unstuck the GUI a lot faster, status bar can be used to show the advance of the saving if needed.

Technic 2: in place change
When checking in or out the file, you know that only check informations will change. if this information is fixed length, you know the offset on the file and then change only the value. The operation is ridiculously small.
It is the your mail software erase a mail instantaneously in a file that can be huge.

Technic 3: variable length changes
Create a special element that is the very last element just before the closing tag of the list

. . .
<CHECK CHECKED="">User Name</CHECK>
</TODOLIST>

Knowing the offset, you can overwrite the actual end with what you want. It is fast.

The technics are not exclusives.

Patrice

“Everything should be made as simple as possible, but no simpler.” Albert Einstein

I have looked into this and am using multi-threading for exporting tasklists after saving, but I'm reluctant to risk the user's data and get it wrong! Everything I read tells me "multi-threading is hard to get right".

But I agree it is an option.

ppolymorphe wrote:

Technic 2: in place change

It's all variable length data to keep the file sizes down.

ppolymorphe wrote:

Technic 3: variable length changes

I'm not sure I understand this entirely. If you change a task at the beginning of the tasklist, one in the middle, and then one at the end, how does writing all the changes to the end of the file work? And when do all the changes get reconciled because someone might need to produce a report using a stylesheet and this will fail if all the data is not in the correct locations.

Have you experimented with turning 'backups' off just to see if that has an effect?

ps. I put my 1800 task (4.5Mb) tasklist on our work LAN and that took maybe 1.5 seconds to check-in, and maybe 3 seconds to check-out (for editing).
pps. I'm happy to explore this further in 7.1.
ppps. I'll also add some logging so we can see where the time is going
pppps. Is your tasklist Unicode or Ansi?

I have some benchmark with Checking in/out local on my computer.
My feeling is that rebuilding the file on fly takes a oweful part of the time needed when Check in/out.
Test done with no other change in the file.
Manual timing gives about 1.50 second per saving.

Patrice

“Everything should be made as simple as possible, but no simpler.” Albert Einstein

Simply testing purpose
I just wanted to time the delay of reaction when checking in/out (the time for mouse cursor to go back to wait state).

A while ago, when I was explaining how TDL was working, the TaskList on which I was working was shared. And I found annoying that more than once, when typing on keyboard, I got stuck with an automatic check-in because of a timeout too short.

Patrice

“Everything should be made as simple as possible, but no simpler.” Albert Einstein

I don't use shared tasklists for myself.
That is why I saw it while explaining TDL to a user that use shared tasklists on a daily basis.
While doing this, I have been catch with GUI freezes bc of automatic check in.
2-3 seconds of GUI freeze is long when you are focused on it, and waiting for a reaction.

Patrice

“Everything should be made as simple as possible, but no simpler.” Albert Einstein

I could envisage this as an exporter plugin that would output a tasklist to the appropriate XML format, and then transform it with such a stylesheet into a SVG file.

Did you know that stylesheets can also be used to output XML? Meaning that initially you could experiment without me needing to change any code and then when you have something working I could turn it into an exporter.

I can only imagine that the task title has somehow had a carriage return embedded in it as part of the editing process, something I can certainly prevent, but I'd prefer not to fix this on a wider scale because of the performance implications.

ps. I suggest you edit the post title to include 'stylesheet' so that the 'right' people read it.

I can only imagine that the task title has somehow had a carriage return embedded in it as part of the editing process,...

Yes - confirmed. In the title there was an invisible carriage return: "Name/Crlf/Blank/Date". TDL displayed it as "one line", but it was stored (and of course analysed by XSL) as "two liner with CRLF".

The export of files via XSL-stylesheet has "HTML" as default for the new created files. In 99% that's correct, but e.g. "Time_Spent_xls_eng.xsl" creates a CSV-formatted output. Is there a way to handle the file-extension for output in relation to the created file format?

XSL processors are unaware of the format of the output. The XSL renders data and the user or code that runs the processor (TDL in this case) determines the file name and extension based on "knowledge" of what the file is. But I don't think TDL would have a way by default to "know" the output format.
I would approach this in one of two ways:

With ToDoList: XSL files would need to include a recognizable comment near the top, like:

<!-- Extension: CSV -->

With that, TDL can scan a selected .XSL and change the extension prior to the run.

Or, with my Nebula UDT (see my sig) you can generate the output and then with a single click on a toolbar button you can rename the file. I think think this is the easiest option, and thus, would be my preferred approach.
If you can think of a way that the UDT can be enhanced for this specific use case, I'll consider it.

Yes, that is the "technical" solution. But what I want to say is: "How does the user knows what he has to select?" He sees 10 stylesheets, 9 make HTML, 1 makes CSV ....
A workaround could be the name of the stylesheet, which could be like "TimeReport_CSV.xsl". But that's only a info for a user who understands the workaround