#206 – WPF Applications Use an Event-Driven Paradigm

WPF uses an event-driven programming model.  In event-driven programs, the user interface elements are first rendered and then the application waits for one or more events to occur.  Events typically originate from the user and normally represent the user interacting with the application through some input device.  For example, a user clicks on a button by clicking with the left mouse button.

When an event occurs, some code in the application executes, in a method known as an event handler.  The application will then typically perform some action in response to the event.  (E.g. open a new window, based on a button being clicked).

Events can also originate from a source other than the user.  For example, an internal software timer might Tick every 100 ms, generating an event for each tick.

#205 – The WPF GUI Pipeline

In WPF, there are a number of different actors or elements that impact how data (or content) will be presented to the user.  The various elements can be thought of a “pipeline”, with data flowing in at one end and final GUI elements coming out of the other end.

The different elements in the GUI pipeline are:

  • Data binding – Data from some object is bound to one or more controls
  • Value Converters – Bound data may be converted to another format
  • Templates – Control and data templates specify constituent elements for a control
  • Presenters – ContentPresenter and ItemsPresenter act as placeholders within a template, indicating where the content goes
  • Layout – Determine position and size of all elements
  • Style – Apply a predefined style, which specifies one or more property values

#204 – Detecting Key Presses in a WPF Window

You can detect key presses in a class that derives from Window by overriding the OnKeyDown and OnKeyUp methods (which in turn fire the KeyDown and KeyUp events).  

These key down/up methods are invoked in addition to any control that has focus and might also provide key down/up methods.

For example, a TextBox also has KeyDown and KeyUp events that are fired.  If a user presses a key while a TextBox has focus, the sequence of events is:

  • KeyDown in TextBox
  • KeyDown in Window
  • KeyUp in TextBox
  • KeyUp in Window

Here’s an example:

    public partial class MainWindow : Window
    {
        private static Key[] vowels = { Key.A, Key.E, Key.I, Key.O, Key.U };

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if ((vowels.Contains(e.Key)) && (!e.IsRepeat))
                lblVowels.Content = lblVowels.Content + e.Key.ToString();
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);

            if (vowels.Contains(e.Key))
                lblVowels.Content = lblVowels.Content + ",";
        }

#203 – Window Size and Location Are Specified in Device Independent Units

The Window class has Left and Top properties that specify the location on the screen of the left and top sides of the window.  It also has Width and Height properties that specify the window’s size.  All of these are expressing in device independent units.

If the current DPI setting is 96 dpi (dots per inch), the WPF units are equivalent to pixels.  I.e. A window specified as 96 units high would appear as 1 inch high.  If the DPI setting is different, the specified WPF units are scaled by dpi/96 to get the resulting pixel value.

In other words:    # pixels = (# WPF Units) * (DPI / 96)

The reason for this is so that WPF applications will appear at roughly the same dimensions on various monitors, regardless of the pixel density.

#202 – An Example of Modal/Modeless Dialogs

You can see an example of both modal and modeless dialogs if you run the Windows Notepad application.

After starting the Notepad application, enter some text and then bring up the Find dialog using Edit | Find.  You’ll notice that you can switch back and forth between the Find dialog and the main Notepad window.  The Find dialog is a modeless dialog, since you can still interact with the main application.

Now close the Find dialog and open the Print dialog using File | Print.  While the Print dialog is open, you’ll notice that you can no longer interact with the main Notepad window.  The Print dialog is a modal dialog.

#201 – Showing a Modal Dialog Using ShowDialog

Rather than using the Window.Show method to show a modeless window, you can use the ShowDialog method to show the window as a modal dialog.

A modal dialog is one that appears in front of all other windows in the application and blocks interaction with all the other windows until the modal dialog is closed.

A modeless window, on the other hand, is one that you can interact with along with other windows in the application.

In the example below, we use the same child window class, but display one window as modeless (using Show) and one as modal (using ShowDialog).

        private void btnModal_Click(object sender, RoutedEventArgs e)
        {
            ChildWindow win = new ChildWindow();
            win.Title = "Modal child - " + DateTime.Now.ToLongTimeString();
            win.ShowDialog();
        }

        private void btnModeless_Click(object sender, RoutedEventArgs e)
        {
            ChildWindow win = new ChildWindow();
            win.Title = "Modeless window - " + DateTime.Now.ToLongTimeString();
            win.Show();
        }

Minimizing a modal window will also minimize its parent.

#200 – Parent/Child Relationships Between Windows

Creating a new Window object and displaying it using the Show method results in a new window in your application that is independent from any existing windows.  The new window can be minimized and maximized independently and gets its own icon on the taskbar.

Creating a new (independent) window:

    Window w = new Window();
    w.Title = DateTime.Now.ToLongTimeString();
    w.Show();

WPF supports the notion of parent/child relationships between windows.  You set up the relationship by setting the Owner property of the child window to point to the parent.

    Window w = new Window();
    w.Title = string.Format("Child #{0}", this.OwnedWindows.Count + 1);
    w.Owner = this;
    w.Show();

When you make one window the child of another:

  • When a parent is minimized, all the child windows are minimized
  • When child is minimized, parent is not minimized
  • You can interact with either window
  • The parent can’t cover a child window
  • Closing a parent closes all the child windows

#199 – An Application’s Windows Property Lists All of Its Windows

The Application class has a Windows property which is a collection of all of the windows that have been created by the current application.

Assume that we have an application with a main window that includes the following two buttons:

Whenever the user clicks on the Create New Window button, we create and show a new window.

    private void btnCreate_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        AnotherWindow win = new AnotherWindow();
        win.Title = DateTime.Now.ToLongTimeString();
        win.Show();
    }

In the Click event handler for the Where Are the Windows? button, we can iterate through all of the windows that the application created and display some information about each one.

        private void btnWhere_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            foreach (Window w in App.Current.Windows)
            {
                sb.AppendFormat("Window [{0}] is at ({1},{2}).\n", w.Title, w.Top, w.Left);
            }

            MessageBox.Show(sb.ToString(), "My Windows");
        }

The final result:

#198 – Creating and Showing Additional Windows

You can create and show additional Window objects at runtime–in addition to your application’s main window.

Let’s assume that we want a second type of window in our application, beyond the MainWindow class that the WPF Application wizard creates for us.  To start with, right-click in the Solution Explorer and select Add, New Item.

In the dialog, that comes up, select WPF as the template group and then Window (WPF) as the object to add.  Give the new window a name.

It will now show up in the Solution Explorer.

Finally, to create and show the new window, you just create an instance of the new class and call its Show method.  For example, we might add a Button on the main form that creates a new window whenever you click on it.

    private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        AnotherWindow win = new AnotherWindow();
        win.Show();
    }

#197 – Override Application Class Methods for Standard Events

We’ve already mentioned some of the main events that the Application class fires–e.g. Startup, Exit and SessionEnding.  We also showed how you can add event handlers to your Application-derived class to handle these events, using SessionEnding as our example.

Rather than adding an event handler for Application.SessionEnding to our class, we could have just overridden the OnSessionEnding method.  This is more appropriate in a class that already derives from Application.  Instead of specifying the handler in App.xaml and adding the handler to App.xaml.cs, we can just add code for the override to App.xaml.cs.

As an example, the override for OnSessionEnding could look something like this:

	public partial class App : Application
	{
            protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
            {
                // Always call method in base class, so that the event gets raised.
                base.OnSessionEnding(e);

                // Place your own SessionEnding logic here
            }
    }