Monday, May 01, 2006
I wanted to share an experience I had last week while working with my development team. I think I've mentioned this before, but we're the first group in my company to move to .NET 2.0. As a result, we're also the first to get to really use Visual Studio 2005. We had been working on mockups and UI designs with our users for quite a while on this project.

Finally, we were getting ready to start work on the internals. The three of us met in a conference room with a projector and I pulled up the Class Designer in Visual Studio. I've done team-coding a few times before, where one person is typing while everyone else is yelling out bugs or typos. While the process can be annoying, it also tends to produce less-buggy code. Doing the same thing with the Class Designer worked out really well. It helped us visualize how everything would tie together. We were drawing out our code, but behind the scenes, the Class Designer was also spitting out workable code!

I don't think I could use the Class Designer in all cases, but it really does force you to think about design issues and it makes for a good team development product. It also helps ease developers who aren't as comfortable with OO concepts see how inheritance is working. Give it a try and let me know how it works for you.
posted on Monday, May 01, 2006 11:36:56 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Thursday, April 27, 2006

I've really become a fan of MSN Desktop Search at work. I don't really use the toolbar at all, because I've got tabbed browsing in both Firefox and the IE7 beta, but the search features are great. I also like it better than Google's offering because the results don't pull up in a webpage. Even with all the buzz over AJAX lately, there is still a lot more interaction that can happen with a true client application.

Anyway, on to Desktop Search. It makes finding emails SO MUCH easier. The ability to use special search keywords makes it even better. For example, let's say I wanted to find all emails from me. Here's what I could do:

kind:mail from:David Mohundro

Voila! I now have all emails from me. The kind keyword lets you specify things like mail, pictures, documents, etc. If you want to get even more detailed, you can use things like the ext keyword and specify file extensions. Here's a search to find all icon files that have 'mail' in the name.

ext:ico mail

I haven't seen an all-encompassing list of all of the different keywords that you can use with Desktop Search, but there are quite a few if you'll look in the Desktop Search Help under Advanced Users.

And don't forget to use CTRL+ALT+M to get focus to your search box!

NOTE: If you're running into performance problems with it, check out this KB download. It really helped speed Desktop Search up on my PC. I couldn't work without it now.

posted on Thursday, April 27, 2006 11:47:24 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Wednesday, April 26, 2006
Never heard of PowerShell you say?

I hadn't either, at least until today. That's because it used to be called Monad. Microsoft released the RC1 of PowerShell today and it is pretty sweet. Basically, it offers scripting and CLI access to the .NET Framework. Instead of using cmd.exe, you'll be using PowerShell.exe. I haven't used it much yet, but it is really cool. Just as a quick example, try this out in PowerShell:

$var = "This is a sweet"

Now, type $var. and then hit tab.

OH WOW! Tab completion for System.String from the command line!

Check it out!
posted on Wednesday, April 26, 2006 11:44:27 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Friday, April 21, 2006
Continuing in the tradition of posts on Visual Studio WSoD's, I ran into another one today that gave me this error: "Object does not match target type."

Okay... super.

Luckily, I got some warnings with line numbers. They pointed me to a line in my designer code that looked like this:

CType(Me.MyUserControlInstance, System.ComponentModel.ISupportInitialize).BeginInit()

The other error was the same line except it was the matching EndInit call.

Similar to before, I was working with code that was in the process of being updated to the .NET 2.0 Framework. I had been working with a control that was inheriting from a .NET 1.1 control. The ISupportInitialize code had been in there for the 1.1 parent. The designer didn't mind at all until I moved the parent control over to 2.0. The solution was easy enough... just take the ISupportInitialize code out. I'm not sure if this is the recommended solution or not (I didn't Google it for too long), but it works.
posted on Friday, April 21, 2006 2:34:20 PM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Friday, April 14, 2006

Ever seen this error in Visual Studio 2005?

I've seen it all too often and it is really annoying.

However, I did find a possible fix today. I was working in a project that had multiple assembly references. One of the references was using a 1.1 assembly, so it came along for the ride. By simply removing that reference, I was able to view my form without any problems. If I tried to add the reference back, I would get the designer error again. I reworked the assembly being referenced to not use any 1.1 components and... lo and behold... the designer errors stopped.

Is this just a really random bug or is this a subtle way to push people to migrate to .NET 2.0?

posted on Friday, April 14, 2006 2:09:51 PM (Central Standard Time, UTC-06:00)  #    Comments [3]
I had some code that I was porting from .NET 1.1 to .NET 2.0 today. The code I had was checking the version of comctl32.dll to see if it was version 6 or greater to determine if the application supported visual styles or not. As I didn't write that code, I'm not sure if that is a valid way to check for visual style support in .NET 1.1.

I DO know that in .NET 2.0, that check doesn't necessarily work correctly; however, there is a new way to check that is much easier and more reliable -- Application.RenderWithVisualStyles. If you call Application.EnableVisualStyles in your Sub Main method, this boolean will be set to true. That's all you need to check!

See MSDN documentation here.
posted on Friday, April 14, 2006 10:50:11 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Thursday, April 13, 2006
Check out this article on ABC News. Or this press release from Winternals.

Apparently, a few members of Geek Squad have been using pirated versions of software from Winternals. In case you're unfamiliar with Winternals, they're the enterprise side of Sysinternals, which provides the excellent Process Explorer as well as a few other invaluable tools. Mark Russinovich, who discovered the original Sony DRM rootkit, is one of the people who established the company.

Way to go Best Buy.
posted on Thursday, April 13, 2006 12:06:37 PM (Central Standard Time, UTC-06:00)  #    Comments [0]
Mozilla Firefox 1.5.0.2 was released today. At first glance, it seems to be a lot more stable than 1.5.0.1. I had all sorts of issues with the last version randomly crashing on me. In their defense, it seemed to be a combination of some of the extensions I was using and the corporate proxy. I never had any problems at home using the same extensions.

Check it out.

(via Neowin.net Software)
posted on Thursday, April 13, 2006 11:51:05 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
I stumbled across this post by Mike Woodring today. Apparently, the UserState property from the BackgroundWorker isn't accessible from the RunWorkerCompleted event. What in the world???

Anyway, I wasn't actually looking for information on the UserState property -- I was trying to find some information on the Error property off of RunWorkerCompletedEventArgs. It gets populated when an exception is thrown in your background thread. The weird thing is, I couldn't get my code to fall in there. The debugger kept popping up on the line where I was actually throwing the exception. Believe it or not, but that is actually the intended behavior... IF you're running in the debugger. Check out this post on the MSDN forums. If you're running standalone, then the Error property will be set as expected and you can check it. I wonder if you can continue on the exception and get to the RunWorkerCompleted event. I'll have to try that.

One last potential gotcha involving using the Error property: make sure it is the FIRST thing you check in your RunWorkerCompleted event handler. See this post on the MSDN forums for details there. According to Mike Woodring's post referenced above, if you access the Result property off of the RunWorkerCompletedEventArgs parameter and the Error property is populated or the Cancelled property is true, you'll get an invalid operation exception.

So now you know... and knowing is half the battle!
posted on Thursday, April 13, 2006 11:48:15 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Tuesday, April 11, 2006

MSBuild is a great tool. If you're not familiar with it, it is Microsoft's new build engine which was released with .NET 2.0. Visual Studio 2005 uses it behind the scenes. If you'd like to see it in action, pull up a VS2005 Command Prompt and type "msbuild YourSolution.sln" and watch the magic. It provides a much faster way of recompiling solutions and projects than reopening Visual Studio.

MSBuild runs off XML files. If you'd like to see one, just open up one of your vbproj or csproj files. Visual Studio projects default to the MSBuild format. Unfortunately, the solution files still aren't in an XML format. Because it uses XML, you can extend a build to do any number of tasks. I'll walk you through a very simple example.

My task is a Replace task that simple takes an input string (likely a file path or assembly name), an old value, and a new value and returns a value with all old values replaced with new values. It works exactly like the Replace method off of String objects.

First off, create a new Class Library project in Visual Studio. You'll need to add a reference to Microsoft.Build.Framework and to Microsoft.Build.Utilities. Next, for your class, inherit from Microsoft.Build.Utilities.Task. You'll be forced to override an Execute method. That's really all it takes to get a custom task. Everything else is driven off of public properties that are described by specific MSBuild attributes like "Required" or "Output".

Here's the source for my Replace task:

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using System;
using System.Collections.Generic;
using System.Text;

namespace Tasks
{
    
/// <summary>
    /// Custom MSBuild task to perform String replacement. Primarily used for
    
/// Namespace to directory replacement (DTC.NRA.App -> DTC\NRA\App).
    
/// </summary>
    public class Replace : Task
    {
        
string _input;
        [
Required]
        
public string Input
        {
            
get { return _input; }
            
set { _input = value; }
        }

        
string _oldValue;
        [
Required]
        
public string OldValue
        {
            
get { return _oldValue; }
            
set { _oldValue = value; }
        }

        
string _newValue;
        [
Required]
        
public string NewValue
        {
            
get { return _newValue; }
            
set { _newValue = value; }
        }

        
string _results;
        [
Output]
        
public string Results
        {
            
get { return _results; }
            
set { _results = value; }
        }

        
public override bool Execute()
        {
            
bool success = true;
            
try
            {
                Results = Input.Replace(OldValue, NewValue);
            }
            
catch (Exception e)
            {
                Log.LogErrorFromException(e);
            }
            
return success;
        }
    }
}

The XML below is how I'm currently using this task.

<UsingTask TaskName="Tasks.Replace" AssemblyFile="C:\Development\References\MSBuildTasks.dll" />
<
PropertyGroup>
  <
RootDirectory>C:\Development\Build\</RootDirectory>
</
PropertyGroup>
<
Target Name="AfterBuild">
  <
Replace Input="$(RootNamespace)" OldValue="." NewValue="\">
    <
Output TaskParameter="Results" PropertyName="NamespaceDirectories" />
  </
Replace>
  <
CreateItem Include="$(OutputPath)\**\*.*">
    <
Output TaskParameter="Include" ItemName="FilesToArchive" />
  </
CreateItem>
  <
Copy SourceFiles="@(FilesToArchive)" DestinationFolder="$(RootDirectory)$(NamespaceDirectories)\%(FilesToArchive.RecursiveDir)" />
</
Target>

As you can see, I've got the UsingTask which references the assembly I built. Then you can use the Replace task like any other provided task. The above exactly can be copied into a Visual Studio project and it will copy the output files from your build into the RootDirectory you specify with the root namespace making up the folders beneath it (i.e. give the namespace System.Windows.Forms, this will copy your compiled assemblies to c:\Development\Build\System\Windows\Forms\*).

posted on Tuesday, April 11, 2006 11:43:25 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Friday, March 31, 2006

Yesterday afternoon, I posted on ShellExecuteEx and executing files with unknown file extensions. I was really confused, though, because my feed just wouldn't show up in my aggregator (RSS Bandit). I knew it wasn't my reader, though, because I had navigated to my feed's address (http://feeds.feedburner.com/DavidMohundro) and it wasn't displaying my added post. I threw a few test posts out there but nothing would show up. I'll admit, I couldn't help but wonder if something had happened with FeedBurner. I shouldn't have doubted, though. It was indirectly my fault.

You see, FeedBurner has a nice report called FeedMedic (located under their Troubleshootize tab). I hadn't found that report until this afternoon. It informed me that my source feed was too large.

Wha???

Apparently, my last post on file extensions was too big. I ended up editing the last post and taking out a lot of the unnecessary HTML that came out of my code to HTML addin. I ended up using Jeff Atwood's great FormatToHtml VS2005 macro. The HTML it generates is a lot simpler, but also a lot cleaner. That solution alone dropped my post under the "large" size. Now everyone can happily see my posts again!

posted on Friday, March 31, 2006 5:04:26 PM (Central Standard Time, UTC-06:00)  #    Comments [0]

Recently, I was adding the ability to open files from an application at work. Luckily, I had run into this situation before, so I knew that you could run a Process.Start on the file in question and have Explorer open it for you. What I didn't know was that it would throw a Win32Exception if the file didn't have an associated program to open it. My next step was what any good developer would do: Google it.

Fairly early on in my searching, I came across a post by Ned Batchelder that described the EXACT same scenario I was working on! Ned wanted to open a file with an unknown extension from a managed application just like me. He details his Google search and explains how he discovered information about the API functions, ShellExecute and ShellExecuteEx. Basically, you can call ShellExecuteEx with an "open" verb and Explorer will attempt to open the file. If it fails with an SE_ERR_NOASSOC error, then call ShellExecuteEx again but using an "openas" verb. Doesn't sound too bad, though some code snippets still would've sped the process up for me :-) I did learn it better this way, though.

