The Portable Network Graphics (PNG) format is a great image format. It's file sizes are typically much smaller than GIF or JPG, and it supports alpha transparency. So, you can have a transparent background on your image, and it will look correct on any page. With GIF or JPG, a designer would need to carefully adjust the image background to match the page background. The only problem with PNG, is that IE doesn't support it natively! Instead of rendering the transparent areas correctly, they are displayed as opaque grey areas. Fortunately, IE 7 supposedly fixes that, and there are workarounds for IE 5.5 and 6. Bob Osola has a
nice explanation and a javascript fix.
Suppose we wanted to package this solution in a way that makes it easy for ASP.NET developers to use, or we have custom controls that will rely on this fix. Currently, they would have to keep up with the client script, and reference it from each page in each application they wrote. Using Web Resources and Client Script Management in ASP.NET 2.0, we can greatly simplify reuse of this script.
Solution OverviewThe fix itself is a little javascript that must be run on every page that requires PNG support. It works by looping through all the elements on a page and, if it's an <img> element with a .png image, applying the CSS filter to fix PNG in IE. We've modified it slightly to do the same for <input type="image"> elements, such as those rendered by the DataGridView's command button columns. As is, the developer must add the script to their application, and add this line to the <head> element of each page that needs PNG transparency:
<!--[if lt IE 7]>
<script defer type="text/javascript" src="pngfix.js"></script>
<![endif]-->
But, we want the solution to be easy for developers to use. From the application developer's perspective, we want the expirience to be:
- Add a reference to MyCompany.Web.dll
- Add the line MyCompany.Web.UI.ClientScriptManager.EnablePNGSupport(...) to a control, page, or master page's OnLoad event.
Setting it upThis tells us that we'll need a MyCompany.Web Class Library Project, and a class called 'ClientScriptManager'. So in VS05, create a project named MyCompany.Web. For various reasons, it's also a good idea to set the default namespace to empty here. This will let you set the namespace inside your class, and will make using Web Resources easier. To do this:
- Rt Click the project
- Choose Properties
- Choose the Application Tab
- Empty the 'Default Namespace' field.
Embedding ResourcesNext, create a folder named Resources, and add the
pngfix.js file to it. To use this javascript file from web applications, it needs to be embedded in the assembly and marked as a web resource. Follow these steps to do this:
- Right click the script file and choose 'Properties'
- Set Build Action to 'Embedded Resource'.
- Go to the Solution Explorer and click 'Show All Files'
- Expand the 'My Project' item
- Open AssemblyInfo.vb
- Add this line to the bottom: <Assembly: WebResource("pngfix.js", "text/javascript")>
- Note: If you didn't set the default namespace to empty as above, you'll need to include it here (ie: "MyCompany.Web.pngfix.js")
To recap, this just told the compiler to take the pngfix.js file and stuff it into the .dll when it's compiled, and mark it as usable by web applications. This fix also requires a blank image file to work correctly. Do the same as above for the file
blank.gif.Getting Resources at the ClientThe last step is to create a method that will allow developers to easily use this javascript. This method needs to add a script to the page to set the path to blank.gif, and a script to run the fix in pngfix.js. But remember that file has been embedded in a dll, so that our developers don't have to keep up with it. This is where Web Resources come in. To get the content that was embedded into the dll, ASP.NET 2.0 gives us a method named GetWebResourceUrl(...). When called, this method returns a URL to a special path that looks something like this:
/WebResource.axd?d=rN9s8cQhdsLqV...
This location actually maps to an HttpHandler built in to ASP.NET 2.0, which will do the work of pulling the embedded resource out of our assembly, and returning it to the client. So, to get the embedded resource- be it a script, image, or any other file, all we need to do is browse to the url provided by GetWebResourceUrl. But, your developers shouldn't have to worry about all that.
The code below creates a class with a shared method for registering the two scripts needed for this fix to work. It first checks to see if the browser is IE 5.5 or 6. Next, it checks to be sure the script isn't already registered, since adding it twice would cause errors. Finally, it registers the scripts.
Imports System.Web.UI.WebControls
Imports System.Web.UI
Imports System.Web
Namespace MyCompany.Web.UI
''' <summary>
''' Class for managing client side scripts frequently used by MyCompany web applications.
''' </summary>
''' <remarks></remarks>
Public Class ClientScriptManager
''' <summary>
''' Adds client script to add PNG transparency support to Internet Explorer 6.0 and lower browsers,
''' if it has not already been added by a previous call to this method.
''' </summary>
''' <param name="page">The page.</param>
''' <remarks>
''' <para>IE 6 and lower do not properly display PNGs with transparent backgrounds.
''' This method puts a javascript include tag in the page's <head> declaration
''' if the browser is IE 6 or 5.5. This fix is based on the one
''' found here: http://homepage.ntlworld.com/bobosola
''' </para>
''' <para>
''' Typically this would be done using Page.ClientScript, however that method does
''' not support the 'defer' attribute, which is required in this case.
''' </para>
''' <para>
''' To use, add the following to your Page or MasterPage:
''' <code>
''' Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
''' MyCompany.Web.UI.ClientScriptManager.AddPNGSupport(Me.Page)
''' End Sub
''' </code>
''' </para>
''' </remarks>
Public Shared Sub EnsurePNGSupport(ByVal page As System.Web.UI.Page)
If HttpContext.Current.Request.Browser.Browser = "IE" Then
Dim Version As Double = CDbl(HttpContext.Current.Request.Browser.Version)
If Version < 7.0 And Version > 5.5 Then
Dim pageHeader As System.Web.UI.HtmlControls.HtmlHead = page.Header
If Not pageHeader.Page.ClientScript.IsClientScriptBlockRegistered( "SetBlankImageUrl") Then
Dim blankImageScript As String = String.Format("<script type=""text/javascript"">var blankImageUrl = '{0}'</script>", pageHeader.Page.ClientScript.GetWebResourceUrl( GetType(MyCompany.Web.UI.ClientScriptManager), "blank.gif"))
pageHeader.Page.ClientScript.RegisterClientScriptBlock( pageHeader.Page.GetType(), "SetBlankImageUrl", blankImageScript)
End If
If Not ContainsControlWithId(pageHeader.Controls, "pngFixBlock") Then
Dim scriptBlock As New LiteralControl(String.Format("<script defer type=""text/javascript"" src=""{0}""></script>", pageHeader.Page.ClientScript.GetWebResourceUrl( GetType(MyCompany.Web.UI.ClientScriptManager), "pngfix.js")))
scriptBlock.ID = "pngFixBlock"
pageHeader.Controls.Add(scriptBlock)
End If
End If
End If
End Sub
''' <summary>
''' Gets a value indicating whether a control with the given id already exists in a collection.
''' </summary>
''' <param name="controls"></param>
''' <param name="id"></param>
''' <returns></returns>
''' <remarks></remarks>
Private Shared Function ContainsControlWithId(ByVal controls As ControlCollection, ByVal id As String) As Boolean
For Each control As Control In controls
If control.ID = id Then Return True
Next
Return False
End Function
End Class
End Namespace
Copy the above, and put in a class called 'ClientScriptManager.vb.'
Using the FixBuild, and if everything worked correctly, your developers can reference the assembly from their projects, and fix PNG transparency with a single line of code in their page's OnLoad event:
MyCompany.Web.UI.ClientScriptManager.EnsurePNGSupport(Me)
An even better option would be to put this in a master page OnLoad event:
MyCompany.Web.UI.ClientScriptManager.EnsurePNGSupport(Me.Page)
Finally, it's worth noting that this would also work for custom controls. You can use the same technique to embed a png to be used by, say, the ImageUrl property of your control. The only gotcha is that the script looks for the .png extension to determine if it needs to fix an element, but the URL returned by GetWebResourceUrl doesn't end in '.png'. To fix this, simply add the extension to the query string:
If Not String.IsNullOrEmpty(Me.ImageUrl) Then
printLink.ImageUrl = ImageUrl
ElseIf Me.DesignMode Then
printLink.ImageUrl = Me.Page.ClientScript.GetWebResourceUrl(Me.GetType(), "printer48.png")
Else
printLink.ImageUrl = Me.Page.ClientScript.GetWebResourceUrl(Me.GetType(), "printer48.png") + "&.png"
End If
Files for this post:
pngfix.js (2.19 KB)blank.gif (.04 KB)