Thursday, August 21, 2008

One of my favorite .NET tools is about to transition in an unknown way.

"After more than eight years of working on .NET Reflector, I have decided it is time to move on and explore some new opportunities."

So begins the email I just received from Lutz Roeder, the man behind Reflector, an almost scarily powerful tool for .NET development.

Eight years? Wow. Just doesn't seem that long. But I can completely understand the desire to move on.

Unfotunately, the following passage gives a little clue as to what might be next.

"Red Gate will continue to provide the free community version and is looking for your feedback and ideas for future versions."

Ah... So there will be a community version and then something else. The thing is, Reflector seems to be one of those tools that's incredibly useful, to an incredibly small audience. And almost useless to everyone else. A bit like the SysInternals tools I've mentioned before. So successfully "productizing" it might be a pretty good challenge.

The thing is, even for me, and even though Reflector has been quite useful, I'm not sure I'd have paid for it, to be brutally honest.

But I have to applaud Mr. Roeder for what he's accomplished and congratulate him in moving forward.

posted on Thursday, August 21, 2008 7:13:27 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 14, 2008

We recently got into a discussion where I work about the best way to explain to a user why a control is disabled.

One person argued that it made sense to leave the "disabled" controls enabled, and, when a user tried to click/use the control, pop up a messagebox explaining why it won't work in this instance.

To me, that seemed to go against everything I'd ever learned about UI design and controls, namely, if a control is disabled, it ought to look disabled on screen and it shouldn't do anything if you poke at it.

Still, I'd had plenty of experience myself with apps where controls were disabled and I had no idea why they were disabled, much less how to go about getting that functionality enabled. That can certainly make for a frustrating time.

Then someone suggested a tooltip or a status bar message. If you tried to click the disabled control, or hovered your mouse over it, you'd get a little, innocuous message somewhere telling you why that control was disabled.

Awesome idea!

Only one problem.

Tooltips don't work for disabled controls. Actually, they do for menus, toolbars and likely a few others, but that's another story.

And you don't get MouseOver events on disabled controls.

Sigh.

Well, I couldn't just walk away from this.

A quick google turned up a few bits:

  • There was this from Roy Auchterlounie, but it's MFC.
  • But then there was this nugget from a post by Linda Lui, apparently with MS Support. It's in C#, but it's relatively easy to translate to VB.

The only thing was, Lui's solution was not exactly what I'd call an encapsulated solution.  As Scott Hanselman would say, dropping snippets of code like this all over my forms just doesn't have a wonderfully fragrant code smell.

I'd messed around with Control Extenders under ASP.NET some time ago, and this seemed like the perfect excuse to try it out on a good ol' WinForms app.

A little refactoring later, and I've ended up with the "Disabled Tool Tip" Extender Control. It directly inherits from the out-of-the-box tooltip in VS2008. As a result, there's not a lot of code here. Also, it should work with VS2005, but I'm not guaranteeing as much.

Add this class to your project, recompile, then drop one onto a form.

Zip! Boom! Pow! Every control on your form should now have a "ToolTip on DisabledToolTip" property. You simply set this new property to the tooltip you want to show when the control is disabled and you're done.

image

(my test rig, she is much fine, no?\:\-\) )

A few notes about this class.

One significant issue I ran into immediately, was how do you retrieve a reference to the containing form if you're a component sited on that form. All the obvious stuff didn't work. There's gotta be a more straightforward way to do it, but I failed to find it, at least with respect to a Component type control (one of those that isn't actually sited ON the form, but rather in that little area at the bottom of the designer).

I ended up caching an instance of some control during the SetToolTip method, since this method is called during the form initialization by any control on the form that had a tooltip set for it via the designer.

   Public Shadows Sub SetToolTip(ByVal control As Control, ByVal caption As String)
      MyBase.SetToolTip(control, caption)

      '---- if we don't have the parent form yet...
      If rParentForm Is Nothing Then
         '---- attempt to get it from the control
         rParentForm = control.FindForm
         '---- if that doesn't work
         If rParentForm Is Nothing Then
            '---- cache the control for use a little later
            rControl = control
         End If
      End If
   End Sub

But, you can't use the FindForm method here, necessarily, because if the form is still being initialized, you'll get back nothing.

So, I ended up implementing the ISupportInitialize interface, and, during the EndInit method, if I have a cached control reference, I use it at this point to retrieve the parent form via FindForm.

   Public Sub EndInit() Implements ISupportInitialize.EndInit
      '---- if we weren't able to retrieve the form from the control
      '     before, we should be able to now
      If rControl IsNot Nothing Then
         rParentForm = rControl.FindForm
      End If
   End Sub

Roundabout, yes, but it seems to work in a very stable way, and it means I don't have to resort to MFC style subclassing and the like. I can just monitor events on the parent form via a simple WithEvents variable reference.

Anyway, the full code for the class is here. It's short enough that I'm not going to bother with a ZIP file at this point.

And finally, as with any code you pick up off the net, I'm making no guarantees of any sort. If it works for you, great. If not. Well, I'll certainly do my best to help if you let me know. It works for me, but it is necessarily full on, battle tested, bulletproof stuff? Uh. No.

Enjoy! If you see any improvements to be made, please share!

And please, if you post it elsewhere, give me (and Linda Lui) proper credit!

Imports System.ComponentModel

''' <summary>
''' Custom ToolTip Component that is based on a normal tooltip component but tracks tips 
''' for disabled controls
''' Note that the because this is a separate extender, all the controls on a form
''' can have an "Enabled" tip (as normal) AND a "disabled" tip.
''' 
''' By Darin Higgins 2008
''' Based on a code example by Linda Lui (MSFT)
'''
''' </summary>
''' <remarks></remarks>
''' <editHistory></editHistory>
Public Class DisabledToolTip
   Inherits ToolTip
   Implements ISupportInitialize

   '---- hold onto a reference to the host form
   '     to monitor the mousemove
   Private WithEvents rParentForm As System.Windows.Forms.Form

   Private _rbActive As Boolean = True
   ''' <summary>
   ''' Active for the Disabled ToolTip has a slightly different meaning
   ''' than "Active" for a regular tooltip
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   <DefaultValue(True)> _
   Public Shadows Property Active() As Boolean
      Get
         Return _rbActive
      End Get
      Set(ByVal value As Boolean)
         If _rbActive <> value Then
            _rbActive = value
         End If
      End Set
   End Property



   '---- hold on to a control temporarily while we wait for things to 
   '     settle
   Private rControl As Control

   ''' <summary>
   ''' Shadow the settooltip function so we can intercept and save a control
   ''' reference. NOTE: the form MIGHT not be setup yet, so the control
   ''' might not know what it's parent is yet, so we cache the the first control
   ''' we get, and use it later, if necessary
   ''' </summary>
   ''' <param name="control"></param>
   ''' <param name="caption"></param>
   ''' <remarks></remarks>
   Public Shadows Sub SetToolTip(ByVal control As Control, ByVal caption As String)
      MyBase.SetToolTip(control, caption)

      '---- if we don't have the parent form yet...
      If rParentForm Is Nothing Then
         '---- attempt to get it from the control
         rParentForm = control.FindForm
         '---- if that doesn't work
         If rParentForm Is Nothing Then
            '---- cache the control for use a little later
            rControl = control
         End If
      End If
   End Sub


   Public Sub BeginInit() Implements ISupportInitialize.BeginInit
      '---- Our base tooltip is disabled by default
      '     because we don't want to show disabled tooltips when
      '     a control is NOT disabled!
      MyBase.Active = False
   End Sub


   ''' <summary>
   ''' Supports end of initialization phase tasks for this control
   ''' </summary>
   ''' <remarks></remarks>
   Public Sub EndInit() Implements ISupportInitialize.EndInit
      '---- if we weren't able to retrieve the form from the control
      '     before, we should be able to now
      If rControl IsNot Nothing Then
         rParentForm = rControl.FindForm
      End If
   End Sub


   Public Sub New(ByVal IContainer As IContainer)
      MyBase.New(IContainer)
   End Sub


   ''' <summary>
   ''' Monitor the MouseMove event on the host form
   ''' If we see it move over a disabled control
   ''' Check for a tooltip and show it
   ''' If the cursor moved off the control we're displaying
   ''' a tip for, hide the tip.
   ''' </summary>
   ''' <param name="sender"></param>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Private Sub rParentForm_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles rParentForm.MouseMove
      Static ctrlWithToolTip As Control = Nothing

      Dim ctrl = rParentForm.GetChildAtPoint(e.Location)

      If ctrl IsNot Nothing Then
         If Not ctrl.Enabled Then
            If ctrlWithToolTip IsNot Nothing Then
               If ctrl IsNot ctrlWithToolTip Then
                  '---- if we're not over the control we last showed
                  '     a tip for, close down the tip
                  Me.Hide(ctrlWithToolTip)
                  ctrlWithToolTip = Nothing
                  MyBase.Active = False
               End If
            End If
            If ctrlWithToolTip Is Nothing Then
               Dim tipstring = Me.GetToolTip(ctrl)
               If Len(tipstring) And Me.Active Then
                  '---- only enable the base tooltip if we're going to show one
                  MyBase.Active = True
                  Me.Show(tipstring, ctrl, ctrl.Width / 2, ctrl.Height / 2)
                  ctrlWithToolTip = ctrl
               End If
            End If

         ElseIf ctrlWithToolTip IsNot Nothing Then
            '---- if we're over an enabled control
            '     the tip doesn't apply anymore
            Me.Hide(ctrlWithToolTip)
            ctrlWithToolTip = Nothing
            MyBase.Active = False
         End If
      ElseIf ctrlWithToolTip IsNot Nothing Then
         '---- if we're not over a control at all, but we've got a
         '     tip showing, hide it, it's no longer applicable
         Me.Hide(ctrlWithToolTip)
         ctrlWithToolTip = Nothing
         MyBase.Active = False
      End If
   End Sub
