65.9K
CodeProject 正在变化。 阅读更多。
Home

使用控件适配器在 IE 中正确显示 PNG 图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (21投票s)

2006年9月26日

7分钟阅读

viewsIcon

95822

downloadIcon

315

Internet Explorer 5.5 和 6 无法使用标准的 IMG 标签显示透明 PNG 图像。这里提供了一个解决方案,该方案使用控件适配器,在要显示的图像是 PNG 时输出不同的 HTML。

Sample Image - IePngControlAdapter.jpg

引言

如果您曾经在网站上处理过透明 PNG 图像,那么您一定很清楚 IE 5.5 和 IE 6 的显示问题。网络上存在多种解决方案。这里介绍一种使用控件适配器为 PNG 图像输出不同 HTML 的方法。

背景

上图显示了 IE 使用标准 IMG 标签显示透明 PNG 时遇到的问题

<img src="sample.png" width="180" height="130">

它只是不知道如何处理透明度信息。微软的解决方案解释了如何使用其 AlphaImageLoader 滤镜作为显示图像的替代方法。此滤镜自 5.5 版本起可供 IE 使用。他们建议为 IE 5.5/6 客户端渲染不同的 HTML。真是令人头疼。

现有解决方案

如果您在网上搜索此问题的解决方案,会找到不少。 其中最好的一个来自 Bob Osola。它使用 JavaScript 在客户端修改文档。太棒了!但是,它确实需要客户端启用 JavaScript。

让我们看看使用 ASP.NET 2.0 引入的一项新功能——控件适配器——来替代的解决方案。即使 Bob 的 JavaScript 方法对您有效,该解决方案也可能启发您以其他独特的方式使用控件适配器。

控件适配器解决方案

控件适配器允许您拦截控件的渲染功能并用自己的功能替换它们。Fritz Onion 在 2006 年 10 月的 MSDN 杂志有一篇精彩文章,详细介绍了控件适配器。我们将在这里进行更深入的探讨。

我们将编写一个控件适配器来适配 ASP.NET Image Web 控件的输出。让我们从查看代码开始

public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase))
    {
      base.Render(writer); // Have control render itself
      return;
    }

    writer.AddAttribute(HtmlTextWriterAttribute.Id, img.ClientID);
    writer.AddStyleAttribute(HtmlTextWriterStyle.Filter,
    "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
    Page.ResolveClientUrl(img.ImageUrl) + "')");

    if (!img.Width.IsEmpty)
      writer.AddStyleAttribute(HtmlTextWriterStyle.Width, 
                               img.Width.ToString());

    if (!img.Height.IsEmpty)
      writer.AddStyleAttribute(HtmlTextWriterStyle.Height, 
                               img.Height.ToString());

    writer.RenderBeginTag(HtmlTextWriterTag.Span);
    writer.RenderEndTag();
  }
}

我们从继承自 WebControlAdapter 的公共类开始,它包含一些基本的渲染实现,这些实现允许原始 Image 控件执行渲染。WebControlAdapter 类包含一个名为 Control 的重要成员,它指向原始 Image 控件。将 Control 成员转换为 Image 对象,我们可以访问其属性来按需输出我们自己的 HTML。

给定一个如下所示的图像标签

<asp:image id="Image1" width="180" height="130" 
           imageurl="Sample.png" runat="server" />

我们想输出这样的 HTML

<div id="Image1"
 style="filter:progid:DXImageTransform.
        Microsoft.AlphaImageLoader(src='Sample.png');
        width:180px;height:130px;">
</div>

但请注意,只有当图像 URL 引用带有 PNG 扩展名的文件时,我们才会更改输出。否则,我们将让原始 Image 控件自行渲染其输出。

集成

还有一个细节需要处理。我们需要配置网站,以便我们的控件适配器与标准的 Image 控件关联。我们还希望仅当客户端使用 IE5.5 或 IE6 时才使用此控件适配器。(IE7 据说支持原生透明 PNG。IE5.5 之前的版本没有 AlphaImageLoader 滤镜,因此无法正确显示 PNG。)要实现这一点,我们在 App_Browsers 文件夹中添加一个浏览器文件,其内容如下

<browsers>
  <browser refID="IE55">
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
  <browser id="IE6" parentID="IE6to9">
    <identification>
      <capability name="majorversion" match="6" />
    </identification>
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
</browsers>

有很多网络资源可以解释浏览器文件的格式。对我们来说,重要的部分是 controlAdapters,我们在此将适配器与标准的 Image 控件关联。第一个浏览器元素仅将控件适配器添加到由标准浏览器定义文件创建的 IE55 定义中。第二个浏览器元素过滤现有的定义 (IE6to9),以创建一个专用于 IE6 的新定义。

更好的想法

CodeProject 最令人谦卑的一点是,您能多快地意识到自己知道得多么少。这从未改变。写完一篇文章后,总会有人(通常非常友善地)指出解决问题的更好方法。

这篇文章就是这种情况。发布后不久,我收到了 Michael 和 Richard 的消息,他们指出使用 IMG 标签而不是 DIV 标签进行输出会容易得多。我们所要做的就是添加 AlphaImageLoader 滤镜,并将 ImageURL 属性替换为一个一像素的透明 GIF。然后,我们让 Image 控件像往常一样自行重新渲染。

public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase) &&
        oImage.Attributes["PNG"] != "true")
    {
      base.Render(writer); // Have control render itself
      return;
    }

    // Setup the filter 
    string strImageUrl = oImage.ImageUrl;
    oImage.Style.Add(HtmlTextWriterStyle.Filter,
      "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
      Page.ResolveClientUrl(strImageUrl) + "', sizingMethod='image')");

    // Replace the image with a 1 x 1 transparent gif
    oImage.ImageUrl = Page.ResolveClientUrl("~/Images/Spacer.gif");
    base.Render(writer); // Have control render itself
  }
}

注意事项

  • Michael 建议在滤镜中添加 sizingMethod='image',这样即使您没有明确包含宽度和高度,图像也会以其正常大小显示。
  • lofty_2 想要一种除了文件扩展名之外的方法来标记 PNG 图像。您可能正在动态生成 PNG 图像,例如图表或图形,因此可能根本没有扩展名。为了解决这个问题,我只是发明了一个 png 属性,您可以将其设置为 true。Visual Studio 会警告 Image 控件上这是一个无效属性,但该控件会愉快地将其收集并存储在 Attributes 集合中供我们在控件适配器中使用。

来自营销部门的呐喊

我前几天收到了一封来自我们营销部门某人的奇怪电子邮件。她说她收到了几封用户的电子邮件,他们找不到下载我们免费安全产品的下载链接。这似乎是极不可能的,因为我们网站上的几乎每一页顶部都有一个巨大的红色“立即下载”按钮。这些人怎么会错过它呢?

散步思考后,我产生了一个想法。我让我们的营销人员找出这些客户正在使用什么浏览器。他们使用的是 FireFox 或 Opera。一位客户甚至尝试了这两种浏览器,但仍然坚持说任何页面上都没有下载按钮。经过一番实验,我找到了问题所在。

我们的大红色下载按钮是一个 PNG 图像。似乎使用 FireFox 和 Opera 的这些客户更改了他们的用户代理字符串,使其精确复制 IE 6 发送的代理字符串。我的服务器无法知道实际使用的浏览器,而控件适配器正在渲染 IE 特定的 AlphaImageLoader 滤镜,FireFox 和 Opera 都不会对其进行处理或显示。

现在,我们可以抱怨用户这样篡改他们的代理字符串,但这并不能改变他们确实这样做的事实。也不能改变他们仍然期望您的网站完美显示的这一事实。如您所知,您的网站出现问题从来都不是用户的错。即使他们使用烤面包机来浏览互联网,也总是您的错!

更接近完美

我最近遇到了一个 IE“功能”,它允许您在 HTML 中添加条件,只有 IE 会识别。有些人将其归类为 hack,但我会简要指出,hack 的通常定义是利用浏览器缺陷。此新代码将使用微软计划继续支持的有效 IE 语法。话虽如此,它是这样工作的

<!--[if IE]>
  <span>This html gets displayed only by IE</span>
<![endif]-->
<![if !IE]>
  <span>This html gets displayed by every other browser</span>
<![endif]>

没什么可解释的。IE 将显示的 HTML 位于标准的 HTML 注释块内,因此非 IE 浏览器会忽略它。非 IE 浏览器显示的 HTML 由 IE 会解析但其他浏览器会忽略的标签界定。这里是从MSDN获取的所有详细信息。让我们修改控件适配器,以便在渲染 PNG 图像时使用这些特殊的 IE 条件。

public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase) &&
        oImage.Attributes["PNG"] != "true")
    {
      base.Render(writer); // Have control render itself
      return;
    }

    string strImageUrl = oImage.ImageUrl;

    writer.Write("<![if !lt IE 7]>");
    base.Render(writer); // Used by non-IE & IE7
    writer.Write("<![endif]>");

    oImage.Style.Add(HtmlTextWriterStyle.Filter,
      "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
      Page.ResolveClientUrl(strImageUrl) + "', sizingMethod='image')");
    oImage.ImageUrl = Page.ResolveClientUrl("~/Images/Spacer.gif");

    writer.Write("<!--[if lt IE 7]>");
    base.Render(writer); // Used by IE < 7
    writer.Write("<![endif]-->");
  }
}

我们的计划是将此特殊的 PNG 输出写入所有浏览器,而不仅仅是 IE。我们将依靠浏览器的渲染引擎和特殊的 IE 条件来显示正确的 HTML。我们不再需要依赖有效的用户代理字符串。用户可以随意更改代理字符串,他们仍然会正确看到 PNG 图像!

要实现这一点,还需要进行一项更改。我们之前的浏览器文件配置为仅 IE 客户端接收我们的特殊控件适配器输出。现在我们需要所有客户端浏览器都能收到此输出。这个新的浏览器文件将我们的控件适配器关联到现有的“default”组,即所有客户端浏览器

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
</browsers>

使用代码

代码下载包含一个小型 ASP.NET 2.0 网站,其中控件适配器位于 App_Code 文件夹中,还有一个 default.aspx 演示页面。我还为与 <img ... runat="server"> 标签一起使用的 HtmlImage 控件包含了一个控件适配器。这是我更喜欢的在页面上显示图像的标签。但是请注意,只有当您包含 runat="server" 属性时,才会调用控件适配器。否则,解析引擎只会将标签输出为未解析的 HTML。

© . All rights reserved.