C#实现的ActiveX截图打印控件

 C#开发ActiveX控件参考资料:

  http://www.cnblogs.com/zhf/archive/2009/03/02/1401299.html

  http://www.cnblogs.com/homer/archive/2005/01/08/88780.html

C#开发ActiveX的详细介绍见以上两篇文章,我只补充这两篇文章未说明的。

1.实现脚本调用ActiveX方法,必需将方法定义到一个接口中(该接口使用一个唯一的GUID,不能与ActiveX控件的GUID相同),然后在ActiveX方法即控件类中实现该接口中的方法。

2.在ActiveX中使用WebBrowser控件,不可直接从工具箱中拖出该控件使用,只需声明一个WebBrowser对象,在需要使用的时候实例化即可。不然在ActiveX中无法正常使用,即使在本地测试的时候没有问题,在页面中使用你会发现WebBrowser对象已经被释放。

3.在Windows 7下,必需以管理员权限运行VS,设置的COM自动注册才能顺利完成。

以我的代码为例:

代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace EstatePrintMapActiveX
{
//控件类,GUID可以在工具中生成,此处的GUID即为页面中引用控件时使用的CLASSID
//ComVisible(true)设置是否自动注册控件,设置项目属性中的“生成”项下的“为COM互操作注册”为勾选实现在本地自动注册(windows7需要以管理员运行该项目)
[Guid("1161E5A8-B698-4e57-8B56-B5B06B69FF4D"), ProgId("EstatePrintMapActiveX.UserControl1"),
ClassInterface(ClassInterfaceType.None), ComVisible(
true)]
public partial class UserControl1 : UserControl, IMonitor, IObjectSafety
{
#region 实现IObjectSafety 成员

public void GetInterfacceSafyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
{
pdwSupportedOptions
= 1;
pdwEnabledOptions
= 2;
}

public void SetInterfaceSafetyOptions(int riid, int dwOptionsSetMask, int dwEnabledOptions)
{
throw new NotImplementedException();
}

#endregion

System.Windows.Forms.WebBrowser webBrowser;

bool isDetailPrint = false;//默认打印多张
string stringUrl;//需要打印的页面的URL

public UserControl1()
{
InitializeComponent();
}

//供JS调用的方法(必需在IMonitor接口中定义,接口名可自定)
public void SetUrlText(string strUrl, string pra)
{
if (pra == "1")//多图页面,打印多张
{
stringUrl
= strUrl;
btnCapture.Text
= "启动楼层图打印控件";//通过脚本参数改变控件按钮文字
SetWebBrowser();
}
else if (pra == "2")
{
stringUrl
= strUrl;
SetWebBrowser();
isDetailPrint
= true;//详细页面,打印单张
}
else
{
//无平面图信息则隐藏控件
this.Visible = false;
this.Height = 0;
this.Width = 0;
Dispose(
true);
}
}

//声明webBrowserObj并初始化
private void SetWebBrowser()
{
try
{
WebBrowser webBrowserObj
= new WebBrowser();
webBrowserObj.Dock
= System.Windows.Forms.DockStyle.Fill;
webBrowserObj.Location
= new System.Drawing.Point(0, 0);
webBrowserObj.MinimumSize
= new System.Drawing.Size(20, 20);
webBrowserObj.Name
= "webBrowser";
webBrowserObj.ScrollBarsEnabled
= false;//不显示滚动条
webBrowserObj.Size = new System.Drawing.Size(1050, 97);
webBrowserObj.TabIndex
= 0;
webBrowserObj.Navigating
+= new System.Windows.Forms.WebBrowserNavigatingEventHandler(this.webBrowser_Navigating);//加载事件
webBrowserObj.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser_DocumentCompleted);//加载完成事件

webBrowser
= webBrowserObj;

webBrowser.Navigate(stringUrl);
//加载页面
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

//截图方法
private void btnCapture_Click(object sender, EventArgs e)
{
try
{
//截图
Bitmap bmp = Snapshot.TakeSnapshot(this.webBrowser.ActiveXInstance, new Rectangle(new Point(), webBrowser.Size));

////生成图片的方法,imgName为图片完整路径(含文件名)
//bmp.Save(imgName, System.Drawing.Imaging.ImageFormat.Bmp);
//bmp.Dispose();

Form printView
= new Form1(bmp, isDetailPrint);
printView.Show();
//在弹出的Form中显示截图及“打印”、“关闭”按钮

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

}

//加载完成记录高度和宽度、启动按钮可用
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
btnCapture.Enabled
= true;//“启动打印控件”按钮可用

//设置WebBrowser的Size,之后调用截图方法时要使用该Size以保证截的图为完整的
webBrowser.Size = new Size(int.Parse(webBrowser.Document.Body.GetAttribute("scrollwidth")),
int.Parse(webBrowser.Document.Body.GetAttribute("scrollHeight")));

}

//开始加载页面
private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
btnCapture.Enabled
= false;//初始加载时“启动打印控件”按钮不可用
}

}
}

 其中脚本方法接口和安全接口定义如下: 

