321 Chương 8: Đồ họa, đa phương tiện, và in ấn 13. 13. In văn b n đ n gi nả ơ ả In văn b n đ n gi nả ơ ả Bạn cần in text hoặc hình. Thụ lý sự kiện PrintDocument.PrintPage , và sử dụng các phương thức DrawString và DrawImage của lớp Graphics để in dữ liệu ra trang. .NET sử dụng mô hình in dựa-trên-sự-kiện bất đồng bộ (asynchronous event-based printing model). Để in một văn bản, bạn cần tạo một đối tượng System.Drawing.Printing.PrintDocument , cấu hình các thuộc tính của nó, và rồi gọi phương thức Print để thực hiện tác vụ in. Bộ thực thi sẽ phát sinh các sự kiện BeginPrint , PrintPage , và EndPrint của lớp PrintDocument trên một tiểu trình mới. Bạn thụ lý các sự kiện này và sử dụng đối tượng System.Drawing.Graphics để xuất dữ liệu ra trang. Hình và text được ghi ra trang theo cùng cách như bạn vẽ lên cửa sổ bằng GDI+. Tuy nhiên, bạn có thể cần phải theo vết vị trí của bạn trên trang, vì mọi phương thức của lớp Graphics đều yêu cầu tọa độ chỉ định nơi cần vẽ. Các thiết lập cho máy in được cấu hình thông qua các thuộc tính PrintDocument.PrinterSettings và PrintDocument.DefaultPageSettings . Thuộc tính PrinterSettings trả về một đối tượng PrinterSettings (đã được mô tả trong mục 8.12) cho biết máy in sẽ được sử dụng. Thuộc tính DefaultPageSettings cung cấp đối tượng PageSettings cho biết độ phân giải (resolution), lề (margin), hướng trang (orientation) Bạn có thể cấu hình các thuộc tính này trong mã lệnh, hoặc bạn có thể sử dụng lớp System.Windows.Forms.PrintDialog để người dùng thực hiện các thay đổi thông qua hộp thoại in chuẩn của Windows (xem hình 8.9). Trong hộp thoại in, người dùng có thể chọn một máy in và chọn số lượng bản in (number of copies). Người dùng cũng có thể nhắp vào nút Properties để cấu hình các thiết lập nâng cao như cách bố trí trang (layout) và độ phân giải máy in (resolution). Cuối cùng, người dùng có thể chấp thuận hoặc hủy bỏ thao tác in bằng cách nhắp OK hoặc Cancel. Hình 8.9 Hộp thoại Print 322 Chương 8: Đồ họa, đa phương tiện, và in ấn Trước khi sử dụng lớp PrintDialog , bạn phải gắn nó vào đối tượng PrintDocument bằng cách thiết lập thuộc tính PrintDialog.Document . Theo đó, bất kỳ sự thay đổi nào do người dùng thực hiện trong hộp thoại in sẽ tự động được áp dụng vào đối tượng PrintDocument . Ví dụ dưới đây là một form chỉ chứa một nút lệnh. Khi người dùng nhắp vào nút này, ứng dụng sẽ tạo một đối tượng PrintDocument mới, cho phép người dùng cấu hình các thiết lập in, và rồi bắt đầu thao tác in bất đồng bộ. Phương thức thụ lý sự kiện đáp ứng cho sự kiện PrintPage sẽ ghi ra nhiều dòng text và một bức hình. using System; using System.Windows.Forms; using System.Drawing; using System.Drawing.Printing; public class SimplePrint : System.Windows.Forms.Form { private System.Windows.Forms.Button cmdPrint; // (Bỏ qua phần mã designer.) private void cmdPrint_Click(object sender, System.EventArgs e) { // Tạo một văn bản và gắn vào phương thức thụ lý sự kiện. PrintDocument doc = new PrintDocument(); doc.PrintPage += new PrintPageEventHandler(this.Doc_PrintPage); // Cho phép người dùng chọn một máy in // và chỉ định các thiết lập khác. PrintDialog dlgSettings = new PrintDialog(); dlgSettings.Document = doc; // Nếu người dùng nhắp OK thì in văn bản. if (dlgSettings.ShowDialog() == DialogResult.OK) { // Phương thức này trả về tức thì, trước khi tác vụ in // bắt đầu. Sự kiện PrintPage sẽ phát sinh bất đồng bộ. doc.Print(); } 323 Chương 8: Đồ họa, đa phương tiện, và in ấn } private void Doc_PrintPage(object sender, PrintPageEventArgs e) { // Định nghĩa font. Font font = new Font("Tahoma", 30); // Xác định vị trí trên trang. // Trong trường hợp này, chúng ta đọc các thiết lập lề // (mặc dù không có gì ngăn chúng ta vượt qua biên lề). float x = e.MarginBounds.Left; float y = e.MarginBounds.Top; // Xác định chiều cao của một dòng (dựa trên font được sử dụng). float lineHeight = font.GetHeight(e.Graphics); // In năm dòng text. for (int i=0; i < 5; i++) { // Vẽ text với bút vẽ đen, sử dụng font và // tọa độ mà chúng ta đã xác định. e.Graphics.DrawString("Thập Diện Mai Phục " + i.ToString(), font, Brushes.Black, x, y); // Dịch xuống một dòng. y += lineHeight; } y += lineHeight; // Vẽ hình. e.Graphics.DrawImage(Image.FromFile(Application.StartupPath + "\\test.bmp"), x, y); } } Ví dụ này có một hạn chế là nó chỉ in một trang đơn. Để in các văn bản phức tạp và nhiều trang hơn, bạn cần phải tạo một lớp chuyên biệt để đóng gói các thông tin về văn bản, trang hiện hành Kỹ thuật này sẽ được trình bày trong mục 8.14. 324 Chương 8: Đồ họa, đa phương tiện, và in ấn Hình 8-10 Văn bản đã được in (sử dụng máy in Adobe PDF) 14. 14. In văn b n có nhi u trangả ề In văn b n có nhi u trangả ề Bạn cần in các văn bản phức tạp gồm nhiều trang và in nhiều văn bản khác nhau cùng một lúc. Đặt thông tin muốn in vào một lớp tùy biến dẫn xuất từ PrintDocument , và thiết lập thuộc tính PrintPageEventArgs.HasMorePages là true trong khi vẫn còn trang để in. Sự kiện PrintDocument.PrintPage cho phép bạn chỉ in một trang đơn. Nếu muốn in nhiều trang hơn, bạn cần thiết lập thuộc tính PrintPageEventArgs.HasMorePages là true trong phương thức thụ lý sự kiện PrintPage . Trong khi HasMorePages là true , lớp PrintDocument vẫn tiếp tục phát sinh các sự kiện PrintPage (một sự kiện cho một trang). Tuy nhiên, bạn cần biết là đang in đến trang thứ mấy, dữ liệu gì sẽ được in trên mỗi trang Để thực hiện điều này, cách hay nhất là tạo một lớp tùy biến. Lớp TextDocument dưới đây thừa kế từ PrintDocument và thêm ba thuộc tính: Text lưu trữ một mảng các dòng text, PageNumber cho biết trang vừa được in, và Offset cho biết dòng vừa được in (trong mảng Text ). public class TextDocument : PrintDocument { private string[] text; 325 Chương 8: Đồ họa, đa phương tiện, và in ấn private int pageNumber; private int offset; public string[] Text { get {return text;} set {text = value;} } public int PageNumber { get {return pageNumber;} set {pageNumber = value;} } public int Offset { get {return offset;} set {offset = value;} } public TextDocument(string[] text) { this.Text = text; } } Tùy thuộc vào kiểu tài liệu muốn in, bạn có thể chỉnh sửa lớp này. Ví dụ, bạn có thể lưu trữ một mảng gồm các dữ liệu hình, một vài nội dung sẽ được sử dụng làm header hoặc footer trên mỗi trang, thông tin về font, hoặc tên của file mà bạn muốn đọc thông tin từ nó. Gói các thông tin này vào một lớp đơn sẽ khiến cho việc in nhiều văn bản cùng một lúc dễ dàng hơn. Phần mã khởi đầu cũng giống như mục 8.13, chỉ khác là bây giờ tạo đối tượng TextDocument (thay vì tạo đối tượng PrintDocument ). Phương thức thụ lý sự kiện PrintPage giữ vết của dòng hiện hành và kiểm tra có còn chỗ trống trên trang hay không trước khi thực hiện in dòng kế tiếp. Nếu cần trang mới, thuộc tính HasMorePages được thiết lập là true và sự kiện PrintPage phát sinh lần nữa cho trang kế tiếp. Nếu không, thao tác in xem như hoàn tất. Phần mã đầy đủ cho form được trình bày dưới đây: using System; using System.Windows.Forms; using System.Drawing; using System.Drawing.Printing; public class MultiPagePrint : System.Windows.Forms.Form { 326 Chương 8: Đồ họa, đa phương tiện, và in ấn private System.Windows.Forms.Button cmdPrint; // (Bỏ qua phần mã designer.) private void cmdPrint_Click(object sender, System.EventArgs e) { // Tạo văn bản gồm 100 dòng. string[] printText = new string[100]; for (int i=0; i < 100; i++) { printText[i] = i.ToString(); printText[i] += ": Thập Diện Mai Phục (House of Flying Daggers)"; } PrintDocument doc = new TextDocument(printText); doc.PrintPage += new PrintPageEventHandler(this.Doc_PrintPage); PrintDialog dlgSettings = new PrintDialog(); dlgSettings.Document = doc; // Nếu người dùng nhắp OK thì in văn bản. if (dlgSettings.ShowDialog() == DialogResult.OK) { doc.Print(); } } private void Doc_PrintPage(object sender, PrintPageEventArgs e) { // Thu lấy văn bản đã gửi sự kiện này. TextDocument doc = (TextDocument)sender; // Định nghĩa font và xác định chiều cao. Font font = new Font("Tahoma", 10); float lineHeight = font.GetHeight(e.Graphics); 327 Chương 8: Đồ họa, đa phương tiện, và in ấn // Tạo các biến lưu giữ vị trí trên trang. float x = e.MarginBounds.Left; float y = e.MarginBounds.Top; // Tăng biến đếm cho trang (cho biết số trang đã được in). doc.PageNumber += 1; // In tất cả thông tin vừa khít trên trang. // Vòng lặp này kết thúc khi dòng kế tiếp vượt quá biên lề, // hoặc không còn dòng nào để in. while ((y + lineHeight) < e.MarginBounds.Bottom && doc.Offset <= doc.Text.GetUpperBound(0)) { e.Graphics.DrawString(doc.Text[doc.Offset], font, Brushes.Black, x, y); // Dịch đến dòng dữ liệu kế tiếp. doc.Offset += 1; // Dịch xuống một dòng trên trang. y += lineHeight; } if (doc.Offset < doc.Text.GetUpperBound(0)) { // Vẫn còn ít nhất một trang nữa. // Cho biết sự kiện này sẽ phát sinh lần nữa. e.HasMorePages = true; }else { // Thao tác in đã hoàn tất. doc.Offset = 0; } } } 328 Chương 8: Đồ họa, đa phương tiện, và in ấn 15. 15. In text d ng wrappingạ In text d ng wrappingạ Bạn cần phân tích một khối text lớn thành các dòng riêng biệt sao cho vừa khít trên một trang. Sử dụng phương thức nạp chồng Graphics.DrawString (phương thức này nhận vào một hình chữ nhật biên). Thông thường, bạn sẽ cần phá một khối text lớn thành các dòng riêng biệt để có thể in lên trang từng dòng một. .NET Framework có thể thực hiện công việc này một cách tự động bằng một phiên bản của phương thức Graphics.DrawString (nhận vào một hình chữ nhật biên). Bạn cần chỉ định hình chữ nhật mô tả nơi bạn muốn text sẽ hiển thị. Theo đó, text sẽ được wrap một cách tự động cho vừa khít bên trong đường biên này. Đoạn mã dưới đây thực hiện cách tiếp cận này: using System; using System.Windows.Forms; using System.Drawing; using System.Drawing.Printing; public class WrappedPrint : System.Windows.Forms.Form { private System.Windows.Forms.Button cmdPrint; // (Bỏ qua phần mã designer.) private void cmdPrint_Click(object sender, System.EventArgs e) { // Tạo một văn bản và gắn nó vào phương thức thụ lý sự kiện. string text = "Windows Server 2003 builds on the core strengths " + "of the Windows family of operating systems security, " + "manageability, reliability, availability, and scalability. " + "Windows Server 2003 provides an application environment to " + "build, deploy, manage, and run XML Web services. " + "Additionally, advances in Windows Server 2003 provide many " + "benefits for developing applications."; PrintDocument doc = new ParagraphDocument(text); doc.PrintPage += new PrintPageEventHandler(this.Doc_PrintPage); 329 Chương 8: Đồ họa, đa phương tiện, và in ấn // Cho phép người dùng chọn một máy in // và chỉ định các thiết lập khác. PrintDialog dlgSettings = new PrintDialog(); dlgSettings.Document = doc; // Nếu người dùng nhắp OK thì in văn bản. if (dlgSettings.ShowDialog() == DialogResult.OK) { doc.Print(); } } private void Doc_PrintPage(object sender, PrintPageEventArgs e) { // Thu lấy văn bản đã gửi sự kiện này. ParagraphDocument doc = (ParagraphDocument)sender; // Định nghĩa font và text. Font font = new Font("Arial", 15); e.Graphics.DrawString(doc.Text, font, Brushes.Black, e.MarginBounds, StringFormat.GenericDefault); } } public class ParagraphDocument : PrintDocument { private string text; public string Text { get {return text;} set {text = value;} } public ParagraphDocument(string text) { this.Text = text; } } 330 Chương 8: Đồ họa, đa phương tiện, và in ấn Hình 8.11 Văn bản đã được in (sử dụng máy in Adobe PDF) 16. 16. Hi n th print-previewể ị Hi n th print-previewể ị Bạn cần sử dụng print-preview để biết được văn bản khi được in ra sẽ trông như thế nào. Sử dụng PrintPreviewDialog hoặc PrintPreviewControl (cả hai đều thuộc không gian tên System.Windows.Forms ). .NET cung cấp hai điều kiểm có thể nhận vào một đối tượng PrintDocument , chạy đoạn mã thực hiện thao tác in, và sử dụng nó để tạo print-preview trên màn hình. Hai điều kiểm này là: • PrintPreviewDialog —hiển thị print-preview trong một cửa sổ độc lập. • PrintPreviewControl —hiển thị print-preview trong một form tùy biến. Để sử dụng cửa sổ print-preview độc lập, bạn cần tạo đối tượng PrintPrevewDialog , ấn định văn bản, và gọi phương thức PrintPreviewDialog.Show . PrintPreviewDialog dlgPreview = new PrintPreviewDialog(); dlgPreview.Document = doc; dlgPreview.Show(); Cửa sổ print-preview (xem hình 8.12) cung cấp tất cả các điều khiển cần thiết để di chuyển từ trang này sang trang khác, thu phóng trang Cửa sổ này cũng cung cấp nút Print cho phép người dùng gửi trực tiếp văn bản đến máy in. Bạn có thể biến đổi cửa sổ này bằng cách chỉnh sửa các thuộc tính của PrintPrevewDialog . Bạn cũng có thể thêm PrintPreviewControl vào bất kỳ form nào để hiển thị print-preview kế bên các thông tin khác. Trong trường hợp này, bạn không cần gọi phương thức Show . Ngay khi bạn thiết lập thuộc tính PrintPreviewControl.Document thì preview được tạo ra. Để xóa preview, cần thiết lập thuộc tính Document là null , và để làm tươi preview, cần gán lại thuộc tính Document . PrintPreviewControl chỉ hiển thị các trang preview, không có thêm điều khiển nào khác. Tuy nhiên, bạn có thể thêm các điều kiểm để thực hiện thu phóng trang, lát nhiều trang Bạn chỉ cần điều chỉnh các thuộc tính của PrintPreviewControl cho phù hợp. . Properties để cấu hình các thiết lập nâng cao như cách bố trí trang (layout) và độ phân giải máy in (resolution). Cuối cùng, người dùng có thể chấp thuận hoặc hủy bỏ thao tác in bằng cách nhắp OK hoặc. phương thức của lớp Graphics đều yêu cầu tọa độ chỉ định nơi cần vẽ. Các thiết lập cho máy in được cấu hình thông qua các thuộc tính PrintDocument.PrinterSettings và PrintDocument.DefaultPageSettings PrintDocument trên một tiểu trình mới. Bạn thụ lý các sự kiện này và sử dụng đối tượng System.Drawing.Graphics để xuất dữ liệu ra trang. Hình và text được ghi ra trang theo cùng cách như bạn vẽ lên