programming4us
programming4us
DESKTOP

Mixing Windows and Forms

10/2/2010 3:21:18 PM
The cleanest way to integrate WPF and Windows Forms content is to place each in a separate window. That way your application consists of well-encapsulated window classes, each of which deals with just a single technology. Any interoperability details are handled in the glue code—the logic that creates and shows your windows.

1. Adding Forms to a WPF Application

The easiest approach to mixing windows and forms is to add one or more forms (from the Windows Forms toolkit) to an otherwise ordinary WPF application. Visual Studio makes this easy—just right-click the project name in the Solution Explorer, and choose Add → New Item. Then, select the Windows Forms category on the left side, and choose the Windows Form template. Lastly, give your form a file name, and click Add. The first time you add a form, Visual Studio adds references to all the required Windows Forms assemblies, including System.Windows.Forms.dll and System.Drawing.dll.

You can design a form in a WPF project in the same way that you design it in a Windows Forms project. When you open a form, Visual Studio loads the normal Windows Forms designer and fills the Toolbox with Windows Forms controls. When you open the XAML file for a WPF window, you get the familiar WPF design surface instead.

For better separation between WPF and Windows Forms content, you might choose to place the "foreign" content in a separate class library assembly. For example, a Windows Forms application might use the WPF windows defined in a separate assembly. This approach makes especially good sense if you plan to reuse some of these windows in both Windows Forms and WPF applications.


2. Adding WPF Windows to a Windows Forms Application

The reverse trick is a bit more awkward. Visual Studio doesn't directly allow you to create a new WPF window in a Windows Forms application. (In other words, you won't see it as one of the available templates when you right-click your project and choose Add → New Item.) However, you can add the existing .cs and .xaml files that define a WPF window from another WPF project. To do so, right-click your project in the Solution Explorer, choose Add → Existing Item, and find both these files. You'll also need to add references to the core WPF assemblies (PresentationCore.dll, PresentationFramework.dll, and WindowsBase.dll).

There's a shortcut to adding the WPF references you need. You can add a WPF user control (which Visual Studio does support), which causes Visual Studio to add these references automatically. You can then delete the user control from your project. To add a WPF user control, right-click the project, choose Add → New Item, pick the WPF category, and select the User Control (WPF) template.


Once you add a WPF window to a Windows Forms application, it's treated correctly. When you open it, you'll be able to use the WPF designer to modify it. When you build the project, the XAML will be compiled, and the automatically generated code will be merged with your code-behind class, just as it is in a full-fledged WPF application.

Creating a project that uses forms and windows isn't too difficult. However, there are a few extra considerations when you show these forms and windows at runtime. If you need to show a window or form modally (as you would with a dialog box), the task is straightforward, and your code is essentially unchanged. But if you want to show a window modelessly, you need a bit of extra code to ensure proper keyboard support, as you'll see in the following sections.

3. Showing Modal Windows and Forms

Showing a modal form from a WPF application is effortless. You use exactly the same code you'd use in a Windows Forms project. For example, if you have a form class named Form1, you'd use code like this to show it modally:

Form1 frm = new Form1();
if (frm.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
MessageBox.Show("You clicked OK in a Windows Forms form.");
}

You'll notice that the Form.ShowDialog() method works in a slightly different way than WPF's Window.ShowDialog() method. While Window.ShowDialog() returns true, false, or null, Form.ShowDialog() returns a value from the DialogResult enumeration.

The reverse trick—showing a WPF window from a form—is just as easy. Once again, you simply interact with the public interface of your Window class, and WPF takes care of the rest:

Window1 win = new Window1();
if (win.ShowDialog() == true)
{
MessageBox.Show("You clicked OK in a WPF window.");
}

4. Showing Modeless Windows and Forms

It's not quite as straightforward if you want to show windows or forms modelessly. The challenge is that keyboard input is received by the root application and needs to be delivered to the appropriate window. In order for this to work between WPF and Windows Forms content, you need a way to forward these messages along to the right window or form.

If you want to show a WPF window modelessly from inside a Windows Forms application, you must use the static ElementHost.EnableModelessKeyboardInterop() method. You'll also need a reference to the WindowsFormsIntegration.dll assembly, which defines the ElementHost class in the System.Windows.Forms.Integration namespace. (You'll learn more about the ElementHost class later in this chapter.)

You call the EnableModelessKeyboardInterop() method after you create the window but before you show it. When you call it, you pass in a reference to the new WPF window, as shown here:

Window1 win = new Window1();
ElementHost.EnableModelessKeyboardInterop(win);
win.Show();

When you call EnableModelessKeyboardInterop(), the ElementHost adds a message filter to the Windows Forms application. This message filter intercepts keyboard messages when your WPF window is active and forwards them to your window. Without this detail, your WPF controls won't receive any keyboard input.

If you need to show a modeless Windows Forms application inside a WPF application, you use the similar WindowsFormsHost.EnableWindowsFormsInterop() method. However, you don't need to pass in a reference to the form you plan to show. Instead, you simply need to call this method once before you show any form. (One good choice is to call this method at application startup.)

WindowsFormsHost.EnableWindowsFormsInterop();

Now you can show your form modelessly without a hitch:

Form1 frm = new Form1();
frm.Show();

Without the call to EnableWindowsFormsInterop(), your form will still appear, but it won't recognize all keyboard input. For example, you won't be able to use the Tab key to move from one control to the next.

You can extend this process to multiple levels. For example, you could create a WPF window that shows a form (modally or modelessly), and that form could then show a WPF window. Although you won't need to do this very often, it's more powerful than the element-based interoperability support you'll learn about later. This support allows you to integrate different types of content in the same window but doesn't allow you to nest more than one layer deep (for example, creating a WPF window that contains a Windows Forms control that, in turn, hosts a WPF control).

5. Visual Styles for Windows Forms Controls

When you show a form in a WPF application, that form uses the old fashioned (pre–Windows XP) styles for buttons and other common controls. That's because support for the newer styles must be explicitly enabled by calling the Application.EnableVisualStyles() method. Ordinarily, Visual Studio adds this line of code to the Main() method of every new Windows Forms application. However, when you create a WPF application, this detail isn't included.

To resolve this issue, just call the EnableVisualStyles() method once before showing any Windows Forms content. A good place to do this is when the application is first started, as shown here:

public partial class App : System.Windows.Application
{
protected override void OnStartup(StartupEventArgs e)
{
// Raises the Startup event.
base.OnStartup(e);

System.Windows.Forms.Application.EnableVisualStyles();
}
}

Notice that the EnableVisualStyles() method is defined in the System.Windows.Forms.Application class, not the System.Windows.Application class that forms the core of your WPF application.

6. Windows Forms Classes That Don't Need Interoperability

As you know, Windows Forms controls have a different inheritance hierarchy than WPF elements. These controls can't be used in a WPF window without interoperability. However, there are some Windows Forms components that don't have this limitation. Provided you have a reference to the necessary assembly (usually System.Windows.Forms.dll), you can use these types without any special considerations.

For example, you can use the dialog classes (such as ColorDialog, FontDialog, PageSetupDialog, and so on) directly. In practice, this isn't terribly useful because these dialog boxes are slightly outdated and because they wrap structures that are part of Windows Forms, not WPF. For example, if you use the ColorDialog, you'll get a System.Drawing.Color object rather than the System.Windows.Media.Color object you really want. The same is true when you use the FontDialog, PageSetupDialog, and PrintPreviewDialog that are designed to work with the older Windows Forms printing model. In fact, the only Windows Forms dialog box that's of any use and that doesn't have a WPF equivalent in the Microsoft.Win32 namespace is FolderBrowserDialog, which lets the user pick a folder.

More useful Windows Forms components include the SoundPlayer, which you can use as a lightweight equivalent to WPF's MediaPlayer and MediaElement; the BackgroundWorker , which you can use to manage an asynchronous task safely; and the NotifyIcon (described next), which allows you to show a system tray icon.

The only disadvantage to using the NotifyIcon in a WPF window is that there's no design-time support. It's up to you to create the NotifyIcon by hand, attach event handlers, and so on. Once you supply an icon using the Icon property and set Visible to true, your icon will appear in the system tray (shown in Figure 1). When your application ends, you should call Dispose() on the NotifyIcon to remove it from the system tray immediately.

Figure 1. A system tray icon

The NotifyIcon does use some Windows Forms–specific bits. For example, it uses a Windows Forms context menu, which is an instance of the System.Windows.Forms.ContextMenuStrip class. Thus, even if you're using the NotifyIcon with a WPF application, you need to define its context menu using the Windows Forms model.

Creating all the objects for a menu in code and attaching event handlers is more than a little tedious. Fortunately, there's a simpler solution when building a WPF application that uses the NotifyIcon—you can create a component class. A component class is a custom class that derives from System.ComponentModel.Component. It provides two features that ordinary classes lack: support for deterministically releasing resources (when its Dispose() method is called) and design-time support in Visual Studio.

Every custom component gets a design surface (technically known as the component tray) where you can drag and configure other classes that implement IComponent, including Windows Forms. In other words, you can use the component tray to build and configure a NotifyIcon, complete with a context menu and event handlers. Here's what you need to do to build a custom component that wraps an instance of the NotifyIcon and includes a context menu:

  1. Open or create a new WPF project.

  2. Right-click the project name in the Solution Explorer and choose Add → New Item. Pick the Component Class template, supply a name for your custom component class, and click Add.

  3. Drop a NotifyIcon onto the design surface of your component. (You'll find the NotifyIcon in the Common Controls section of the Toolbox.)

  4. At this point, Visual Studio adds the reference you need to the System.Windows.Forms.dll assembly. However, it won't add a reference to the System.Drawing.dll namespace, which has many core Windows Forms types. You must add a reference to System.Drawing.dll manually.

  5. Drop a ContextMenuStrip onto the design surface of your component (from the Menus & Toolbars section of the Toolbox). This will represent the context menu for your NotifyIcon. Figure 2 shows both ingredients in Visual Studio.

    Figure 2. The design surface of a component
  6. Select the NotifyIcon and configure it using the Properties window. You'll want to set the following properties: Text (the tooltip text that appears when you hover over the NotifyIcon), Icon (the icon that appears in the system tray), and ContextMenuStrip (the ContextMenuStrip you added in the previous step).

  7. To build the context menu, right-click the ContextMenuStrip and choose Edit Items. You'll see a collection editor that you can use to add the menu items (which you should place after the root menu item). Give them easily recognizable names because you'll need to connect the event handlers yourself.

  8. To see your component class code, right-click the component in the Solution Explorer and choose View Code. (Don't open the .Designer.cs code file. This file contains the code that Visual Studio generates automatically, which is combined with the rest of the component code using partial classes.)

  9. Add the code that connects your menu's event handlers. Here's an example that adds the event handler for two menu commands—a Close button and a Show Window button:

    public partial class NotifyIconWrapper : Component
    {
    public NotifyIconWrapper()
    {
    InitializeComponent();

    // Attach event handlers.
    cmdClose.Click += cmdClose_Click;
    cmdShowWindow.Click += cmdShowWindow_Click;
    }

    // Use just one instance of this window.
    private Window1 win = new Window1();

    private void cmdShowWindow_Click(object sender, EventArgs e)
    {
    // Show the window (and bring it to the forefront if it's already visible).
    if (win.WindowState == System.Windows.WindowState.Minimized)
    win.WindowState = System.Windows.WindowState.Normal;

    win.Show();
    win.Activate();
    }

    private void cmdClose_Click(object sender, EventArgs e)
    {
    System.Windows.Application.Current.Shutdown();
    }

    // Clean up when this component is released by releasing all
    // contained components (including the NotifyIcon).
    protected override void Dispose(bool disposing)
    {
    if (disposing && (components != null)) components.Dispose();
    base.Dispose(disposing);


    }

    // (Designer code omitted.)
    }

Now that you've created the custom component class, you simply need to create an instance of it when you want to show the NotifyIcon. This triggers the designer code in your component, which creates the NotifyIcon object, making it visible in the system tray.

Removing the system tray icon is just as easy—you simply need to call Dispose() on your component. This step forces the component to call Dispose() on all contained components, including the NotifyIcon.

Here's a custom application class that shows the icon when the application starts and removes it when the application ends:

public partial class App : System.Windows.Application
{
private NotifyIconWrapper component;

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
component = new NotifyIconWrapper();
}

protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
component.Dispose();
}
}

To complete this example, make sure you remove the StartupUri attribute from the App.xaml file. This way, the application starts by showing the NotifyIcon but doesn't show any additional windows until the user clicks an option from the menu.

This example relies on one more trick. A single main window is kept alive for the entire application and shown whenever the user chooses Show Window from the menu. However, this runs into trouble if the user closes the window. There are two possible solutions—you can re-create the window as needed the next time the user clicks Show Window, or you can intercept the Window.Closing event and quietly conceal the window instead of destroying it. Here's how:

private void window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
this.WindowState = WindowState.Minimized;
this.ShowInTaskbar = false;
}

Notice that this code doesn't change the Visibility property of the window or call its Hide() method because neither action is allowed when the window is closing. Instead, the code minimizes the window and then removes it from the taskbar. When restoring the window, you'll need to check the window state and return the window to its normal state along with its taskbar button.

Other  
 
PS4 game trailer XBox One game trailer
WiiU game trailer 3ds game trailer
Top 10 Video Game
-   Uncharted 4: A Thief's End | E3 2015 Extended Gameplay Trailer
-   V.Next [PC] Kickstarter Trailer
-   Renowned Explorers [PC] Launch Date Trailer
-   The Void (Game Trailer)
-   World of Warships [PC] Open Beta Trailer
-   F1 2015 | Features Trailer
-   Battle Fantasia Revised Edition | Debut Trailer for Steam
-   Victor Vran [PC] Story Trailer
-   Star Wars Battlefront PC Alpha footage
-   Skyforge [PC] Open Beta Gameplay Trailer
-   Armored Warfare [PC] PvE Trailer
-   F1 2015 [PS4/XOne/PC] Features Trailer
-   Act of Aggression [PC] Pre-Order Trailer
-   Sword Coast Legends [PC] Campaign Creation E3 2015 Trailer
-   Sword Coast Legends [PC] Campaign Creation E3 2015 Dungeon Run Trailer
Game of War | Kate Upton Commercial
programming4us
 
 
programming4us