Wednesday, 3 June 2009

SelectedIndexChanged and again! and again! and again!

If you have a ListView, and it has MultiSelect enabled and you select a row, then scroll down, say, 100 rows, and shift-click, how many SelectedIndexChanged events do you expect? How many do you get? 100, of course, one for every row that had a selection change. You get no special EventArg to let you know this is one of a set, or how many you might expect. As far as the eventing is concerned each highlighted row is wonderous occasion in its own right.

I've got a ListView that shows TFS changesets, when I click on one (in the middle in this example) another list populates that shows all the files in that changeset (nifty, eh?). When I shift-click on another row way down the list, I want to see all the files in that set of changesets. Using my trusted SelectedIndexChanged to let me know the selection has changed, I get oodles of lovely events and can refresh my list of files. If I shift-click right up near the top of the list I get an event for each of the rows being de-selected on one for each of the new rows being selected.

This is not what I want. Now I know that I can use each event to add/remove files from my other list, but because I'm merging data where the same file is in two changesets (to get a combined list of change types), this gets complicated. All I want is one event to tell me that SomeSelectionIndicesChanged. Then I can clear the file list, and re-populate it.

Here is some code to do just that. I don't like the gratuitous use of threads like this, but in WinForm apps, you rarely have more than one instance of the app, and users don't normally do complex multi-row selections on several ListViews at once, so there is little danger of weird concurrency issues. All it does is every time a SelectedIndexChanged is raised, it waits 1/10th second and then raises a new AfterMultiSelect event, if another SelectedIndexChanged occurs in that time, it stops and starts waiting again. If a SelectedIndexChanged occurs after the 1/10 second, but before the AfterMultiSelect has finished being handled, that doesn't matter because it's being handled back on the controls own thread.

Anyway, the code:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Project.ControlManagers
public class ListManager
private ListView listView;
private delegate void SelectionChange();

public event EventHandler AfterMultiSelection;

public ListController(ListView listView)
this.listView = listView;
// listen for all the change events
listView.SelectedIndexChanged += new EventHandler(listView_SelectedIndexChanged);

Thread t = null;
void listView_SelectedIndexChanged(object sender, EventArgs e)
ThreadStart ts = new ThreadStart(QueueSelectionChange);
// if we already had a thread, kill it
if (t != null)

// start a new thread to process the event
t = new Thread(ts);

void QueueSelectionChange()
// have a nap. in this time if another SelectedIndexChanged event is fired this thread will
// be aborted and nothing more will happen.
// invoke the call to raise the new event on the ListViews thread, not this one
// otherwise things will go awry
SelectionChange sc = ProcessSelectionChange;
t = null;

void ProcessSelectionChange()
// if anyone is listening raise the AfterMultiSelection event
if (AfterMultiSelection != null)
AfterMultiSelection(listView, new EventArgs());

I wouldn't be at all surprised if someone didn't have a good reason not to do this. The same technique can be used to do something after a user has stopped scrolling or resizing something if the work you want to do post scroll/resize is rather expensive and looks crappy if it happens on every scroll/resize event.

Footnote: This is too slow for hundreds of rows, but seems fine for a couple of dozen or so.

Monday, 13 April 2009

TF10130: '...' is a reserved name and may not be included in a path.

I was just testing some of Lizard when I got this error:
TF10130: 'Con' is a reserved name and may not be included in a path.
from this line:
string serverItem = workspace1.TryGetServerItemForLocalItem(localItem);

Where localItem = "c:\...\Con"

Googling the error I found that other people had found that Com1 is reserved, but there is no obvious list of reserved names. MS documentation on TF error messages isn't much use either (

Anyone know anything more about reserved names?

Thursday, 8 January 2009

Happy Birthday LizardTF!

I just realised that yesterday was the birthday of this blog and in two days it's a year since the first release (that was 0.1.3, I'm not sure what happened to 0.1.0,1,2)

It feels like so much longer!

Thanks to everyone who has supported Lizard though comments and suggestions, please keep it up,



Merging and Rollbacks

Recent work has been now been focussed on finishing of the functions that I've been planning from the beginning, that got left behind for the extension re-write.

No new builds yet, but the source is coming along. I've had to spend some time re-familiarising myself with the folder diff engine, the gnu diff integration and the scintilla.Net controls. Finally I cleared up some rather tatty code behind the 3-way diff visualiser. This is neatly laid out in a set of classes under LizardDiff.Diff3Docs, one class per view, instead of a big single mess.

New features are:
  • Uncontrolled Changes in Lizard Review - see changes to files not checked out, new files added to TFS controlled folders but not 'added' and files deleted from the file system but not from TFS. This was a popular request.
  • Create Patch from the Folder Compare screen; you can build/save a gnu unified diff based patch file from the difference found by the comparer.
  • Proper (and improved) version browsing from the Folder Compare screen
  • Rename Tracking when comparing file system to TFS or TFS to TFS when 'to' and 'from' have the same repository path. This is done be parsing the changesets between 'to' and 'from' for renames. These are displayed in the output.
  • 2 Way Diff now shows numbers of Inserts, Changes and Deletions.

The next step is to manage applying the generated patch file to another folder/branch and list updated files and conflicts, and allow conflict resolution through the diff/merge tool. This be the 'Lizard Merge' tool, and will allow merging across any folders, 2nd/3rd, etc generation branches as well as strict TFS 1st generation branches. This will also become the 'Lizard Rollback' tool by taking the difference between a higher and lower change set (rather than lower to higher) and applying to the higher version.

All the current work is being checked into the .Net 2.0 branch, and will be merged into the 3.5 branch (using Lizard Merge) prior to the next.

I'll have to check my notes, but I think that will make LizardTF feature complete and ready for Beta!