代码
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace EstatePrintMapActiveX
{
[Guid(
"8FF612BC-4386-4629-B81A-E84AD7CB7AE7"), InterfaceType(ComInterfaceType.InterfaceIsDual), ComVisible(true)]
public interface IMonitor
{
void SetUrlText(string strUrl, string pra);
}

[Serializable, ComVisible(
true)]
public enum ObjectSafetyOptions
{
INTERFACESAFE_FOR_UNTRUSTED_CALLER
= 0x00000001,
INTERFACESAFE_FOR_UNTRUSTED_DATA
= 0x00000002,
INTERFACE_USES_DISPEX
= 0x00000004,
INTERFACE_USES_SECURITY_MANAGER
= 0x00000008
};

//// MS IObjectSafety Interface definition
//[ComImport(), Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
//public interface IObjectSafety
//{
// [PreserveSig]
// long GetInterfaceSafetyOptions(ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions);

// [PreserveSig]
// long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions);
//};

[ComImport, Guid(
"CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
[PreserveSig]
void GetInterfacceSafyOptions(
int riid,
out int pdwSupportedOptions,
out int pdwEnabledOptions);

[PreserveSig]
void SetInterfaceSafetyOptions(
int riid,
int dwOptionsSetMask,
int dwEnabledOptions);
}


}

截图方法(引自随飞:http://chinasf.cnblogs.com):  

代码
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace EstatePrintMapActiveX
{
/// <summary>
/// ActiveX 组件快照类
/// AcitveX 必须实现 IViewObject 接口
/// 作者:随飞
/// http://chinasf.cnblogs.com
/// chinasf@hotmail.com
/// </summary>
public static class Snapshot
{
/// <summary>
/// 取快照
/// </summary>
/// <param name="pUnknown">Com 对象</param>
/// <param name="bmpRect">图象大小</param>
/// <returns></returns>
public static Bitmap TakeSnapshot(object pUnknown, Rectangle bmpRect)
{
if (pUnknown == null)
return null;

//必须为com对象
if (!Marshal.IsComObject(pUnknown))
return null;

//IViewObject 接口
UnsafeNativeMethods.IViewObject ViewObject = null;
IntPtr pViewObject
= IntPtr.Zero;
//内存图
Bitmap pPicture = new Bitmap(bmpRect.Width, bmpRect.Height);
Graphics hDrawDC
= Graphics.FromImage(pPicture);
//获取接口
object hret = Marshal.QueryInterface(Marshal.GetIUnknownForObject(pUnknown),
ref UnsafeNativeMethods.IID_IViewObject, out pViewObject);

try
{
ViewObject
= Marshal.GetTypedObjectForIUnknown(pViewObject, typeof(UnsafeNativeMethods.IViewObject)) as UnsafeNativeMethods.IViewObject;
//调用Draw方法
ViewObject.Draw((int)DVASPECT.DVASPECT_CONTENT,
-1,
IntPtr.Zero,
null,
IntPtr.Zero,
hDrawDC.GetHdc(),
new NativeMethods.COMRECT(bmpRect),
null,
IntPtr.Zero,
0);
Marshal.Release(pViewObject);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}

//释放
hDrawDC.Dispose();
return pPicture;
}
}

}

打印方法: 

代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace EstatePrintMapActiveX
{
public partial class Form1 : Form
{
int PageCount;
int pageCountTmp;
bool isDetailPrint = false;
public Form1(Bitmap bmp, bool isDetailPrintPar)
{
InitializeComponent();

isDetailPrint
= isDetailPrintPar;

Image imgObj
= (Image)bmp;
pictureBox1.Height
= imgObj.Height;
pictureBox1.Width
= imgObj.Width;

panel1.Width
= pictureBox1.Width;
panel1.Height
= pictureBox1.Height;

this.Width = pictureBox1.Width;
this.Height = pictureBox1.Height;

panel1.Visible
= true;

pictureBox1.Image
= imgObj;
PageCount
= pictureBox1.Image.Height / 1500;
pageCountTmp
= PageCount;

btn.Visible
= true;
btnClose.Visible
= true;
}

//打印过程中调用
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
if (!isDetailPrint)//多页
{
if (PageCount >= 1)
{
e.HasMorePages
= true;//分页设置
e.Graphics.DrawImage(pictureBox1.Image,
new RectangleF(new Point(0, 20),
new Size(1050, 1500)),
new RectangleF(new Point(0, (pageCountTmp - PageCount) * 1500),
new Size(1050, 1500)),
GraphicsUnit.Pixel);

if (PageCount == 1)
{
e.HasMorePages
= false;//结束分页及打印
}
}
PageCount
--;//计数
}
else//单页
{
e.Graphics.DrawImage(pictureBox1.Image,
new Rectangle(new Point(0, 20),
new Size(int.Parse(Math.Round(pictureBox1.Image.Width * 0.8).ToString()),
int.Parse(Math.Round(pictureBox1.Image.Height * 0.8).ToString()))));
}
}

//关闭按钮事件
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
this.Dispose();
}

//打印按钮事件:调用打印方法
private void btn_Click(object sender, EventArgs e)
{
printDocument1.Print();
this.Visible = false;
this.Height = 0;
this.Width = 0;
Dispose(
true);
}
}
}

  

  如果想不使用ActiveX实现客户端截图打印,而是在页面后台接收需要打印的页面URL,使用WebBrowser的DrawToBitmap方法实现截图,会遇到两个问题。