Anyway, here's where I started. First, I pulled in the definition for the SHELLEXECUTEINFO structure.

Friend Structure SHELLEXECUTEINFO
    
Public cbSize As Integer
    Public fMask As SEE_MASK
    
Public hwnd As IntPtr
    <MarshalAs(UnmanagedType.LPTStr)> _
    
Public lpVerb As String
    <MarshalAs(UnmanagedType.LPTStr)> _
    
Public lpFile As String
    <MarshalAs(UnmanagedType.LPTStr)> _
    
Public lpParameters As String
    <MarshalAs(UnmanagedType.LPTStr)> _
    
Public lpDirectory As String
    Dim nShow As Integer
    Dim hInstApp As SE_ERR
    
Dim lpIDList As IntPtr
    <MarshalAs(UnmanagedType.LPTStr)> _
    
Public lpClass As String
    Public hkeyClass As IntPtr
    
Public dwHotKey As Integer
    Public hIcon As IntPtr
    
Public hProcess As IntPtr
End Structure

Next, I created definitions for the SW, SEE_MASK, and SE_ERR constants.

#Region " SW Constants "
    Friend Enum SW As Integer
        HIDE = 0
        SHOWNORMAL = 1
        NORMAL = 1
        SHOWMINIMIZED = 2
        SHOWMAXIMIZED = 3
        MAXIMIZE = 3
        SHOWNOACTIVATE = 4
        SHOW = 5
        MINIMIZE = 6
        SHOWMINNOACTIVE = 7
        SHOWNA = 8
        RESTORE = 9
        SHOWDEFAULT = 10
        FORCEMINIMIZE = 11
        MAX = 11
    End Enum
