来自蜡人张:RDLC报表(五)
随着Visual Studio 2005中文版的推出,Microsoft汉化了MSDN的大部分内容,开发者再也不用啃英文了,本来想介绍一下LocalReport的Render方法,现在您可以到http://msdn2.microsoft.com/zh-cn/library/ms252207(VS.80).aspx获 得关于这部分的详细信息。之所以以前想介绍这个方法,是因为我将想大家介绍一种在Crystal Report中无法实现的自定义票据打印纸张的方法。Anyway,现在我直接向大家介绍这种方法,可能这种方法并不是很好的,但是确实是我经过一段时间 的摸索总结出来的。萝卜(http://luobos.cnblogs.com)曾经提到过的变通的方法不知道是不是我要介绍的这一种,欢迎和我进行交流!
要想使用RDLC报表并进行页面设置,我们先来看一下LocalReport是否有类似PageSettings的类、属性、方法或事件等,我仔细找了一 下,发现Microsoft.Reporting.WinForms.ReportPageSettings类具有PaperSize属性和Margin 属性,但可惜的是它们都是只读的,对我们来说没有意义;另外,LocalReport具有GetDefaultPageSettings()方法,这也只 能是获取当前报表的页面设置。没办法,只能采用变通的方法了。在.NET中如果想使用自定义纸张,最好的方法莫过于使用 System.Drawing.Printing.PrintDocument类了,还记得我在前面提到的一个GotReportViewer的例子吗?
1 private int m_currentPageIndex; 2 private IList<Stream> m_streams; 3 4 private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek) 5 { 6 Stream stream = new FileStream(name + "." + fileNameExtension, FileMode.Create); 7 m_streams.Add(stream); 8 return stream; 9 } 10 11 private void Export(LocalReport report) 12 { 13 string deviceInfo = 14 "<DeviceInfo>" + 15 " <OutputFormat>EMF</OutputFormat>" + 16 " <PageWidth>8.5in</PageWidth>" + 17 " <PageHeight>11in</PageHeight>" + 18 " <MarginTop>0.25in</MarginTop>" + 19 " <MarginLeft>0.25in</MarginLeft>" + 20 " <MarginRight>0.25in</MarginRight>" + 21 " <MarginBottom>0.25in</MarginBottom>" + 22 "</DeviceInfo>"; 23 Warning[] warnings; 24 m_streams = new List<Stream>(); 25 report.Render("Image", deviceInfo, CreateStream, out warnings); 26 27 foreach (Stream stream in m_streams) 28 stream.Position = 0; 29 } 30 31 private void PrintPage(object sender, PrintPageEventArgs ev) 32 { 33 Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]); 34 ev.Graphics.DrawImage(pageImage, ev.PageBounds); 35 36 m_currentPageIndex++; 37 ev.HasMorePages = (m_currentPageIndex < m_streams.Count); 38 } 39 40 private void Print() 41 { 42 const string printerName = "Microsoft Office Document Image Writer"; 43 44 if (m_streams == null || m_streams.Count == 0) 45 return; 46 47 PrintDocument printDoc = new PrintDocument(); 48 printDoc.PrinterSettings.PrinterName = printerName; 49 if (!printDoc.PrinterSettings.IsValid) 50 { 51 string msg = String.Format("Can't find printer \"{0}\".", printerName); 52 Console.WriteLine(msg); 53 return; 54 } 55 printDoc.PrintPage += new PrintPageEventHandler(PrintPage); 56 printDoc.Print(); 57 } 58 59 private void Run() 60 { 61 LocalReport report = new LocalReport(); 62 report.ReportPath = "Report.rdlc"; 63 report.DataSources.Add(new ReportDataSource("Sales", LoadSalesData())); 64 65 Export(report); 66 67 m_currentPageIndex = 0; 68 Print(); 69 }
对,就是那个通过命令行而不是ReportViewer的GUI界面进行打印报表的例子,这个例子就使用LocalReport的Render方法将报表 的内容导出为EMF图像流,然后在PrintDocument的PrintPage事件中使用时事件参数 System.Drawing.Printing.PrintEventArgs类的DrawImage方法将EMF图像流输出到打印机。我在上面说的变 通的方法也要使用这种方法。具体的细节将在以后的随笔中陆续给出。
既然我们使用这种方法进行报表的打印,那么Visual Studio的控件ReportViewer的工具栏就不再符合我们的要求了。因为这个报表浏览器的工具栏上的按钮虽然可以设置属性显示或隐藏其中的一部 分,但是我们却不能自己往这个工具栏上添加按钮(显然,我们需要实现自己的页面设置、预览和打印按钮),在这一点上,建议Microsoft将工具栏和报 表浏览器分离,应该做得和BindingNavigator那样就好了。
我们先设置ReportViewer控件的ShowToolBar方法为false,然后在ReportViewer控件纸上添加除页面设置、预览、打印外的应该有的按钮,像刷新、终止、导出、缩放、搜索、导航等,这些按钮的Click事件定义如下:
1 /// <summary> 2 /// 获取当前时间组成的字符串,用作生成不会重复的文件名 3 /// </summary> 4 /// <returns></returns> 5 private string GetTimeStamp() 6 { 7 string strRet = string.Empty; 8 System.DateTime dtNow = Pub.DateTimeEx.ServerTime; 9 strRet += dtNow.Year.ToString() + 10 dtNow.Month.ToString("00") + 11 dtNow.Day.ToString("00") + 12 dtNow.Hour.ToString("00") + 13 dtNow.Minute.ToString("00") + 14 dtNow.Second.ToString("00") + 15 System.DateTime.Now.Millisecond.ToString("000"); 16 return strRet; 17 18 } 19 20 /// <summary> 21 /// 导出到Excel 22 /// </summary> 23 /// <param name="sender"></param> 24 /// <param name="e"></param> 25 private void toolExcel_Click(object sender, EventArgs e) 26 { 27 28 Microsoft.Reporting.WinForms.Warning[] Warnings; 29 string[] strStreamIds; 30 string strMimeType; 31 string strEncoding; 32 string strFileNameExtension; 33 34 byte[] bytes = this.reportViewer1.LocalReport.Render("Excel", null, out strMimeType, out strEncoding, out strFileNameExtension, out strStreamIds, out Warnings); 35 36 string strFilePath = @"D:\" + this.GetTimeStamp() + ".xls"; 37 38 using (System.IO.FileStream fs = new FileStream(strFilePath, FileMode.Create)) 39 { 40 fs.Write(bytes, 0, bytes.Length); 41 } 42 43 if (Pub.WinForm.Msg.Question("报表打印: \r\n 成功导出Excel文件!" + strFilePath + "\r\n 要现在打开文件" + strFilePath + "吗?") == DialogResult.Yes) 44 { 45 System.Diagnostics.Process.Start(strFilePath); 46 } 47 48 } 49 50 /// <summary> 51 /// 刷新报表数据 52 /// </summary> 53 /// <param name="sender"></param> 54 /// <param name="e"></param> 55 private void tool刷新_Click(object sender, EventArgs e) 56 { 57 this.reportViewer1.RefreshReport(); 58 } 59 60 /// <summary> 61 /// 在加载报表数据时终止报表数据的加载 62 /// </summary> 63 /// <param name="sender"></param> 64 /// <param name="e"></param> 65 private void tool终止_Click(object sender, EventArgs e) 66 { 67 this.reportViewer1.CancelRendering(0); 68 } 69 70 /// <summary> 71 /// 从DrillThrough报表返回到导航页面 72 /// </summary> 73 /// <param name="sender"></param> 74 /// <param name="e"></param> 75 private void tool返回_Click(object sender, EventArgs e) 76 { 77 if (this.reportViewer1.LocalReport.IsDrillthroughReport) 78 this.reportViewer1.PerformBack(); 79 } 80 81 /// <summary> 82 /// 回到报表的第一页 83 /// </summary> 84 /// <param name="sender"></param> 85 /// <param name="e"></param> 86 private void tool第一页_Click(object sender, EventArgs e) 87 { 88 this.reportViewer1.CurrentPage = 1; 89 } 90 91 /// <summary> 92 /// 跳转到报表的最后一页 93 /// </summary> 94 /// <param name="sender"></param> 95 /// <param name="e"></param> 96 private void tool最后一页_Click(object sender, EventArgs e) 97 { 98 this.reportViewer1.CurrentPage = this.reportViewer1.LocalReport.GetTotalPages(); 99 } 100 101 /// <summary> 102 /// 以25%的比例显示报表 103 /// </summary> 104 /// <param name="sender"></param> 105 /// <param name="e"></param> 106 private void tool25_Click(object sender, EventArgs e) 107 { 108 this.reportViewer1.ZoomMode = ZoomMode.Percent; 109 this.reportViewer1.ZoomPercent = 25; 110 } 111 112 /// <summary> 113 /// 以50%的比例显示报表 114 /// </summary> 115 /// <param name="sender"></param> 116 /// <param name="e"></param> 117 private void tool50_Click(object sender, EventArgs e) 118 { 119 this.reportViewer1.ZoomMode = ZoomMode.Percent; 120 this.reportViewer1.ZoomPercent = 50; 121 } 122 123 /// <summary> 124 /// 以100%的比例显示报表 125 /// </summary> 126 /// <param name="sender"></param> 127 /// <param name="e"></param> 128 private void tool100_Click(object sender, EventArgs e) 129 { 130 this.reportViewer1.ZoomMode = ZoomMode.Percent; 131 this.reportViewer1.ZoomPercent = 100; 132 } 133 134 /// <summary> 135 /// 以200%的比例显示报表 136 /// </summary> 137 /// <param name="sender"></param> 138 /// <param name="e"></param> 139 private void tool200_Click(object sender, EventArgs e) 140 { 141 this.reportViewer1.ZoomMode = ZoomMode.Percent; 142 this.reportViewer1.ZoomPercent = 200; 143 } 144 145 /// <summary> 146 /// 以400%的比例显示报表 147 /// </summary> 148 /// <param name="sender"></param> 149 /// <param name="e"></param> 150 private void tool400_Click(object sender, EventArgs e) 151 { 152 this.reportViewer1.ZoomMode = ZoomMode.Percent; 153 this.reportViewer1.ZoomPercent = 400; 154 } 155 156 /// <summary> 157 /// 将缩放模式设置为整页 158 /// </summary> 159 /// <param name="sender"></param> 160 /// <param name="e"></param> 161 private void tool整页_Click(object sender, EventArgs e) 162 { 163 this.reportViewer1.ZoomMode = ZoomMode.FullPage; 164 } 165 166 /// <summary> 167 /// 将缩放模式设置为页宽 168 /// </summary> 169 /// <param name="sender"></param> 170 /// <param name="e"></param> 171 private void tool页宽_Click(object sender, EventArgs e) 172 { 173 this.reportViewer1.ZoomMode = ZoomMode.PageWidth; 174 } 175 176 /// <summary> 177 /// 在报表中搜索txtSearch中的字符 178 /// </summary> 179 /// <param name="sender"></param> 180 /// <param name="e"></param> 181 private void tool搜索_Click(object sender, EventArgs e) 182 { 183 if (this.txtSearch.Text.Trim() == string.Empty) 184 return; 185 186 this.reportViewer1.Find(this.txtSearch.Text.Trim(), 1); 187 } 188 189 /// <summary> 190 /// 搜索报表中下一处txtSearch中的字符 191 /// </summary> 192 /// <param name="sender"></param> 193 /// <param name="e"></param> 194 private void tool搜索下一个_Click(object sender, EventArgs e) 195 { 196 if (this.txtSearch.Text.Trim() == string.Empty) 197 return; 198 199 this.reportViewer1.FindNext(); 200 } 201 202 /// <summary> 203 /// 跳转到上一页 204 /// </summary> 205 /// <param name="sender"></param> 206 /// <param name="e"></param> 207 private void tool上一页_Click(object sender, EventArgs e) 208 { 209 if (this.reportViewer1.CurrentPage != 1) 210 this.reportViewer1.CurrentPage--; 211 } 212 213 /// <summary> 214 /// 跳转到下一页 215 /// </summary> 216 /// <param name="sender"></param> 217 /// <param name="e"></param> 218 private void tool下一页_Click(object sender, EventArgs e) 219 { 220 if (this.reportViewer1.CurrentPage != this.reportViewer1.LocalReport.GetTotalPages()) 221 this.reportViewer1.CurrentPage++; 222 } 223 224 /// <summary> 225 /// 跳转到由txt跳转中指定的页数 226 /// </summary> 227 /// <param name="sender"></param> 228 /// <param name="e"></param> 229 private void tool跳转_Click(object sender, EventArgs e) 230 { 231 if (this.txt跳转.Text.Trim() == string.Empty) 232 return; 233 234 int intJump = 0; 235 236 if (System.Int32.TryParse(this.txt跳转.Text.Trim(), out intJump)) 237 if (intJump <= this.reportViewer1.LocalReport.GetTotalPages()) 238 this.reportViewer1.CurrentPage = intJump; 239 240 }
来自蜡人张:RDLC报表(六)
你可能已经注意到了在调用LocalReport的Render方法时用到了一个XML格式的DeviceInfo结构,在SQL Server 2005 Report Services中,DeviceInfo结构是为了给特定的呈现格式传递参数。来看一个简单的DeviceInfo结构:
<OutputFormat>EMF</OutputFormat>
<PageWidth>21cm</PageWidth>
<PageHeight>29.70cm</PageHeight>
<MarginTop>2cm</MarginTop>
<MarginLeft>2cm</MarginLeft>
<MarginRight>2cm</MarginRight>
<MarginBottom>2cm</MarginBottom>
</DeviceInfo>
这个简单的DeviceInfo结构至少为LocalReport的Render方法指定了输出格式、页宽、页高、左边距、右边距、下边距信息,在我们使 用PrintPage的方法将LocalReport呈现为EMF图片时,EMF图片在页面上显示的大小、边距就是由这个DeviceInfo结构来决定 的,如果为DeviceInfo结构和PrintDocumnt设置不匹配的页面大小或边距,那么在PrintPage事件中使用DrawImage方法 画出的图片将出现放大或缩小的情况,这是我们不愿意看到的结果。也就是说,在使用自定义纸张进行单据打印时,我们不仅要为PrintDocument设置 页面大小和边距,还要为LocalReport设置与PrintDocument相同的页面大小和边距。关于DeviceInfo的结构,可以参考http://msdn2.microsoft.com/zh-cn/library/ms155373.aspx
下面是我封装的一个为生成DeviceInfo结构使用的类:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace RDLCReport 6 { 7 public class EMFDeviceInfo 8 { 9 private bool m_Landscape = false; 10 11 public bool Landscape 12 { 13 get 14 { 15 return this.m_Landscape; 16 } 17 set 18 { 19 this.m_Landscape = value; 20 } 21 } 22 23 /* 24 * The pixel depth of the color range supported by the image output. 25 * Valid values are 1, 4, 8, 24, and 32. 26 * The default value is 24. 27 * ColorDepth is only supported for TIFF rendering and is otherwise ignored by the report server for other image output formats. 28 * Note: 29 * For this release of SQL Server, the value of this setting is ignored, and the TIFF image is always rendered as 24-bit. 30 * 31 * 默认值为24,且只有当输出格式为TIFF时才该项设置才起作用 32 * 33 */ 34 private int m_ColorDepth = 24; 35 36 public int ColorDepth 37 { 38 get 39 { 40 return this.m_ColorDepth; 41 } 42 } 43 44 45 /* 46 * The number of columns to set for the report. This value overrides the report's original settings. 47 * 48 * 未用到此项设置 49 * 50 */ 51 private int m_Columns = 0; 52 53 public int Columns 54 { 55 get 56 { 57 return this.m_Columns; 58 } 59 set 60 { 61 this.m_Columns = value; 62 } 63 } 64 65 66 /* 67 * The column spacing to set for the report. This value overrides the report's original settings. 68 * 69 * 未用到此项设置 70 * 71 */ 72 private int m_ColumnSpacing = 0; 73 74 public int ColumnSpacing 75 { 76 get 77 { 78 return this.m_ColumnSpacing; 79 } 80 set 81 { 82 this.m_ColumnSpacing = value; 83 } 84 } 85 86 /* 87 * The resolution of the output device in x-direction. The default value is 96. 88 * 89 * 解析度,默认值为96 90 * 91 */ 92 private int m_DpiX = 96; 93 94 public int DpiX 95 { 96 get 97 { 98 return this.m_DpiX; 99 } 100 set 101 { 102 this.m_DpiX = value; 103 } 104 } 105 106 107 /* 108 * The resolution of the output device in y-direction. The default value is 96. 109 * 110 * 解析度,默认值为96 111 * 112 */ 113 private int m_DpiY = 96; 114 115 public int DpiY 116 { 117 get 118 { 119 return this.m_DpiY; 120 } 121 set 122 { 123 this.m_DpiY = value; 124 } 125 } 126 127 128 /* 129 * The last page of the report to render. The default value is the value for StartPage. 130 * 131 * 要输出的报表的最后一页 132 * 133 */ 134 private int m_EndPage = 0; 135 136 public int EndPage 137 { 138 get 139 { 140 return this.m_EndPage; 141 } 142 set 143 { 144 this.m_EndPage = value; 145 } 146 } 147 148 149 /* 150 * The first page of the report to render. A value of 0 indicates that all pages are rendered. The default value is 1. 151 * 152 * 起始页,0代表所有页面都将输出,默认值为1。 153 * 154 */ 155 private int m_StartPage = 1; 156 157 public int StartPage 158 { 159 get 160 { 161 return this.m_StartPage; 162 } 163 set 164 { 165 this.m_StartPage = value; 166 } 167 } 168 169 /* 170 * The bottom margin value, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 1in). This value overrides the report's original settings. 171 * 172 * 底部边距,必须加上单位如"in" 173 * 174 */ 175 private decimal m_MarginBottom = 0; 176 177 public decimal MarginBottom 178 { 179 get 180 { 181 return this.m_MarginBottom; 182 } 183 set 184 { 185 this.m_MarginBottom = value; 186 } 187 } 188 189 190 /* 191 * The top margin value, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 1in). This value overrides the report's original settings. 192 * 193 * 顶部边距,必须加上单位如"in" 194 * 195 */ 196 private decimal m_MarginTop = 0; 197 198 public decimal MarginTop 199 { 200 get 201 { 202 return this.m_MarginTop; 203 } 204 set 205 { 206 this.m_MarginTop = value; 207 } 208 } 209 210 211 /* 212 * The left margin value, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 1in). This value overrides the report's original settings. 213 * 214 * 左边距,必须加上单位如"in" 215 * 216 */ 217 private decimal m_MarginLeft = 0; 218 219 public decimal MarginLeft 220 { 221 get 222 { 223 return this.m_MarginLeft; 224 } 225 set 226 { 227 this.m_MarginLeft = value; 228 } 229 } 230 231 232 /* 233 * The right margin value, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 1in). This value overrides the report's original settings. 234 * 235 * 右边距,必须加上单位如"in" 236 * 237 */ 238 private decimal m_MarginRight = 0; 239 240 public decimal MarginRight 241 { 242 get 243 { 244 return this.m_MarginRight; 245 } 246 set 247 { 248 this.m_MarginRight = value; 249 } 250 } 251 252 /* 253 * One of the Graphics Device Interface (GDI) supported output formats: BMP, EMF, GIF, JPEG, PNG, or TIFF. 254 * 255 * 图形设备接口(GDI)支持的一种输出格式,可以是BMP, EMF, GIF, JPEG, PNG, 或 TIFF. 256 * 此处使用EMF 257 */ 258 private string m_OutputFormat = "EMF"; 259 260 public string OutputFormat 261 { 262 get 263 { 264 return this.m_OutputFormat; 265 } 266 set 267 { 268 this.m_OutputFormat = value; 269 } 270 } 271 272 /* 273 * The page height, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 11in). This value overrides the report's original settings. 274 * 275 * 页面高度,必须加上单位如"in" 276 * 277 */ 278 private decimal m_PageHeight = 0; 279 280 public decimal PageHeight 281 { 282 get 283 { 284 return this.m_PageHeight; 285 } 286 set 287 { 288 this.m_PageHeight = value; 289 } 290 } 291 292 293 /* 294 * The page width, in inches, to set for the report. You must include an integer or decimal value followed by "in" (for example, 8.5in). This value overrides the report's original settings. 295 * 296 * 页面宽度,必须加上单位如"in" 297 * 298 */ 299 private decimal m_PageWidth = 0; 300 301 public decimal PageWidth 302 { 303 get 304 { 305 return this.m_PageWidth; 306 } 307 set 308 { 309 this.m_PageWidth = value; 310 } 311 } 312 313 /// <summary> 314 /// 返回包含DeviceInfo的字符串 315 /// </summary> 316 public string DeviceInfoString 317 { 318 get 319 { 320 string strRet = string.Empty; 321 322 strRet += "<DeviceInfo>" + 323 " <OutputFormat>" + this.m_OutputFormat + "</OutputFormat>"; 324 325 if (this.m_Landscape) 326 strRet += 327 " <PageWidth>" + this.m_PageHeight.ToString() + "cm</PageWidth>" + 328 " <PageHeight>" + this.m_PageWidth.ToString() + "cm</PageHeight>"; 329 else 330 strRet += 331 " <PageWidth>" + this.m_PageWidth.ToString() + "cm</PageWidth>" + 332 " <PageHeight>" + this.m_PageHeight.ToString() + "cm</PageHeight>"; 333 334 strRet += 335 " <MarginTop>" + this.m_MarginTop.ToString() + "cm</MarginTop>" + 336 " <MarginLeft>" + this.m_MarginLeft.ToString() + "cm</MarginLeft>" + 337 " <MarginRight>" + this.m_MarginRight.ToString() + "cm</MarginRight>" + 338 " <MarginBottom>" + this.m_MarginBottom.ToString() + "cm</MarginBottom>"; 339 340 strRet += "</DeviceInfo>"; 341 342 return strRet; 343 344 } 345 346 } 347 } 348 }
好了,解决了DeviceInfo,现在来看一下如何在PrintDocument的PrintPage事件中向打印机输出由LocalReport呈现 的EMF图片。使用的方法基本上就是在GotReportViewer的例程Print a report from a console app中使用的方法,但是需要指出的一点是例程中使用事件参数System.Drawing.Printing.PrintPageEventArgs类 的Graphics属性的DrawImage方法向打印机输出EMF图片,在实际的应用中,发现DrawImage方法绘出的图片会出现放大或缩小的情 况,即使为DrawImage方法指定了看起来正确的参数 ev.Graphics.DrawImageUnscaledAndClipped(this.m_PageImage, ev.PageBounds);,我使用的方法是DrawImageUnscaledAndClipped,在为DeviceInfo结构和 PrintDocument指定好适当且匹配的页面设置时,输出的结果是比较好的。