Ask Kathleen

Customize Your Application Startup

Customize your application startup screen and add log-in functionality; learn how to view derived types in your projects; check for improper GUIDs; and drill down on extension methods.

Technologies mentioned in this article include LINQ, VB.NET, and C#.

Q In researching startup procedures for a database application using VB.NET (2005), I came across your article, "Enable the Application Framework in VB" (Ask Kathleen, October 2007). But my startup module needs to display a log-in screen to get a user name and password. It doesn't work to set the log-in form as the startup form, because the application shuts down after I log in, even though I'm opening the MDI form:

Private Sub loginButton_Click() _
   Handles loginButton.Click
   If Me.Login() Then
      Dim frm = New MdiForm
      frm.Show()
      Me.Close()
   End If
End Sub

I could make the MDI form the startup form, but if the user cancels the login process, the system shouldn't continue to display the MDI form; instead, it should exit. Also, I'm adding a splash screen, so what will be the correct implementation in combination with the login-screen and MDI form?

A The MDI form is the main form of the application, so it should be the startup form. Set the splash screen using the Splash Screen combo box on the Project Properties|Application dialog. You can set the minimum length of time the splash screen is shown with the MinimumSplashScreenDisplayTime as I discussed in a column I wrote about a year ago (Ask Kathleen, August 2007).

Adjacent to the Splash Screen entry on the Project Properties dialog, you'll find a button labeled Application Events. Clicking on this button lets you enter event handlers for key points in the application lifecycle, including Startup, StartupNextInstance, NetworkAvailabilityChanged, Shutdown, and UnhandledException. This is a partial class that derives from Microsoft.VisualBasic.WindowsFormsApplicationBase. You can find more information about the class's abilities in the help topic for WindowsFormsApplicationBase.

You can control application startup using the Startup event; this includes the ability to add a call to your log-in form. If the log-in process fails, you can set the Cancel property of the event arguments to True to block instantiation of the MDI form. This also blocks the remainder of application startup, including closing the splash screen, which hangs the application. Explicitly hiding the splash screen solves this problem:

Private Sub MyApplication_Startup( _
   ByVal sender As Object, ByVal e As _
   Microsoft.VisualBasic. _
   ApplicationServices.StartupEventArgs) _
   Handles Me.Startup
   Dim frm = New LoginForm
   Dim result = frm.ShowDialog
   If result <> DialogResult.OK Then
      e.Cancel = True
      Me.HideSplashScreen()
   End If
End Sub

This assumes the LoginForm returns Windows.Forms.DialogResult.OK if the user logged in successfully and Cancel if the user did not.

Q I've noticed that the Object Browser on rare occasions has a node called "Derived Types." Why can't I make this work with my projects?

A The Object Browser has a rather bizarre implementation for displaying the class hierarchy, but you can force it to display derived types with a hidden trick.

Open a new instance of Visual Studio (VS) 2008. Leave your old one open or close it, it doesn't really matter. Open the Object Browser in this empty instance of VS and you'll find a combo box in the upper left. Drop it down and select "Edit Custom Component Set." Next, browse to the location where you're building the startup project. This directory should contain the appropriate assemblies. Now select all the assemblies for your project. Don't open the solution in Visual Studio, but open an empty instance of Visual Studio and place all the assemblies of your project into a custom component set.

If you select a class in the tree, you'll notice not only a node for base types, but also one for derived types. Selecting derived types can stop the system for a minute if it must search a large solution, but you can see the basic relationships from within the Object Browser.

Once you open a solution in either Visual Basic or C#, the derived type feature is disabled; the VS team has reported that this is by design. In at least some cases, non-referenced projects continue to support listing Derived Types, but the relevant ones do not. So, run two instances of Visual Studio when you need to explore derived classes.

Q I have some logging capabilities that I want to use only during development. Is there a way I can do this, but keep the code out of Release builds?

A There are two techniques to include variable code in your application: the #If compiler directive and the Conditional attribute. These work with both Visual Basic and C#. Each approach depends on compilation constants defined on the compile tab of the project properties. The most common constant is Debug, which is defined by default for Debug builds and removed for Release builds. You can use a #If DEBUG block around your logging code to ensure logging is only done in your debug builds:

   private void Form1_Load(object sender, 
   EventArgs e)
   {
#if DEBUG
      // Development time logging code
      Console.WriteLine("Test 1");
#endif
      // Form startup code
   }

The #If conditional is in the mainline of your code and visible within the logic of your application. This is sometimes good because you see the optional code in place, but it can make it difficult to recognize the logic of the application, which makes it inappropriate in other scenarios.

Alternatively, you can place the code that should run only during development into a subroutine marked with the Conditional attribute. When this attribute is present, the contents of the subroutine aren't executed:

using System.Diagnostics;
private void Form1_Load_2(object sender, 
   EventArgs e)
{
   Test();
   // Form startup code
}

[Conditional("DEBUG")]
private void Test()
{
   // Development time logging code
   Console.WriteLine("Test 2");
}

The class name is ConditionalAttribute, which is consistent with other attributes. The code will compile correctly with either Conditional or ConditionalAttribute specified as the attribute. I recommend the shorter and more common Conditional attribute. I've used the default DEBUG attribute, but you can define any compiler constants you want.

While the choice between the two approaches will sometimes be a matter of style, each approach has additional features. The #If directive lets you use any expression that depends only on conditional constants, literals, and operators. Thus, you can compare to a value rather than merely the presence or absence of a particular compiler constant. Using this, you can leave compiler constants in your application and simply change the value. This is helpful if you're using custom constants to avoid having to search your code to remember the constant's name.

You can't use expressions with ConditionalAttribute, but the attribute does provide the ability to do an effective "Or" to see whether one or more constants exist. You do this by including multiple Conditional attributes.

You can also apply the Conditional attribute to an attribute class, which is a class that derives from System.Attribute. In this case, the attribute is included only if the constant is defined.

Q I'm getting this error:

"Unable to emit assembly: 
   Error emitting 'System.
   Runtime.InteropServices.
   GuidAttribute' attribute –           
    'Incorrect UUID format.'

A This generally occurs because you have a GUID defined in a format not recognized in the current context. This typically happens in the Assembly GUID. The solution is to check for an improper GUID somewhere in your app:

<assembly: Guid(
   "{8207C60B-ADA8-45ae-A117-
   4FCC861B3075}")>

It's easy to encounter this problem when you're using the Create GUID tool available in the Tools menu.

Q I want to achieve a better understanding of the Entity Framework classes and methods. Someone told me I needed Lutz Roeder's Reflector to explore this. Can you tell me where to get it and how to use it?

A You can acquire it at http://aisto.com/roeder/dotnet or by searching for "Lutz Roeder Reflector." To use Reflector, simply add the assemblies (DLLs) you're interested in with the File Open option in the menu. You'll need to know the location of your DLLs in order to open them. Reflector is a great tool, but you might not need it for your exploration.

The Object Browser is often overlooked as a tool for exploring libraries and the .NET Framework. One advantage is that the Object Browser automatically includes currently referenced assemblies, so you don't have to track down the assembly files. As I mentioned earlier, you can get references to your current assemblies, or derived types, through an empty instance of Visual Studio, but not both simultaneously. To explore the Entity Framework, you probably want to use the empty solution trick to get the derived types.

The Object Browser lets you choose between container (assembly) view and namespace view, which means you can see the classes from a programming perspective. You right-click on the tree to switch between views, and you can order the members alphabetically, by scope, or by type. The process of searching also filters irrelevant items from the list displayed. To see the searched item in context, click on the clear filter button adjacent to the Search button. You can also filter to a specific type by selecting Filter To Type in the right-click menu if you find other items distracting (see Figure 1).

Note that the XML comment information is provided in the details section. This is one of the most valuable aspects of the Object Browser, along with the fact you can jump from the Object Browser to the code by double clicking, assuming the code is in the currently loaded solution.

Reflector has two features that set it apart from the Object Browser (see Table 1 for a comparison of the ways the Object Browser and Reflector let you explore project hierarchies). The first is that it lets you disassemble source code and look at the code being run for the assembly. You can view this in IL, or you can let Reflector back calculate or reverse compile the IL into your language of choice (assuming your language of choice is Visual Basic, C#, Microsoft C++, Delphi, or Chrome). Note that decompilation doesn't preserve fidelity, which means you might not get back exactly what you put in, but it will be nearly identical.

The second feature that differentiates Reflector is an Analyzer that is more powerful than the Object Browser's "Find All References." The Analyzer offers insight into how things are being used together, not just the hierarchy used to construct them.

Q I get a compiler error with this code:

Public Function Output() As String
   Dim list = Customers()
   If list Is Nothing OrElse _
      list.Count = 0 Then
      Return String.Empty
   End If
   Return (From item In list _
      Select item.Output). _
      ConcatWithComma
End Function

Unfortunately, the error doesn't make a lot of sense: "Range variable ‘Output' hides a variable in an enclosing block or a range variable previously defined in the query expression." I don't define a variable named Output!

A The problem is that Visual Basic prepares for additional clauses in your LINQ expression by naming the value in the Select clause with the best name it can come up with, which is the field name Output.

For legacy reasons, Visual Basic functions still allow direct assignment to the function name and implicitly create a local variable to supply this value. From a practical perspective, this means you can't have an implicit or explicit name in your LINQ query that matches your function name. You can solve this by naming the Select variable explicitly:

Return (From item In list Select Out2 = _
   item.Output).ConcatWithComma

You can also solve the problem by enclosing item.Output in parentheses to convert it to an expression VB can't evaluate for a name. It will thus make up a name, which doesn't matter because you aren't using the name:

Return (From item In list Select _
   (item.Output)).ConcatWithComma

Q I've written some C# extension methods, and I can use them inside my project. But when I try to use them from another project, the compiler says they aren't available. Here's the simplest case I was testing:

namespace Miscellaneous_Tests
{
   static class Class1
   {
      public static bool test(this string text)
      {
         return true;
      }
   }
}

A Two easy ways for extension methods to fail are to have the class marked as internal, or fail to include the namespace. In your case, adding an explicit public modifier on the static class Class1 will solve the problem.

If you still have a problem, ensure the Miscellaneous_Tests namespace is imported in the class where you want to use the extension methods. Also make sure you reference the assembly.

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