#End Region

#Region " SEE_MASK Constants "
    Friend Enum SEE_MASK As Integer
        CLASSNAME = &H1
        CLASSKEY = &H3
        IDLIST = &H4
        INVOKEIDLIST = &HC
        ICON = &H10
        HOTKEY = &H20
        NOCLOSEPROCESS = &H40
        CONNECTNETDRV = &H80
        FLAG_DDEWAIT = &H100
        DOENVSUBST = &H200
        FLAG_NO_UI = &H400
        UNICODE = &H4000
        NO_CONSOLE = &H8000
        ASYNCOK = &H100000
        HMONITOR = &H200000
        NOZONECHECKS = &H800000
        NOQUERYCLASSSTORE = &H1000000
        WAITFORINPUTIDLE = &H2000000
        FLAG_LOG_USAGE = &H4000000
    End Enum
#End Region

#Region " SE_ERR Constants "
    Friend Enum SE_ERR As Integer
        SE_ERR_FNF = 2              ' file not found
        SE_ERR_PNF = 3              ' path not found
        SE_ERR_ACCESSDENIED = 5     ' access denied
        SE_ERR_OOM = 8              ' out of memory
        SE_ERR_DLLNOTFOUND = 32
        SE_ERR_SHARE = 26
        SE_ERR_ASSOCINCOMPLETE = 27
        SE_ERR_DDETIMEOUT = 28
        SE_ERR_DDEFAIL = 29
        SE_ERR_DDEBUSY = 30
        SE_ERR_NOASSOC = 31
    End Enum
#End Region

