Monthly Archives: August 2008

What’s next with Reflector

0
Filed under .NET, Utilities

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.

Tooltips for Disabled Controls

1
Filed under .NET, Code Garage, VB Feng Shui

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?<g>)

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

Another VS Programming Font

0
Filed under .NET, Fonts

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.

A Great Little Headset

0
Filed under Hardware

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.

No More Simulated Multithreading

0
Filed under .NET, VB Feng Shui

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….

DIM x AS NEW SpeechSynthesizer

0
Filed under .NET, VB Feng Shui

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 past 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.