Ask Kathleen

Whip WPF Snippets Into Shape

Learn how to work around some ugly behavior in WPF when relying on the provided code snippets; change the output of provided WPF snippets; resolve dependency issues in Windows Workflow; and more.

Technology Toolbox: VB .NET, C#

Q
I'm getting a runtime error in my Windows Presentation Foundation (WPF) project that says: "Cannot create instance of 'Window3' defined in assembly 'MyAssemblyName, Version=1.0.0.0, Culture=neutral, PublicKey-Token=null'. Exception has been thrown by the target of an invocation. Error in markup file 'Window3.xaml' Line 1 Position 9."

Window3 is the name of my startup window. Can you tell me what's going wrong?

A
There is an ugly issue with the WPF compiler that you can encounter if you use the provided snippets. When you inserted your snippet, you changed the type from the default "int" to "string" and made some other changes. But you didn't change the value passed to the UIPropertyMetadata constructor. The snippet provided zero as the only parameter, and this parameter is the default for your dependency property. Because it's accepted by the constructor as an object, the .NET compiler sees no problem. However, when the WPF runtime loads the XAML, it tries to assign the default and encounters a type mismatch. When this occurs only once, you'll probably find the name of the property with the type mismatch deep in a nested exception. However, with multiple dependency properties set to invalid defaults, the inner exception isn't available. This happened in your code, making the problem nearly impossible to track down.

I'd highly recommend that you change the dependency property snippets for C# and VB in both WPF and WF. In WPF, don't pass any parameter by default to the UIProperty-Metadata constructor. It sets the value to Nothing in VB or default in C# (zero for numerics), which is all the dangerous snippet code was doing anyway. In the case of WF, you should clean up the namespace usage so it's consistent, at a minimum. I've included the templates I use for dependency properties in the sample code download (download the code here).


Q
I don't like the output from some of the provided WPF snippets. Can I change them?

A
There are two tools you'll want to use to manage snippets. The Code Snippet Manager, which you'll find in the Tools menu of Visual Studio, manages how snippets are loaded and the Snippet Editor lets you edit snippets without touching the XML that defines them directly.

Before you can edit snippets, you have to find them. The Snippet Manager searches for snippets in two default locations: <Visual Studio project directory>\Code Snippets\<language>\My Code Snippets and <Program Files>\Microsoft Visual Studio 8\<language>\Snippets. The default snippets are in the second location, and you'll want to make backups first. You can also add additional locations for the Snippet Manager to search. Within all these directories, subdirectories provide category organization that appears in the Snippet Picker you see when you right-click and tell Visual Studio to "Insert Snippet."

The snippet structure is a specialized XML format with a file extension of .snippet. It's a real pain to work with this format, but the VB community saves the day here. Bill McCarthy and the team on GotDotNet workspaces (now MSDN workspaces) created an output-focused snippet-editing tool that features highlighted tags, support for functions like the class name, and support for references and imports. It's a great tool and its use isn't limited to VB (access the Options button to select your development language). You can download this file here.

Some of the VB snippets delivered by Microsoft fail to load because they have an empty CodeSnippet element at the top of the file. If you can't load a snippet, open it in Notepad, remove this extraneous element, and reload in the Snippet Editor. The default snippets are in a directory under Program Files, so you'll need to run the Snippet Editor as Administrator to edit them.

The Snippet Editor is extremely easy to use. Available snippets appear in a treeview. You'll probably have to add the default WPF snippet path manually by right-clicking on one of the root nodes and adding a path to <Program Files>\Microsoft Visual Studio 8\<language>\Snippets\NetFX30. Tabs at the bottom of the Snippet Editor workspace allow you to manage tokens, imports, references, and detailed information about the snippet, including the snippet's abbreviation. When you return to your code in Visual Studio, you'll find the easiest way to use common snippets is to type this snippet abbreviation and then hit Tab twice (Figure 1).

Neither VB nor C# take advantage of all the functionality in the snippet definition. VB doesn't support functions, so you have to enter the classname of the enclosing class, which is entered automatically in C#. C# ignores the references and imports that you must enter manually if they don't already exist.

When you include your new snippets through Visual Studio's Snippet Manager, be aware that there's a known bug in the Snippet Manager that can cause your snippets to fail to show up immediately. You will eventually see your new or altered snippets if you persevere.


Q
I can't seem to get one of my dependency properties to work correctly in my Windows Workflow code. I get an error that says: "?does not define a static dependency property with name ServerProperty." However, I have already defined it. Can you tell me what's wrong with this code?

Public Shared SerVerProperty As DependencyProperty = _
     DependencyProperty.Register("Server", _
     GetType(String), GetType(GenerationWorkflow))
     Public Property Server() As String
          Get
          Return CType( _
               MyBase.GetValue(SerVerProperty), String)
          End Get
          Set(ByVal value As String)
               MyBase.SetValue(SerVerProperty, value)
          End Set
     End Property

A
Dependency properties have a couple of quirks that can be frustrating. In this case, you might not suspect that an exact case-sensitive name match is required between the name of the instance property and the shared DependencyProperty you use to register it. Changing the name of the ServVerProperty shared variable to ServerProperty will fix this problem. This case sensitivity exists whether you're working in C# or VB.

The DependencyProperty class exists in Workflow and Presentation Foundation, but they have a few significant differences, including case sensitivity and restrictions on the quoted name. In both VB and C#, the case difference between SerVerProperty and the Server property name would not matter in WPF.


Q
What does TryCast do?

A
TryCast is the VB .NET equivalent of "as" in C#. They are both operators that perform a cast and return the results of a successful cast, or Nothing (null in C#) if the cast fails. If you have a class named Client, the current variable is either a Client object or Nothing after the cast:

' VB
Dim current As Client = TryCast(bizObject, Client)

// C#
Client current = bizObject as Client;

The alternatives, and the only option prior to .NET 2.0, are the CType operator and DirectCast operators. The rough C# equivalent is to prefix the variable with the type name in parentheses:

' VB
Dim current As Client = CType(bizObject, Client)

// C#
Client current = (Client)bizObject;

If the cast is successful, these two operators behave the same way, providing an explicit cast to the Client type. They differ from the TryCast approach and as operators when the cast fails. The CType and C# type prefix throws an error when the cast fails. The TryCast and C# as operators return Nothing, or null in C#.

The TryCast operator is new in .NET 2.0 and I think there's still confusion surrounding its use. Last week, I found this sample from a company that should have known better (variable names changed to protect the guilty).

TryCast(bizObject, Client).SetName(name)

When this is called, the TryCast operator will return Nothing, and the attempt to access the method on Nothing will throw an exception. Not only might later programmers assume you considered the cast error, but you'll get a null reference error instead of an illegal cast error. The right approach is to cast, then check the result.

' VB
Dim current As Client = TryCast(bizObject, Client)
If current IsNot Nothing Then
     current.SetName(name)
End If

//C#
Client current = bizObject as Client;
if (current != null)
{ current.SetName(name); }

This is roughly equivalent to:

' VB
Dim current As Client = Nothing
If TypeOf bizObject Is Client Then
     current = CType(bizObject, Client)
End If

// C#
Client current = null;
if (bizObject is Client)
{ current = (Client)bizObject; }

There is a slight performance benefit in the TryCast version. In terms of a single operation, a cast is relatively expensive. The CType version performs the cast twice—once in the type check, and again when you do the actual cast. This performance benefit is small, and you could perhaps ignore it. However, FxCop raises an issue on the second version identifying the multiple casts. This is a case where I let FxCop rule and use the faster TryCast version.

About the Author

Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube