Playing around with WPF/GDI+ Resource interop

WPF Resources

A while back, Jim posted a summary of the different types of resources in WPF. We know that from the 1.0-2.0 versions of the .Net Framework, there were basically two ways to load an image from a resource: through .resx files and by directly adding them to the solution and changing the Build Action to “Embedded Resource”.

 

If you’ve ever tried to load a Bitmap in Windows Forms using the new Bitmap(typeof(MyType), “sunset.jpg”) constructor, you’ll appreciate that they wanted to do something more friendly for markup in WPF.

 

In particular, its:

<Image Source="Sunset.jpg"/>

 

To make this work, you need to add an image to your project, and make sure (in the property grid) that the Build Action is set to Resource.

 

WPF/GDI+ Resource interop

The other day I ran into a situation where I wanted to potentially display an image in either WPF or in GDI/GDI+. I wanted to have one way to specify the image, but the flexibility to use either technology to render the image.

 

The solution? Use WPF’s new URI syntax and the helper methods on WPF’s application class to load the resource into a stream, then use the System.Drawing.Bitmap constructor that takes a stream.

 

I’ve put together a sample that shows how to load an GDI+ image from a WPF resource.

 

I made several discoveries while writing this sample:

 

 

Michael Weinhardt has recently released a new article on resources. You should check out his new msdn magazine article on how pack uri’s work.

 

There’s kinda-sorta an alternate HBitmap solution. If you’re starting with an HBitmap, you can use WPF’s System.Windows.Interop.Imaging methods such as CreateBitmapSourceFromHBitmap and friends. Unfortunately I couldn’t find a way to take a WPF image and scribble to an HBitmap.

 

Using the Loaded event for custom code is much more sane. If you throw an exception in the constructor of the Window, you’re doing it in the XAML parser context – so you’ll get a XamlParseException, with your actual exception in the InnerException.

 

 

 

The sample

 

 

Lets walk through a sample interop case. If you just want to see the code – skip to step 8.

 

Step 0: Install the latest version of Cider

Step 1: Create new WPF Windows application

Step 2: Right click on the project, surf to your sample images in My Pictures and add Sunset.jpg to the project

Step 3: Double check the properties window to ensure that the “Build Action” property for Sunset.jpg is set to “Resource”.

Step 4: Edit XAML.

<Window x:Class="WindowsApplication69.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

  Title="WindowsApplication69" Height="300" Width="300"

    >

    <Grid>

      <Image Source="Sunset.jpg"/>

    </Grid>

</Window>

 

Step 5: Click back on the designer and verify that the image loads (this is a new feature in the latest CTP, enjoy!)

 

Step 6: Add the references we’re going to need to do some interop. Expand the references section in the solution explorer and add a reference to:

System.Windows.Forms

System.Drawing

WindowsFormsIntegration

 

(look Program Files\Reference Assemblies\Microsoft\Framework\v3.0\ for the last one – it helps us use Windows Forms from WPF markup).

 

Step 7: (You’re still with me? Great!) Go back to XAML and add in the windows forms namespace to the window tag:

xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

  

And then go ahead and add a WindowsFormsHost with a picture box in it.

      <WindowsFormsHost Width="100" Height="100" Background="Green">

        <wf:PictureBox x:Name="pictureBox"/>

      </WindowsFormsHost>

So that your final code looks like so:

<Window x:Class="WindowsApplication69.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

    Title="WindowsApplication69" Height="300" Width="300"

    >

    <Grid>

      <Image Source="Sunset.jpg"/>

      <WindowsFormsHost Width="100" Height="100" Background="Green">

        <wf:PictureBox x:Name="pictureBox"/>

      </WindowsFormsHost>

    </Grid>

</Window>

Step 8: Write code! Flip to Window1.xaml.cs.

 

public partial class Window1 : System.Windows.Window {

        public Window1() {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(Window1_Loaded);

        }

        void Window1_Loaded(object sender, RoutedEventArgs e) {

  // build up a relative path to the image.

            System.Uri imageLocation = new

                System.Uri("/WindowsApplication69;component/Sunset.jpg",

            System.UriKind.Relative);

            pictureBox.Image = GetBitmap(imageLocation);

                       

        }

        private System.Drawing.Bitmap GetBitmap(Uri uri) {

            // Use the helper methods on WPF's application

            // class to create an image.

            using (Stream resourceStream = Application.GetResourceStream(uri).Stream) {

                return new System.Drawing.Bitmap(resourceStream);

            }

        }

    }