Drag&drop in a Sorted TreeView

Illustration

If you have a TreeView, chances are that you want it sorted and with a drag&drop functionality. And that is not too hard.

In order to sort items, don’t forget to assign TreeViewNodeSorter property. This requires simple IComparer, e.g.:

internal class NodeSorter : IComparer {
    public int Compare(object item1, object item2) {
        var node1 = item1 as TreeNode;
        var node2 = item2 as TreeNode;

        if (node1.ImageIndex == node2.ImageIndex) { //both are of same type
            return string.Compare(node1.Text, node2.Text, StringComparison.CurrentCultureIgnoreCase);
        } else {
            return (node1.ImageIndex == 0) ? -1 : +1;
        }
    }
}

This will ensure that “folders” (with ImageIndex==0) are sorted before files (any other value of ImageIndex). All that is left is to call Sort method when needed.

In order to support drag&drop, a bit more work is needed. Before we even start doing anything, we need to set AllowDrop=true on our TreeView. Only then we can setup events. To initiate drag we just work with ItemDrag event:

this.DoDragDrop(e.Item, DragDropEffects.Move);

In DragOver we need to check for “droppability” of each item. Rules are simple; We allow only tree nodes in; if we drop file on file, it will actually drop it in file’s folder; and don’t allow parent to be dropped into its child. This class will then either allow movement (DragDropEffects.Move) or it will deny it (DragDropEffects.None).

var fromNode = e.Data.GetData("System.Windows.Forms.TreeNode") as TreeNode;
if (fromNode == null) { return; } //not our stuff

var dropNode = tree.GetNodeAt(tree.PointToClient(new Point(e.X, e.Y)));
while ((dropNode != null) && (dropNode.ImageIndex != 0)) { //search for suitable folder
    dropNode = dropNode.Parent;
}

var noCommonParent = (fromNode.Parent != dropNode);
while (noCommonParent && (dropNode != null)) {
    if (fromNode == dropNode) { noCommonParent = false; } //to stop parent becoming a child
    dropNode = dropNode.Parent;
}

e.Effect = noCommonParent ? DragDropEffects.Move : DragDropEffects.None;

Final movement happens in DragDrop event. First part is same node discovery process we had in DragOver. After that we simply move nodes from one parent to another and we wrap all up by performing a sort.

var fromNode = e.Data.GetData("System.Windows.Forms.TreeNode") as TreeNode;
var dropNode = tree.GetNodeAt(tree.PointToClient(new Point(e.X, e.Y)));
while ((dropNode != null) && (dropNode.ImageIndex != 0)) { //search for suitable folder
    dropNode = dropNode.Parent;
}

var fromParentNodes = (fromNode.Parent != null) ? fromNode.Parent.Nodes : tree.Nodes;
fromParentNodes.Remove(fromNode);
if (dropNode == null) {
    tree.Nodes.Add(fromNode);
} else {
    dropNode.Nodes.Add(fromNode);
}

tree.Sort();
tree.SelectedNode = fromNode;

Full sample can be downloaded here.

PS: In sample code you will see that I use ImageIndex==0 to determine whether node is of folder type. In real program you would probably go with sub-classing TreeNode.

Chromecast

Illustration

Chromecast arrives in a very stylish box with a device on very top and all other stuff under. It comes with a short HDMI extension cable, USB-A to micro-USB cable, and a nice 850 mA power adapter.

First thing to note is that this device CANNOT work once you plug it into a HDMI port. You must use micro-USB on the other end to bring it power. While you do get everything you need in the box, it makes for not-so-clean look if it is visible (e.g. you have only free HDMI on side of your TV). It is not too bad if you have USB on your TV, but it gets annoying if you need to add, yet another, power brick to the back.

Yes, need for additional power is not a big surprise since HDMI allows for only 50 mA. What I am surprised about is Google forgetting to mention that. All Google’s pictures with Chromecast plugged in (including Chromecast introduction event) show device plugged in HDMI-only. Somehow every picture has one essential (and messy) cable missing. Sneaky…

Without any video running current consumption is around hefty 300 mA and definitely more than I would expect. During playback it needs about 50 mA more with occasional (short) jumps to 500 mA range. Device will get warm during use but there were no problems even after couple hours of playback so there is no major issue here.

As you plug device in, it will require short setup which is as simple as it gets. Especially so because you do everything on your computer. Most other devices (e.g. Roku) force you to enter wireless password on TV which is always annoying when there is no keyboard ready. Chromecast has successfully avoided this.

Use is equally simple. Just select the cast button in a supporting application and Chromecast will start playing content. Of course, there will be a small delay because Chromecast will have to independently visit video stream location by using its own wireless connection. Benefit of such architecture is that you save on bandwidth between devices and you can even turn off your mobile without interrupting a playback. Your glorious mobile phone becomes just a remote control.

There is an extension that enables you to cast a Chrome browser tab but I found Chromecast to be very lacking in this area. Casting a tab is unbearably slow. Any scroll operation takes ages and videos are barely a slideshow. And you can forget about sound. Only thing that works from PC is actually a YouTube.

All this brings us to most serious downfall - application support for Chromecast is lacking at best. You can really only count on Netflix, Google Play, and YouTube. If you need something else, though luck. There might be some improvement in the future but I wouldn’t hold my breath there.

Could you live without Chromecast? Definitely yes, especially if you already have your laptop connected. But at $35 this device is a steal. Assuming that you have free HDMI port and you are living in States, it is a good buy.

If you are outside of States, it is not a useful device at all. Since Google Play and Netflix are restricted to US market, all you can really do is play Youtube videos and play with Chrome tabs. Considering all the hops you need to get around in order to purchase it in the first place, it is hard to justify even thinking about it.

SVN to Mercurial

Synchronizing SVN repository to Mercurial one is deceptively easy. Convert extension is as simple as it gets:

hg convert svn://1.2.3.4/Repo Repo

Unfortunately, this method has issues with speed, especially when there is huge SVN repository on other side of slow (and unreliable) VPN connection. I had seen few hours download times per commit if everything goes well. If something fails, you are back at square zero and you need to re-sync complete commit again. And even if commits are not big, scanning time will drive you crazy.

I found that, under those circumstances, two step approach works best. First we synchronize remote SVN repository to local (SVN) one. In all instances I tried this, SVN offered superb speed (compared to Mercurial) and it hasn’t suffered from broken connections as much.

My client of choice was Visual SVN and few commands were all it took to create local copy:

> svnadmin create C:\Repo

> echo exit 0 > c:\Repo\hooks\pre-revprop-change.bat

> svnsync initialize file:///C:/Repo svn://1.2.3.4/Repo
Copied properties for revision 0.

> svnsync sync file:///C:/Repo svn://1.2.3.4/Repo
Committed revision 1.
Copied properties for revision 1.
Transmitting file data ...

After local copy is created, you can use hg convert without further trouble.

SetWindowLongPtr

[SetWindowLong](http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx) function is well known among those who want to change various properties of already created windows. However, there is a slight issue with it on 64-bit Windows - it does not work properly. Someone originally defined this function to return LONG. Unfortunately LONG is actually defined as a 32-bit integer on both 32 and 64-bit Windows. Since SetWindowLong is also intended for setting some pointer sized properties (e.g. GWL_WNDPROC) this will not do.

Fortunately Microsoft also saw the error and created new function SetWindowLongPtr which corrects declaration so it is valid for both 32 and 64-bit world. Except they didn’t.

While comment clearly says “This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Windows, use the SetWindowLongPtr function.”, this is not really the full truth. If you check its declaration in WinUser.h you will see the ugly truth - on 32-bit system, SetWindowLongPtr is a simple #define toward good old SetWindowLong. This means that statement is valid for someone working in C/C++. For all other languages, this statement is misleading at best.

Solution for C# is simple, just check whether code is running in 32-bit mode and call SetWindowLong yourself. In all other cases just call SetWindowLongPtr. Code Analysis will complain a bit about CA1901 (P/Invoke declarations should be portable) and CA1400 (P/Invoke entry points should exist) but we can safely ignore these warnings if we make sure to call correct function ourselves.

These declarations should do the trick:

public static IntPtr SetWindowLongPtr(IntPtr hWnd, Int32 nIndex, IntPtr dwNewLong) {
    if (IntPtr.Size == 4) {
        return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
    } else {
        return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
    }
}

[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "This declaration is not used on 64-bit Windows.")]
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "2", Justification = "This declaration is not used on 64-bit Windows.")]
private static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, Int32 nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
[SuppressMessage("Microsoft.Interoperability", "CA1400:PInvokeEntryPointsShouldExist", Justification = "Entry point does exist on 64-bit Windows.")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, Int32 nIndex, IntPtr dwNewLong);

Babysitting

I’ve been going to a road trip with my family over a weekend and, of course, I forgot to bring audio cable. That small piece of wire was all that was keeping me apart from having my phone output beautiful music for my kids on a car stereo. No biggie I though, car has a bluetooth, it will be a cakewalk to connect the phone.

It wasn’t.

You see, Ford in its eternal wisdom decided to forbid setting such things while car was moving. And the message clearly said: it was for my safety. I found that strange because I was sitting on a passenger seat, pretty sure that I wasn’t one holding the steering wheel. If car is moving, nobody should be allowed to do anything it seems.

And this “security features” seem to be just part of a bigger trend. Let’s just take National Highway Transportation Safety Agency recommendations for GPS functionality. For example, they suggest (V.5.b) that dynamic maps are not the best choice when it comes to visualization of travel data. Their recommendation is to have GPS update every few seconds. Yes, people looking every few seconds for updated map wondering whether what they see is a current information or they have already screwed the turn will definitely make for safer driving.

We should also not forget Transportation Secretary Ray LaHood saying that your mobile phone should be disabled while in a car. I understand that mobile phone is a distraction but thing is that there is no way to reliably detect who is driving. And why shouldn’t passenger use driver’s mobile phone to, God forbid, look up maps or even worse, speak to his Mom who called him at wrong moment?

I am quite annoyed by all this babysitting and forbidding various distracting things for no measurable effect. It restricts quite useful scenarios when you do have two persons in the car and it does not offer any real safety benefit when you don’t. Face it, idiots will stay idiots and no amount of “features” will make them a good driver. They will just think of something else to entertain them while hurling on a highway toward eventual doom.