Finally, I created my definition for the ShellExecuteEx function.

<DllImport("shell32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Friend Shared Function ShellExecuteEx( _
        
ByRef lpExecInfo As SHELLEXECUTEINFO) As Boolean
End Function

I stuck all of this into a NativeMethods class and tried the code below:

Dim info As New NativeMethods.SHELLEXECUTEINFO
info.cbSize = Marshal.SizeOf(info)
info.lpDirectory = Path.GetDirectoryName(fileToStart)
info.lpFile = Path.GetFileName(fileToStart)
info.nShow = NativeMethods.SW.SHOWDEFAULT
info.lpVerb =
"open"
info.fMask = NativeMethods.SEE_MASK.FLAG_NO_UI Or NativeMethods.SEE_MASK.FLAG_DDEWAIT

If Not NativeMethods.ShellExecuteEx(info) Then
    If info.hInstApp = NativeMethods.SE_ERR.SE_ERR_NOASSOC Then
        Dim sinfo As New NativeMethods.SHELLEXECUTEINFO
        sinfo.cbSize = Marshal.SizeOf(info)
        sinfo.lpVerb =
"openas"
        sinfo.lpDirectory = Path.GetDirectoryName(fileToStart)
        sinfo.lpFile = Path.GetFileName(fileToStart)
        sinfo.nShow = NativeMethods.SW.SHOWDEFAULT
        NativeMethods.ShellExecuteEx(sinfo)
    
End If
End If

UPDATED (9/12/2006): Many thanks to Michael and his comment regarding using the SEE_MASK.FLAG_DDEWAIT. That fixed all of the problems I was running into regarding the above code. (see the usage on info.fMask)

A quick note about the code: Ned mentioned in his post that he got the ERROR_NO_ASSOCATION error instead of SE_ERR_NOASSOC. Well, the ERROR_NO_ASSOCATION is what is returned in the Win32 error (Marshal.LastWin32Error). The SE_ERR_NOASSOC is returned in the hInstApp (see MSDN documentation here).

See any problems with that? I certainly didn't (and still don't). It works like a charm for files with associations... however, it would only work one time for files without any associated program. Afterwards, it wouldn't give me anything... no errors, nada, zilch. After a few tries, an AccessViolationException would get thrown. Why? I have no idea. I tried various things to see if I should be cleaning up memory somewhere but I couldn't find anything. I did find out that if I just called ShellExecuteEx with the "openas" verb the first time, I wouldn't get any problems at all. What in the world???

As a result of the strange behavior, I changed my code slightly to look like this:

Try
    Using p As New Process
        p.StartInfo.FileName = fileToStart
        p.StartInfo.UseShellExecute =
True
        p.Start()
   
End Using
Catch win32Ex As Win32Exception
   
Dim sinfo As New NativeMethods.SHELLEXECUTEINFO
    sinfo.cbSize = Marshal.SizeOf(sinfo)
    sinfo.lpVerb =
"openas"
    sinfo.lpDirectory = Path.GetDirectoryName(fileToStart)
    sinfo.lpFile = Path.GetFileName(fileToStart)
    sinfo.nShow = NativeMethods.SW.SHOWDEFAULT

   
If Not NativeMethods.ShellExecuteEx(sinfo) Then
        Throw New Win32Exception
   
End If
End Try

The above code is working like a charm. I still have no idea why my first example won't work for me. If anyone has any ideas or suggestions, please let me know. I haven't worked with Interop between managed and unmanaged code very much. My experience up to this point has primarily been an entirely managed project or an entirely unmanaged project (and that only in college).

NOTES: Here are some resources I found while researching this:

[http://www.nedbatchelder.com/blog/20050318T070512.html]
[http://www.pinvoke.net/default.aspx/shell32/ShellExecuteEx.html]
[http://www.pinvoke.net/default.aspx/Constants/SW.html]
[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/structures/shellexecuteinfo.asp]

Also, be sure to look at the ShellAPI.h header file!

posted on Friday, March 31, 2006 8:08:06 AM (Central Standard Time, UTC-06:00)  #    Comments [2]