« November 2005 | Main | January 2006 »

December 15, 2005

Creating Useful Temporary Macros

In classic text editors such as Emacs and Vi it is a very simple operation to create a temporary macro to repeat complex editing sequences.

Remarkably, in Visual Studio 2005 it is still relatively hard to do this successfully. The general problem is that there are many commands which don’t record and playback accurately.

The key operation in creating many useful temporary macros is being able to use a variety of commands to locate the beginning and end of a selection. Holding down the shift key while using simple movement commands extends a selection but there’s no way to extend the selection when using Incremental Search (Ctrl-I) or regular Find (Ctrl-F). In these situations, the classic solution is to position a mark at the beginning of the selection, move to the end, and then set a selection between the mark and the active position.

Here are some simple macros that set a mark based on the current active position and set the selection range from that mark to the active position.

    Dim markPoint As EnvDTE.EditPoint

 

    Sub Mark()

        markPoint = DTE.ActiveDocument.Selection.ActivePoint.CreateEditPoint

    End Sub

 

    Sub SelectToMark()

        Dim selection As EnvDTE.TextSelection

        Dim activePoint As EnvDTE.EditPoint

        selection = DTE.ActiveDocument.Selection

        activePoint = selection.ActivePoint.CreateEditPoint

 

        selection.MoveToPoint(markPoint)

        selection.MoveToPoint(activePoint, True)

 

    End Sub

By assigning keyboard shortcuts to these two commands it is possible to create complex temporary macros that don’t require editing and which work on the first try.

There are a number of existing commands in the IDE which hint at this functionality but for which I haven’t been able to find a complete working scenario. The commands and the open issues are:

Command

Issues

Edit.SelectToLastGoBack

Haven’t found a means of reliably knowing when a “GoBack” point is being set.
Doesn’t record and playback accurately.

Edit.SwapAnchor

Haven’t found a SetAnchor command.

Edit.EmacsSetMark

Haven’t found an Edit.EmacsSelectToMark command.

 

 

December 06, 2005

Event registration details in C#

What happens if you add a delegate with an event more than once? The delegate will be invoked as many times as it is registered when the event “occurs”.

What happens if you remove a delegate from an event after adding it more than once? One registration is removed at a time.

What happens if you remove a delegate from an event if it has never been registered or if it has already been removed? Nothing.

Since delegates are equal so long as the wrapped methods are the same, it is not necessary to save the actual delegate instance that was added if you intend to remove it later.

 

        public class C1 {

            public event EventHandler E1;

            public void InvokeE1() {

                if (E1 != null) E1(this, new EventArgs());

            }

        }

 

        int invocations = 0;

 

        public void h(object sender, EventArgs args) {

            invocations++;

        }

 

        [TestMethod]

        public void TestRepeatedEventHandlerAddRemove1() {

            C1 c1 = new C1();

            c1.E1 += new EventHandler(h);

            Assert.AreEqual(0, invocations);

            c1.InvokeE1();

            Assert.AreEqual(1, invocations);

 

            // Verify that adding an event handler is invoked as many times as it is added.

            c1.E1 += new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(3, invocations);

 

            // Verify that removing an event handler removes only one copy at a time.

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

 

            // Verify that removing a non-existant event handler does nothing.

            c1.E1 -= new EventHandler(h);

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

        }

 

        [TestMethod]

        public void TestRepeatedEventHandlerAddRemove2() {

            // Verify that it doesn't matter whether the identical delegate instance is

            // used instead of just an "equal" delegate.

            C1 c1 = new C1();

            EventHandler eh = new EventHandler(h);

            c1.E1 += eh;

            Assert.AreEqual(0, invocations);

            c1.InvokeE1();

            Assert.AreEqual(1, invocations);

            c1.E1 += eh;

            c1.InvokeE1();

            Assert.AreEqual(3, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

            c1.E1 -= eh;

            c1.InvokeE1();

            Assert.AreEqual(4, invocations);

        }

 

December 03, 2005

All Rows in Talisman WDS Table Aren’t the Same

A few hours wasted learning this detail, maybe it will help someone out there…

When configuring WDS to work between two LinkSys WRT54G routers with Sveasoft’s Talisman/Basic 1.1 firmware, don’t assume that any row in the WDS table will do, always use the first row.

I have two wired “base-station” WRT54G’s and one that wants to roam around wirelessly. I initially configured the WDS table on the roaming router to have two rows populated to make it easy to switch between enabling (LAN mode) one or the other, depending on the current location of the roaming router. The link worked consistently and flawlessly with the router listed in the first row, but never with the router listed in the second.

Finally I eliminated enough other possibilities to suspect the table row, moved the first row data to the third row and disabled it for record keeping, and copied the second row to the first row and enabled it there (second row disabled). Surprise, surprise, now it works…

There’s apparently more magic going on in the WDS table than I can make out from the documentation or copious amounts of configuration discussion threads out there…

For reference, here are the link I used for basic setup info:

How-To: WDS as a Transparent Wireless Bridge