來自蠟人張: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的例子嗎?
View Code
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事件定義如下:
View Code
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結構:
<
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結構使用的類:
View Code
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指定好適當且匹配的頁面設置時,輸出的結果是比較好的。
