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=http://schemas.microsoft.com/winfx/2006/xaml/presentation


    xmlns:x=http://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=100Background=Green>


        <wf:PictureBox x:Name=pictureBox/>


      </WindowsFormsHost>


 


So that your final code looks like so:


 


<Window x:Class=WindowsApplication69.Window1


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


    xmlns:x=http://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);


            }


        }


 


    }


 


 


 


 


Comments (3)

  1. jfo's coding says:

    I’ve put together a series of articles on WPF, from a Windows Forms developer perspective.&amp;nbsp; This…

  2. jfo's coding says:

    I’ve put together a series of articles on WPF, from a Windows Forms developer perspective. This started