即:“GDI+中发生一般性错误”、“对COM 组件的调用返回了错误 HRESULT E_FAIL”。

  详见我的CSDN问题贴:http://topic.csdn.net/u/20101012/18/a750440e-f472-497f-b666-d1b420ae8f7c.html

  另外,当你需要打印的页面中有VML图或者AJAX效果,或者在页面加载完成之后使用脚本修改过页面的效果的时候,后台方法中的Complete状态判断并不能保证截图时页面已经是最终的显示效果,所以截图效果会与页面的实践显示效果有较大差异,甚至截图为空白。

  而使用ActiveX控件,当页面加载完时“启动控件按钮”可用,单击按钮之后触发截图方法,在客户端完成截图和打印(不生成图片且截图与页面实际显示的效果一致),同时还可可避免以上问题。

  补充:

  被截图的页面必需是不需要登录即可查看的页面;

  该控件在客户端运行,需要.NET Framework的支持, 最好做成基于.NET Framework2.0,因为高版本的.NET Framework文件很大;

  本控件的实现初衷是为了打印页面中VML实现的图,因为VML不支持web打印,且实际验证发现jQuery的区域打印即类似$("div#printAreaDiv").printArea();这样的jQuery方法打印的是整个页面,且与页面实际显示的效果有差异(页面中有AJAX或者页面加载完之后有使用脚本对页面进行过修改的情况);

  关于ActiveX调用脚本方法,可参考:http://www.cnblogs.com/xiaoshatian/archive/2008/09/02/1281786.html

  关于怎么制作安装文件,本篇文章给出的第一个链接里“5.发布”部分有详细的介绍;  

  控件使用示例:

HTML:

代码
<div>
<object id="UserControlObj" name="UserControlObj" classid="clsid:1161E5A8-B698-4E57-8B56-B5B06B69FF4D"
width
="950" height="55">
</object>
</div>

脚本:

代码
var strUrl = "";
var strPar = "2";
strUrl
= window.location.href.split('=')[0].replace("RoomInfoDetail.aspx?&RM_ID",
"RoomInfoPrint.aspx?rid=" + window.location.search.split('=')[1]);
document.all.UserControlObj.SetUrlText(strUrl, strPar);

  控件代码已经打包上传,下载链接:EstatePrintMapActiveX.rar

  文章结尾分享一下CSDN上ActiveX控件开发的讨论帖:

  http://topic.csdn.net/u/20101028/11/085a5e12-ab46-4de5-8860-07234638b57b.html

原文地址:https://www.cnblogs.com/xuezhizhang/p/1887735.html