End Class
posted on Thursday, August 14, 2008 9:45:42 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, August 09, 2008

I blogged about fonts quite a while back here.

But I thought I'd add a reference to a new font I just stumbled across by DamienG on his blog, called Envy Code R.

I especially like his italics as bold trick that enables you to have Italics comments in Visual Studio even though VS only has an option for bold, even in VS2008.

image Envy Code R Pretty good, but a bit compressed, note the small M. To me, the verticals are so close together, they blur a bit. It's also a tad taller than Consolas.

Even if you don't care for the font, his VS color schemes, particularly Humane, are definitely worth a try.

Very nice.

Consolas with his bold-italics trick would be a very nice VS2008 font indeed. Hmmm.

posted on Saturday, August 09, 2008 12:45:06 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

image I've got a Uniden TX-860 Two Line expandable 5.8 ghz phone system at the house that's great for a home office setup. The battery life is excellent, sound is as clear as a wired set, it has distinctive rings (so office calls don't get confused with personal calls), and a headset jack, plus it's expandable to 8 or 10 phones.

The only downside is that it's been very  difficult to find a headset that works well with it.

I've gone through literally a dozen plus headsets and all of them (except the one that came with the phone) make my voice to the other party sound like I'm yelling from across a football field.

But the one that came with the phone... Well...

image

No volume, no mute, big, clunky. It works, but that's about all you can say for it.

I even checked out the highly-rated TheBoom and the Etymotic sets, but they weren't any better.

I actually tried several Plantronics sets from time to time, but none of them gave a good voice level either. Then I ran into the Plantronics MX500C, one that I hadn't tried before. It was cheap so I figured I'd give it a shot.

image

Awesome little gizmo. My voice sounds loud and clear to my callee, it's got a volume control (mic and earpiece) and a mute, plus it's lightweight and comfortable to boot. For 19.99 at Fry's, it's the best companion to my phones I've found yet.

posted on Saturday, August 09, 2008 9:33:52 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 07, 2008

Way back in the wild west days of VB6, if you wanted a long running utility to ALSO pop up a progress bar, be cancelable, and be nicely encapsulated in a reusable class, you didn't have a lot of options.

The first was to actually use VB6's multithreading support via a thread per object ActiveX exe. It worked, but it wasn't a trivial exercise.

One trick I used on a number of occasions was the modal form callback.

Essentially, it works like this.

  • You call into a class to "start" the long running process.
  • The class opens an associated progress form, and passes itself to the form.
  • The Progress form stores a WithEvents reference to the class, so it can be notified of progress as the class does its work.
  • Then, the form shows itself modally, and in the Activate event, calls back into the class to actually start the processing.
  • The class then starts it's long running processing, raising progress events as appropriate, and yielding via DOEVENTS at appropriate times.

It's a tad complex, to be sure, but certainly less complex than some of the alternatives and it still yields a nicely responsive UI that the user can easily cancel out of.

Fast forward to today. I had a similar requirement for a little VB.NET utility, so I figured, what the hey, might as well use that old trick.

After a bit of coding, I run the app and am rewarded with this:

image

Huh? I wasn't "Attempting to call into managed code without transitioning out first." Heck, there wasn't even any API calls in this mix, Much less "Low Level extensibility points"!

Of course, the VS help for this message proved utterly useless, so I start googling.

Equally useless. Surely, I'm not the only guy out there to get this treat, right?

Anyway, eventually, I decided that maybe, just maybe, something about the reentrancy into the class from a modal dialog was causing the problem, though I still don't have concrete proof of that.

I reworked the class to cooperate with the BackgroundWorker component (a nifty little bit of new .NET goodness), and everything's back on track.

The only bit of nastiness left is that my background worker class raises several events, which my progress form monitors and presents, but doing anything within those events directly resulted in an exception about accessing UI objects from a thread other than the one they were created on.

The VS Help was helpful for this though, and adding code like this to the event handlers...

   Delegate Sub ScanningFileCallback(ByVal FileName As String, ByVal Count As Integer)
   Private WithEvents MyClass As MyClass

   Private Sub MyClass_ScanningFile(ByVal FileName As String, ByVal Count As Integer) Handles MyClass.ScanningFile
      If Me.prgProgress.InvokeRequired Then
         Dim d As New ScanningFileCallback(AddressOf MyClass_ScanningFile)
         Me.Invoke(d, New Object() {FileName, Count})
      Else
         me.prgProgress.Maximum = MyClass.FileCount
         me.prgProgress.Value = Count
      End If
   End Sub

worked around that problem nicely, although it'd be nice if the background worker class could somehow handle this transition itself. Hmmm....

posted on Thursday, August 07, 2008 10:11:18 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Sunday, August 03, 2008

Here's an interesting one for you.

I was playing with the SpeechSynthesis namespace recently when I ran off into a coding ditch. You know, one of those times when things are moving along nicely, and you swerve to avoid some minor nit, only to find yourself stuck upside down with the wheels half sunk in muck, trying to claw yourself out with an ICE and log files...

Anyway, I had this:

Public Class Connect
   Private WithEvents rSynth As New SpeechSynthesizer
   ...
And a little later in the class, I was using it like so...
      If rSynth.State <> SynthesizerState.Speaking Then
         rSynth.SpeakAsync(Speak)
      End If
Simple enough.

But it didn't work. State was always coming back Ready, not Speaking, so I was getting a rather laughable stuttering intro to tracks as they were playing... "Now...Now...Now...Playing....Playing....Swerve...Playing....Swerve..."

(Sorry, the app in question is a little MP3 player plug in to announce tracks and allow you to navigate through the player using only a remote and no screen.)

I tried several different approaches but none worked. I could never get the proper state of the synthesizer to be reflected.

Then, on a lark, I remove the New from the SpeechSynthesizer declaration line:

Public Class Connect
   Private WithEvents rSynth As SpeechSynthesizer
   ...

And just assigned a new instance to the rSynth var from Sub New.

It worked!? Huh?

What was going on here?

I'm still not 100% sure, and the documentation on the NEW modifier for a DIM statement certainly doesn't help to clarify things. From what I can tell, it looks like each reference to the regional var rSynth ends up retrieving a new SpeechSynthesizer object. Definitely not the behavior I was expecting. And not behavior I've seen before given this use of the New clause.

In reality, I've had far too many lessons about Dim x as New from VB6 to normally use it like this anyway. I guess I thought VB was passed all that now in .NET, but I'm slowing learning that VB.NET is every bit as quirky as VB6, just differently so.

Ah, progress.

posted on Sunday, August 03, 2008 8:48:12 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, July 24, 2008

I've always been a big fan of the MZTools addin for VB6, so it was a little disappointing, if not understandable, that for his .NET foray, he's wanting to get paid a little something for it.

Not that that's bad, I just haven't been able to commit to MZTools for .NET just yet.

In the meantime, I stumbled across a very slick little VS addin called DPack.

It's actually a small set of different addins. Doesn't look like you get the source, which is unfortunate, but the features are pretty slick, plus they work in VB, C++ and even C# and Ruby.

The Code Browser alone is worth the download. With one keystroke, you get a window like this:

image

It's nicely sorted, browsable, and with one other click, takes you directly to the given code element. Even better, you can assign direct keystrokes to filter only methods, classes, properties, etc. By default, ALT-M shows just the methods in the current file, for instance. Reminds me of the old F2 (Function list) key that used to be in older VB's. I missed that key<sob>.

Also particularly nice is the "Surround Selection With..." feature. You can't customize the surrounding text but they give you the most common items (surround with TRY CATCH, FOR NEXT, DO WHILE, REGION, etc, etc.), so that's not too bad.

It seems very quick and stable. And the best part is, it's completely free!

posted on Thursday, July 24, 2008 9:46:10 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, July 18, 2008

One thing that has definitely taken some getting used to (for me anyway) in VB.NET is the whole concept of streams.

I like the idea, generally, but I was working through some code to stream debugging info into a resource in an already compiled EXE file when I ran into a problem with decompressing the resource data.

It just wouldn't decompress properly.

After backtracking and puzzling over it, I discovered that the problem was actually in the compression phase.

   Private Function pCompressStream(ByVal UncompressedStream As MemoryStream) As MemoryStream
      Dim CompressedStream As New System.IO.MemoryStream()
      Using GZip As New System.IO.Compression.GZipStream(CompressedStream, System.IO.Compression.CompressionMode.Compress)
         GZip.Write(UncompressedStream.ToArray, 0, UncompressedStream.Length)
      End Using
      Return CompressedStream
   End Function

Note the Dim GZip line? If you look at the overloads, there's a possible 3'rd option, LeaveOpen. It defaults to false.

From what I can tell, if you connect a GZipStream up to a FileStream, there's no need for this parameter. When the GZip closes, the file is closed and on you go.

But, with MemoryStreams, it's quite unlikely that you'd want to close the resulting output memory stream after zipping data into it.

But that's exactly what happens if you don't set LeaveOpen to True.

What's worse. You need to Close the GZipStream before reading that MemoryStream(either by using a USING block as I have, or by calling Close directly). If you don't, garbage collection might not happen for some time and the final data in the internal GZip buffers won't get written to your memory stream, resulting in only partial compressed data, which definitely won't decompress properly!

All this is likely old hat to grizzled .NET heads, but if you're like me and coming from an extensive VB6/assembler background, it can certainly causing some headscratching for a bit.

posted on Friday, July 18, 2008 10:09:05 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, June 27, 2008

Just stumbled across what has to be the hands-down coolest workstation yet.

It looks like a cross between the swiveling chair in the Millennium Falcon and the long abandoned gunner's chair from "Alien". It's called the Emperor from NovelQuest.

image

That monitor arm thing? It actually electrically lifts those monitors out of the way to allow you in and out. Not sure how practical that is, since it'd require, what, a 20ft ceiling in your office.

No idea how much. It doesn't go on sale till July '08. But seriously. Wow.

posted on Friday, June 27, 2008 8:17:34 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

image Like a lot of people out there, I've been bitten by the Vista "Slow Copy/Delete" bug many many times myself.

I finally decided I'd had enough today and when looking for a solution.

I came across a couple of posts discussing a hotfix available from Microsoft, but it's apparently not that easy to get a hold of, and its a questionable fix at that.

Then I happened across a short post in a SlashDot discussion that mentioned turning off thumbnails in the Explorer view.

I've never heard of that one, so I gave it a quick shot.

First, open the Folder Options:

image

Then, just make sure the first option "Always Show Icons, never thumbnails" is checked:

image

Surprisingly, this does seem to a have a pretty dramatic effect on the copying and deleting processes, as well as noticeably speeding up the time it takes to get from a right-click to actually seeing the right click context menu in Explorer. It's still not instant like it should be, but it's better than before.

I'll keep playing with it and post if I discover any other interesting bits about this seemingly easy workaround.

posted on Friday, June 27, 2008 8:16:22 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions;