Chapter 2. Applications and Settings

A WPF application is a Windows process in which you have an instance of the WPF Application object. The Application object provides lifetime services and integration with ClickOnce deployment. Between application sessions, you’ll want to be able to keep application and user settings in a way that integrates well with WPF applications. All of these topics are the focus of this chapter.

On the other hand, if you’re interested in XML Browser Applications (XBAPs)—applications hosted in the browser and deployed over the Web—read Chapter 11.

Application Lifetime

In the Windows sense, an “application” is an address space and at least one thread of execution (a.k.a. a “process”). In the WPF sense, an application is a singleton object that provides services for UI components and UI programmers in the creation and execution of a WPF program. More specifically, in WPF, an application is an instance of the Application class from the System.Windows namespace.

Explicit Application Creation

Example 2-1shows code for creating an instance of the Application class.

Example 2-1. Creating an application explicitly
using System;
System.Windows; // the home of the Application class

class Program {
    [STAThread]
    static void Main(  ) {
        Application app = new System.Windows.Application(  );
        Window1 window = new Window1(  );
        window.Show(  );
        app.Run(  );
    }
}

Here, we’re creating an application inside an STA thread,[5] creating a window and showing it, and then running the application. While the application is running, WPF processes Windows messages and routes events to WPF UI objects as necessary. When the Run method returns, messages have stopped being routed and generally don’t start again (unless you show a modal window after the Run method returns, but that’s not something you’ll usually do). During its lifetime, the application provides various services.

Application Access

One of the services the Application class provides is access to the current instance. Once an instance of the Application class is created, [6] it’s available via the Current static property of the Application class. For example, the code in Example 2-1 is equivalent to the code in Example 2-2.

Example 2-2. Implicitly filling in the Application.Current property
using System;
using System.Windows;

class Program {
    [STAThread]
    static void Main(  ) {
        // Fills in Application.Current
        Application app = new System.Windows.Application(  );

        Window1 window = new Window1(  );
        window.Show(  );

        Application.Current.Run(); // same as app.Run(  )
    }
}

Here, in the process’s entry point, we’re creating an application, creating and showing the main window, and then running the application. Creation of the Application object fills the static Application.Current property. Access to the current application is very handy in other parts of your program where you don’t create the application or when you let WPF create the application for you itself.

Implicit Application Creation

Because a Main method that creates and runs an application is pretty darn common, WPF can provide the process’s entry point for you. WPF projects generally designate one XAML file that defines the application. For example, if we had defined our application in a XAML file with code behind, it would look like Example 2-3.

Example 2-3. Declaring an application in XAML
<!-- App.xaml -->
<Application
  x:Class="ImplicitAppSample.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />


// App.xaml.cs
using System;
using System.Windows;

namespace ImplicitAppSample {
  public partial class App : System.Windows.Application {
    protected override void OnStartup(StartupEventArgs e) {
      // let the base class have a crack
      base.OnStartup(e);

      // WPF itself is providing the Main that creates an
      // Application and calls the Run method; all we have
      // to do is create a window and show it
      Window1 window = new Window1(  );
      window.Show(  );
}
}
}

Notice that Example 1-11 is defining a custom application class in this code (ImplicitAppSample.App) that derives from the Application class. In the OnStartup override, we’re only creating a window and showing it, assuming WPF is going to create the Main for us that creates the instance of the App class and calls the Run method (which calls the OnStartup method). The way that WPF knows which XAML file contains the definition of the Application class is that the Build Action is set to ApplicationDefinition, as shown in Figure 2-1.

Setting the Build Action for the application definition XAML file
Figure 2-1. Setting the Build Action for the application definition XAML file

The ApplicationDefinition Build Action lets WPF know which class is our application and hooks it up appropriately in a Main method it generates for us, which saves us from writing several lines of boilerplate code.

Tip

For msbuild aficionados, the standard XAML Build Action setting of Page looks like this in the .csproj file:

<Project ...>
  ...
  <ItemGroup>
    <Page Include="App.xaml" />
    <Compile Include="App.xaml.cs">
      <DependentUpon>App.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
    ...
  </ItemGroup>
  ...
</Project>

When we switch the Build Action to ApplicationDefinition, it looks like this:

<Project ...>
  ...
  <ItemGroup>
    <ApplicationDefinition Include="App.xaml" />
    <Compile Include="App.xaml.cs">
      <DependentUpon>App.xaml</DependentUpon>
      <SubType>Code</SubType>
    </Compile>
    ...
  </ItemGroup>
  ...
</Project>

This setting causes the WPF build tasks to generate the following code:

namespace ImplicitAppSample {
  public partial class App : Application {
    [System.STAThreadAttribute(  )]
    [DebuggerNonUserCodeAttribute(  )]
      public static void Main(  ) {
        ImplicitAppSample.App app =
          new ImplicitAppSample.App(  );
        app.Run(  );
      }
  }
}

Except for the debugger attribute (which stops Visual Studio from stepping into this method when debugging), this is equivalent to what we were writing by hand a few code samples ago.

If our window class is defined in a XAML file itself (as most likely it will be), we can save ourselves from overriding the OnStartup method by setting the StartupUri property in the application’s XAML file (see Example 2-4).

Example 2-4. Setting the StartupUri on the application
<!-- App.xaml -->
<Application
  x:Class="ImplicitAppSample.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="Window1.xaml" />

The combination of setting the Build Action of the application’s XAML file to ApplicationDefinition and the StartupUri property provides the following features:

  • Creating an instance of the Application object and setting it as the value of the Application.Current property

  • Creating and showing an instance of the UI defined in the XAML designated in the StartupUri property

  • Setting the Application object’s MainWindow property

  • Calling the Application object’s Run method, keeping the application running until the main window is closed

This set of features makes more sense when we get a handle on what the “main window” is.

Top-Level Windows

A top-level window is a window that is not contained within or owned by another window (window ownership is discussed in more detail later). A WPF application’s main window is the top-level window that is set in the MainWindow property of the Application object. This property is set by default when the first instance of the Window class is created and the Application.Current property is set. In other words, by default, the main window is the top-level window that’s created first after the application itself has been created. If you like, you can override this default by setting the MainWindow property manually.

In addition to the main window, the Application class provides a list of top-level windows from the Windows property. This is useful if you’d like to implement a Window menu, like the one in Figure 2-2.

Managing the top-level windows exposed by Application
Figure 2-2. Managing the top-level windows exposed by Application

To implement the Window menu, we first start with a MenuItem element:

<!-- Window1.xaml -->
<Window ...>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="auto" />
      <RowDefinition />
      <RowDefinition Height="auto" />
    </Grid.RowDefinitions>
    <Menu>
      <MenuItem Header="Window" x:Name="windowMenu">
        <MenuItem Header="dummy item" />
      </MenuItem>
    </Menu>
  </Grid>
</Window>

MenuItem is a HeaderedItemControl (as described in Chapter 5), which means that it has header content that we’ll use to hold the name of the menu item (“Window”), and subcontent that we’ll use to hold the menu items for each top-level window. Notice the use of a dummy subitem to start with. Without this dummy item, you won’t be able to get notification that the user has asked to show the menu items (whether via mouse or via keyboard).

To populate the Window menu, we’ll handle the menu item’s SubmenuOpened event:

public partial class Window1 : Window {
  ...
  public Window1(  ) {
    InitializeComponent(  );

    windowMenu.SubmenuOpened += windowMenu_SubmenuOpened;
  }

  void windowMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
    windowMenu.Items.Clear(  );
    foreach (Window window in Application.Current.Windows) {
      MenuItem item = new MenuItem(  );
      item.Header = window.Title;
      item.Click += windowMenuItem_Click;
      item.Tag = window;
      item.IsChecked = window.IsActive;
      windowMenu.Items.Add(item);
    }
  }

  void windowMenuItem_Click(object sender, RoutedEventArgs e) {
    Window window = (Window)((MenuItem)sender).Tag;
    window.Activate(  );
  }
}

When the SubmenuOpened event is triggered, we use the Application object’s Windows property to get a list of each top-level Window, creating a corresponding MenuItem for each Window.

Tip

For those of you already steeped in data binding and data templates who are wondering why we’re populating the Window menu manually, it’s because the WindowCollection class that the Windows property returns doesn’t provide notifications when it changes, so once the Window menu is populated initially, there’s no way to keep it up-to-date. Maybe next version . . .

Application Shutdown Modes

Some applications work naturally with the idea of a single main window. For example, many applications (drawing programs, IDEs, Notepad, etc.) have a single top-level window that controls the lifetime of the application itself (i.e., when the main window goes away, the application shuts down). On the other hand, some applications have multiple top-level windows or some other kind of lifetime control that’s independent of a single main window.[7] You can specify when your application shuts down by setting the application’s ShutdownMode property to one of the values of the ShutdownMode enumeration:

namespace System.Windows {
  public enum ShutdownMode {
    OnLastWindowClose = 0, // default
    OnMainWindowClose = 1,
    OnExplicitShutdown = 2,
  }
}

The OnMainWindowClose value is useful when you’ve got a single top-level window, and the OnLastWindowClose value is useful for multiple top-level windows (and is the default). In either of these cases, in addition to the automatic application shutdown the ShutdownMode policy describes, an application can also be shut down manually by calling the Application object’s Shutdown method. However, in the case of OnExplicitShutdown, the only way to stop a WPF application is by calling Shutdown:

public partial class Window1 : System.Windows.Window {
  ...
  void shutdownButton_Click(object sender, RoutedEventArgs e) {
    Application.Current.Shutdown(  );
  }
}

You can change the shutdown mode in code whenever you like, or you can set it in the application definition XAML:

<Application
  x:Class="AppWindowsSample.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="Window1.xaml"
  ShutdownMode="OnExplicitShutdown" />

Tip

Of course, there are a number of ways to shut down a Windows process. The Application.Shutdown method is a nice way of doing it by closing the top-level windows and returning from the Run method. This lets the windows involved get their Closing and Closed notifications, although canceling the shutdown in the Closing event doesn’t actually stop the application shutdown process.

Application Events

You can best see the life cycle of a standard application in the set of events that it exposes:[8]

  • Startup

  • Activated

  • Deactivated

  • DispatcherUnhandledException

  • SessionEnding

  • Exit

Startup event

The Application object’s Startup event is fired when the application’s Run method is called, and it is a useful place to do application-wide initialization, including the handling of command-line arguments, which are passed in the StartupEventArgs:

void App_Startup(object sender, StartupEventArgs e) {
   for (int i = 0; i != e.Args.Length
; ++i) {
    // do something useful with each e.Args[i]
    ...
  }
}

Activated and Deactivated events

The Activated event is called when one of the application’s top-level windows is activated (e.g., via a mouse click or Alt-Tab). Deactivated is called when your application is active and another application’s top-level window is activated. These events are handy when you want to stop or start some interactive part of your application:

void App_Activated(object sender, EventArgs e) {
  ResumeGame(  );
}

void App_Deactivated(object sender, EventArgs e) {
  PauseGame(  );
}

DispatcherUnhandledException event

The application’s dispatcher is an object that routes events to the correct place, including unhandled exceptions. In the event that you’d like to handle an exception otherwise unhandled in your application—maybe to give the user a chance to save his current document and exit—you can handle the DispatcherUnhandledException event:

void App_DispatcherUnhandledException(
   object sender, DispatcherUnhandledExceptionEventArgs e) {

  string err = "Oops: " + e.Exception.Message
);

  MessageBox.Show(err, "Exception", MessageBoxButton.OK);

 // Only useful if you've got some way of guaranteeing that
 // your app can continue reliably in the face of an exception
 // without leaving this AppDomain in an unreliable state...
 //e.Handled = true; // stop exception from bringing down the app
}

The Exception property of the DispatcherUnhandledExceptionEventArgs event argument is useful to communicate to your users what happened, whereas the Handled property is useful to stop the exception from actually bringing down the application (although this is a dangerous thing to do and can easily result in data loss).

SessionEnding event

The SessionEnding event is called when the Windows session itself is ending (e.g., in the event of a shutdown, logoff, or restart):

void App_SessionEnding(object sender, SessionEndingCancelEventArgs e) {
  if (MessageBox.Show(
        e.ReasonSessionEnding.ToString(  ),
        "Session Ending",
        MessageBoxButton.OKCancel) == MessageBoxResult.Cancel) {
   e.Cancel = true; // stop the session from ending
  }
}

The ReasonSessionEnding property of the SessionEndingCancelEventArgs event argument is one value in the ReasonSessionEnding enumeration:

namespace System.Windows {
  public enum ReasonSessionEnding {
    Logoff = 0,
    Shutdown = 1,
  }
}

The Cancel property is useful if you’d like to stop the session from ending, although this is considered rude, and more progressive versions of Windows (like Vista) may not let you change its decision to end a session at all.

Exit event

The Exit event is called when the application is actually exiting, whether the last window has gone away, the Application.Shutdown method is called, or the session is ending. One of the overloads of the Shutdown method allows the programmer to pass an integer exit code, which is ultimately exposed by the process for use by your favorite Win32 process examination APIs. By default, this value is zero, but you can observe or override it in the handlers for this event:

void App_Exit(object sender, ExitEventArgs e) {
  e.ApplicationExitCode = 452; // keep 'em guessing...
}

Application Instancing

While we’ve just been talking about the lifetime of an application, things get a bit more interesting when you take into account that multiple instances of a single application can be running at any one time simply because the user can double-click on the same EXE multiple times. In fact, the default behavior in Windows and WPF does nothing to hamper or support multiple instances of the same application. For example, if we double-click on the AppWindowsSample.exe from Figure 2-2 more than once, we get more than one instance, as Figure 2-3 shows.

Multiple instance applications
Figure 2-3. Multiple instance applications

In Figure 2-3, we’ve got several top-level windows, some associated with each of three instances of the application. Sometimes more than one instance of a single application is a good thing. However, sometimes it just confuses users. For example, in Figure 2-3, even though we’ve got a Window menu, only the windows associated with each instance of the application are shown, which can be confusing as heck to the poor user faced with such an application.

Single instance applications

If you’d like your application to be single instance, it’s easy to detect an existing instance and shut down any subsequent instances (see Example 2-5).

Example 2-5. Very simple existing instance detection
public partial class App : System.Windows.Application {
   Mutex mutex;
   protected override void OnStartup(StartupEventArgs e) {
     base.OnStartup(e);

   // Check for existing instance
   string mutexName = "MyCompanyName.MyAppName";
   bool createdNew;
   mutex = new Mutex(true, mutexName, out createdNew);

  // If there is an existing instance, shut down this one
  if( !createdNew ) { Shutdown(  ); }
  }
}

In Example 2-5, the key is to access a Windows mutex with a session-wide unique name so that we can tell whether it was already created by an initial instance or whether we’re the initial instance. The mutex name we’re using is one we pick to be sufficiently unique for our needs. Once the mutex has been created, it’ll live for the life of the WPF Application object itself, which will live for the life of the process, so if we’re not the first one to create the mutex, we shut down our application, causing our process to exit.

However, it’s at this point that we realize that single instance detection isn’t the only feature we want; we also want the following:

  • Passing command-line arguments to the initial instance (e.g., in case a subsequent instance was passed a filename that the user would like opened)

  • Activating the main window of the initial instance

  • Dealing properly with multiple users logging into a single computer (even the same user logged in multiple times), giving each login an instance of the application

These are services that are not trivial to implement and we’d really love it if .NET provided this functionality for us. The good news is that it does. The bad news is that it’s provided only as part of the .NET 2.0 Visual Basic support for Windows Forms. If you’d like to take advantage of robust single instance management, you have to load the Microsoft.VisualBasic assembly, and you have an interesting integration challenge ahead of you, as both Visual Basic’s support for single instance management and WPF want to be “the” application. However, it is possible and you get to leverage Other People’s Code (OPC), of which I’m a big fan, especially when the “other people” are a multibillion-dollar corporation with a record of framework maintenance and upgrades.[9] For an example of how to integrate single instance detection from Visual Basic into WPF, check out the “Single Instance Detection” sample in the Windows Platform SDK.[10]

Other Application Services

In addition to what we’ve already discussed, the Application class provides access to app-level resources and navigation services. Chapter 12 discusses resources and Chapter 11 discusses navigation. The other major service that WPF applications support is ClickOnce deployment, which we’ll discuss right now.

Application Deployment

For the purposes of demonstration, let’s build something vital for procrastinators the world over: an application to generate excuses. The application was started with the “Windows Application (WPF)” project template in Visual Studio 2005 and was implemented with some very simple code. When you run it, it gives you an excuse from its vast database, as shown in Figure 2-4.

A WPF excuse-generation application
Figure 2-4. A WPF excuse-generation application

Simple Publishing

For anyone to use this wonderful application, it must be published. The simplest way to publish your WPF application is by right-clicking on the project in the Solution Explorer and choosing the Publish option, which will bring up the first page of the Publish Wizard (shown in Figure 2-5).

Publish Wizard publish location
Figure 2-5. Publish Wizard publish location

Figure 2-5 asks you to choose where you’d like to deploy your application, including to the disk, to a network share, to an FTP server, or to a web site. By default, the Publish Wizard will assume you want to publish to the Publish subdirectory of your project directory. Clicking the Next button yields Figure 2-6.

Publish Wizard installation options
Figure 2-6. Publish Wizard installation options

Because we’ve chosen to publish to something besides a web site, the Publish Wizard wants to know how users will access your published application—in other words, from a URL, from a UNC path, or from some optical media. (If you choose to publish to a web site, the only way to access the application is from a URL, so it won’t bother to ask.) We’d like to test web deployment, so we pick that option and leave the default URL alone. Clicking Next yields Figure 2-7.

Install mode in the Publish Wizard
Figure 2-7. Install mode in the Publish Wizard

For WPF applications, Figure 2-7 lets us choose whether we’d like this application to be made available online (when the computer is able to connect to the application’s URL) as well as offline (when the computer can’t connect to the URL), or whether you’d like the application to be only available online. These two options correspond to the ClickOnce terms locally installed and online only, respectively.

The job of the Publish Wizard is to bundle up the files needed to deploy an application using ClickOnce, including the manifest files that ClickOnce needs to deploy the application to a client machine after it’s been published.

Tip

This example assumes a standalone application, which provides its own host window. WPF also supports the XBAP application type, which is an application composed of one or more pages and hosted in Internet Explorer 6+. You can also publish an XBAP via ClickOnce from within Visual Studio, but the options are different. Chapter 11 discusses XBAP creation, publication, and deployment details.

Leaving the default “online or offline” option and clicking the Finish button yields Figure 2-8.

A summary of the chosen Publish options
Figure 2-8. A summary of the chosen Publish options

Figure 2-8 reminds us what we get with a locally installed ClickOnce application (i.e., the application will appear in the Start menu and in the Add or Remove Programs Control Panel). Clicking Finish causes Visual Studio to publish the application to the filesystem, including a publish.htm file that you can use to test deployment. If you happen to have an IIS application set up in the same folder to which Visual Studio publishes, it will launch the publish.htm file for you, as shown in Figure 2-9.

The Visual Studio-generated HTML file for testing ClickOnce applications
Figure 2-9. The Visual Studio-generated HTML file for testing ClickOnce applications

For simple needs, this is the complete experience for publishing a WPF ClickOnce locally installed application.

The User Experience

The user experience for running a ClickOnce locally installed application begins with a web page, such as the one shown in Figure 2-9, that includes a link to install the ClickOnce application. Clicking the link for the first time shows a download progress dialog similar to Figure 2-10.

Progress dialog for checking the application manifest
Figure 2-10. Progress dialog for checking the application manifest

Once the metadata file describing the application deployment settings has been downloaded (this file is called the application manifest), it will be checked for a certificate, which is extra information attached to the application that identifies a validated publisher name. ClickOnce requires all published applications to be signed, so Visual Studio will generate a certificate file for you as part of the initial publication process if you haven’t already provided one.

If the certificate used to sign the application manifest identifies a publisher that is already approved to install the application on the user’s machine (such as from a previous version or a IT-administered group policy), the application will be run without further ado, as shown at the beginning of this chapter in Figure 2-4.

If, on the other hand, the publisher’s certificate cannot be verified or is not yet trusted to run the application in question, a dialog similar to Figure 2-11 will be presented.

The Application Install dialog with an unknown publisher
Figure 2-11. The Application Install dialog with an unknown publisher

Figure 2-11 displays the name of the application, the source of the application, and the publisher of the application according to the certificate (or “Unknown Publisher” if the certificate could not be verified). It also lists a summary of the reasons this dialog is being shown, along with a link to more detailed warning information. However, such information will likely be ignored by the user choosing between the Install and Don’t Install buttons, from which the user will choose depending on the level of trust she has for the publisher she sees in the Security Warning dialog.

If the user chooses Don’t Install, no application code will be downloaded or executed. If she chooses Install, the application is downloaded, added to the Start menu, and added to the Add or Remove Programs Control Panel, all under the umbrella of the progress dialog shown in Figure 2-12, after which the application is executed.

Progress dialog for installing a locally installed ClickOnce application
Figure 2-12. Progress dialog for installing a locally installed ClickOnce application

Subsequent runs of the same version of the application, as launched from either a web site or the Start menu, will not ask for any additional user input (although they may show a dialog if checking for updates), but will launch the installed application directly.

WPF ClickOnce Specifics

There are a great number of additional details to ClickOnce application deployment, including security considerations, command-line handling, updating and rollback, prerequisite installation, access to external information sources, and certificate management, just to name a few. All of these details are beyond the scope of this book and are covered in great detail by other sources.[11] However, following are some specifics to standalone and XBAP ClickOnce deployment you might like to see all in one place.

Standalone WPF applications deployed using ClickOnce:

  • Can implement the main window with Window or NavigationWindow (although only the former has a project template in Visual Studio—the “Windows Application [WPF]” template)

  • Can be online-only or online/offline

  • If installed online/offline, can integrate with the Start menu, and can be rolled back and uninstalled

  • Must set “full trust” in the project’s Security settings (the Window class demands this)

XBAP applications deployed using ClickOnce:

  • Provide their content with one or more Page objects to be hosted in the browser

  • Must be online-only to deploy with ClickOnce

  • There can be no “Security Warning” dialog, so must not attempt to elevate permissions beyond what is provided already on the client’s machine

  • No custom pop-up windows are allowed (e.g., no dialogs); can use standard page navigation, page functions, and message boxes instead

  • Designated as XBAP by setting the HostInBrowser property to True in the project file (will be set by the “XAML Browser Application (WPF)” project template in Visual Studio)

For the details of navigation-based applications and XBAP browser hosting and deployment, read Chapter 11.

Settings

WPF applications gain access to all the same application and user setting options that any other .NET application can use (e.g., the Registry, .config files, special folders, isolated storage, etc.).

Designing Settings

The preferred settings mechanism for WPF applications is the one provided by .NET 2.0 and Visual Studio 2005: the ApplicationSettingsBase class from the System.Configuration namespace with the built-in designer. To access the settings for your application, click on the Settings tab in your project settings. This will bring up the Settings Designer shown in Figure 2-13.

The Settings Designer
Figure 2-13. The Settings Designer

Here we’ve defined two settings: a user setting of type System.String, called LastExcuse; and an application setting of type System.Boolean, called ExcludeAnimalExcuses with a default value of True. These two settings will be loaded automatically when I run my application, pulled from the application’s configuration file (named MyApplication.exe.config) and the user settings file saved from the application’s last session.

The Settings Designer manages a settings file and generates a class that allows you to program against the settings. For instance, our settings example will result in the class in Example 2-6 being generated (roughly).

Example 2-6. The Settings Designer-generated class
using namespace System.Configuration;

namespace excusegen.Properties {
  sealed partial class Settings : ApplicationSettingsBase {
    static Settings defaultInstance =
       ((Settings)(ApplicationSettingsBase.Synchronized(new Settings(  ))));


   public static Settings Default {
      get { return defaultInstance; }
  }

  [UserScopedSettingAttribute(  )]
  [DefaultSettingValueAttribute("")]
  public string LastExcuse {
      get { return ((string)(this["LastExcuse"])); }
      set { this["LastExcuse"] = value; }
  }

  [ApplicationScopedSettingAttribute(  )]
  [DefaultSettingValueAttribute("True")]
  public bool ExcludeAnimalExcuses {
      get { return ((bool)(this["ExcludeAnimalExcuses"])); }
     }
  }
}

There are several interesting things to notice about Example 2-6. The first is the defaultInstance member, which is initialized with an instance of the generated Settings class that’s been synchronized to allow for safe multithreaded access. Second, notice that this defaultInstance member is static and exposed from the Default static property, which makes it very easy to get to our settings, as we’ll soon see. Finally, notice the two properties exposed from the Settings class, one property for each of our settings in the Settings Designer. You can see that the mode of each property, user versus application, the default value, and the type all match. Further, although a user setting is read-write (it has a getter and a setter), because it can change during an application session, the application setting is read-only (it has only a getter). The implementations of the properties are just type-safe wrappers around calls to the ApplicationSettingsBase base class, which does the work of reading and writing your settings to the associated settings storage.

Using Settings

With these typed properties in place and the Default static property to expose an instance of our generated Settings class, usage is no different from any other CLR object, as you can see in Example 2-7.

Example 2-7. Using the Settings Designer-generated class
public partial class Window1 : Window {
  string[] excuses = {...};

  public Window1(  ) {
    InitializeComponent(  );
    this.newExcuseButton.Click += newExcuseButton_Click;

    // If there is no "last excuse," show a random excuse
    if( string.IsNullOrEmpty(Properties.Settings.Default.LastExcuse) ) {
      ShowNextExcuse(  );
    }
    // Show the excuse from the last session
    else {
      excuseTextBlock.Text = Properties.Settings.Default.LastExcuse;
    }
  }

  void newExcuseButton_Click(object sender, RoutedEventArgs e) {
    ShowNextExcuse(  );
  }
  Random rnd = new Random(  );
  void ShowNextExcuse(  ) {
    // Pick a random excuse, saving it for the next session
    // and checking for animals
    do {
      Properties.Settings.Default.LastExcuse =
         excuses[rnd.Next(excuses.Length - 1)];
    }
    while( Properties.Settings.Default.ExcludeAnimalExcuses &&
            HasAnimal(Properties.Settings.Default.LastExcuse) );

   // Show the current excuse
   excuseTextBlock.Text = Properties.Settings.Default.LastExcuse;
  }

  bool HasAnimal(string excuse) {...}

  protected override void OnClosed(EventArgs e) {
    base.OnClosed(e);

    // Save user settings between sessions
    Properties.Settings.Default.Save(  );
  }

}

In Example 2-7, we’re using the LastExcuse user setting to restore the last excuse the user saw when running the application previously, changing it each time a new excuse is generated. The ExcludeAnimalExcuses application setting is checked to exclude animal-based excuses, but it is never set.[12] To store user settings that change during an application’s session, we’re calling the Save method on the Settings object from the ApplicationSettingsBase base class. This class does the magic of not only keeping the settings in memory and notifying you when a setting changes (if you choose to care), but also automatically loading the settings when the application is loaded, saving on demand.

To help with the loading and saving, the ApplicationSettingsBase uses a settings provider, which is a pluggable class that knows how to read/write application settings (e.g., from the local filesystem, from the Registry, from a network server, etc.). The only settings provider that comes out of the box in .NET 2.0 is the one that writes to disk in a way that’s safe to use from even partial trust applications (like an XBAP), but it’s not hard to plug in your own settings provider if you need other behavior.[13]

Integrating Settings with WPF

None of the basics of the ApplicationSettingsBase-inspired support for settings, or any of the other mechanisms for doing settings in .NET, is specific to WPF. However, because the ApplicationSettingsBase class supports data change notifications (specifically, it implements INotifyPropertyChanged), we can bind to settings data just like any other data (for the details of data binding, see Chapter 6 and Chapter 7). For example, instead of manually keeping the TextBlock that shows the excuse up-to-date, we can just bind the Text property to the LastExcuse property, as shown in Example 2-8.

Example 2-8. Data binding to a settings class
<Window ... xmlns:local="clr-namespace:excusegen">
  ...
  <TextBlock ...
    Text="{Binding
            Path=LastExcuse,
            Source={x:Static local:Properties.Settings.Default}}" />
  ...
</Window>

Example 2-8shows a bit of an advanced use of the binding syntax, but basically it says that we’re binding the Text property of the TextBlock to the LastExcuse property of the excusegen.Properties.Settings.Default object. As the LastExcuse property changes, so does the Text property, so we no longer need to keep the Text property manually up-to-date; all we need to do is manage the LastExcuse property and the Text property will follow. For example:

Random rnd = new Random(  );

void ShowNextExcuse(  ) {
  // Pick a random excuse, saving it for the next session
  // and checking for animals
  do {
    // This updates the Text property on the TextBlock, too
    Properties.Settings.Default.LastExcuse =
       excuses[rnd.Next(excuses.Length - 1)];
  }
  while( Properties.Settings.Default.ExcludeAnimalExcuses &&
          HasAnimal(Properties.Settings.Default.LastExcuse) );

   // No longer any need to manually update the TextBlock
   //excuseTextBlock.Text = Properties.Settings.Default.LastExcuse;
}

The ability to use settings to drive a WPF UI makes the new .NET 2.0 ApplicationSettingsBase and Settings Designer the preferred means for managing settings in a WPF application.

Where Are We?

In WPF, the application contains an instance of the Application object. This object provides management services that let you control the lifetime of your application, as well as resource management and navigation, covered in Chapter 12, Appendix C, and Chapter 11, respectively. In this chapter, we also discussed deploying standalone applications using ClickOnce. (XBAP deployment can be found in Chapter 11.) Finally, to manage user and application settings between application sessions, we briefly discussed the ApplicationSettingsBase-related settings services provided by .NET 2.0 and Visual Studio 2005.



[5] * The “Single Threaded Apartment” (STA) was invented as part of the native Component Object Model (COM) to govern the serialization of incoming COM calls. All Microsoft presentation frameworks, native or managed, require that they be run on a thread initialized as an STA thread so that they can integrate with one another and with COM services (e.g., drag-and-drop).

[6] WPF makes sure that, at most, one Application object is created per application domain. For a discussion of .NET application domains, I recommend Essential .NET, by Don Box with Chris Sells (Addison-Wesley Professional).

[7] * For example, if an Office application is serving OLE objects, closing the windows will not cause the process to stop until those OLE objects are no longer needed.

[8] * Navigation events aren’t listed here, but are discussed in Chapter 11.

[9] * Whether Microsoft has a “good” record of framework maintenance and updates, I’ll leave to you to decide....

[11] * The SDK does a pretty good job, as does Smart Client Deployment with ClickOnce: Deploying Windows Forms Applications with ClickOnce, by Brian Noyes (Addison-Wesley Professional).

[12] * There is no configuration API to set an application setting.

[13] The SDK comes with custom settings provider samples that use a web service and the Registry. I didn’t like the one based on the Registry, so I updated it and wrote a little article about the experience of writing and using a custom settings provider. It’s available at http://www.sellsbrothers.com/writing/default.aspx?content=dotnet2customsettingsprovider.htm (http://tinysells.com/86).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset