Makale Özeti

 Bu makalemde sizlerle birlikte DataTable içerisinde bulunan verileri bir arayüz bileşeni kullanmadan print etmemizi sağlayan bir bileşen yazacağız. Bileşen hem windows tabanlı uygulamalarda hem de web uygulamalarında çalışabilir olacak. Uygulamayı Visual Studio 2008 kullanarak .Net Framework 3.5 üzerinde geliştiriyorum. Ancak downloadlar kısmına Visual Studio 2005'de .Net Framework 2.0 kullanılarak yazılmış şeklini de koyacağım.

Makale

         Merhabalar,

         Bu makalemde sizlerle birlikte DataTable içerisinde bulunan verileri bir arayüz bileşeni kullanmadan print etmemizi sağlayan bir bileşen yazacağız. Bileşen hem windows tabanlı uygulamalarda hem de web uygulamalarında çalışabilir olacak.  Uygulamayı Visual Studio 2008 kullanarak .Net Framework 3.5 üzerinde geliştiriyorum. Ancak downloadlar kısmına Visual Studio 2005'de .Net Framework 2.0 kullanılarak yazılmış şeklini de koyacağım.

         Kodu incelemeye başlayalım, Öncelikle class'ımızı oluşturuyoruz ve class'ımızın adını DataTableUtilities olarak veriyoruz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Drawing.Printing;
using System.Collections;
using System.Drawing;
 
namespace DataTablePrint
{
    public class DataTableUtilities
    {


  Metodumuzda kullanacağımız member'ları tanımlıyoruz. lastRowPrinted son printera gönderilecek dökümana sayfa geçişlerinde row'un indeks numarasını tutabilmek için, columnWidths ve rowHeights ise yazılacak kolonların hangi genişlikte veya yükseklikte olduğunun tutulması için, Font yazdırılacak olan metinlerin hangi font'ta yazılacağının belirlenmesi için, Pen ise print edildiğinde çıkacak tablo yapısının çerçevelerinin çizilmesi için gereklidir.

        private int lastRowPrinted = -1;
        private Hashtable columnWidths = new Hashtable();
        private Hashtable rowheights = new Hashtable();
        private Font Font = new Font("Times New Roman", 12);
        private Pen Pen = new Pen(Color.Black);
        DataTable dt = null;

         Print metodunu yazmaya başlayalım. Bu metod esas işimizi yapan metoddur. Öncelikle parametre olarak gelen datatable'ı alarak  member değişkene atanıyor. Sonrasında ise HttpContext bulunup bulunmadığı bilgisine göre karar vererek çalışan uygulamanın web mi?, windows mu? olduğu belirleniyor. Eğer çalışan uygulama Wndows uygulaması ise PrintDocument bileşeni tanımlanıyor ve bu bileşenin PrintPage event'ı handle ediliyor. Yazdırılacak olan dökümanın içeriğini bu event içerisinde belirleyeceğiz. Sonrasında ise aynı nesnenin Print metodunu çağırarak yazıcıya gönderilmesini sağlıyoruz. We uygulamasında ise elimizdeki datatable'dan son satırlarında client tarafından sayfanın yazdırılabilmesi için window.print() olan bir html kodu çıkarıyoruz ve bu html kodunu mevcut response içine yazarak sayfada gözükmesini sağlıyoruz.
 
        public void Print(DataTable _dt)
        {
            dt = _dt;
            if (System.Web.HttpContext.Current == null)
            {
                PrintDocument p = new PrintDocument();
                p.PrintPage += new PrintPageEventHandler(p_PrintPage); ;
                try
                {
                    p.Print();
                }
                catch (Exception ex)
                {
                    throw new Exception("Exception Occured While Printing", ex);
                }
            }
            else
            {
                string s = GenerateHtml();
                System.Web.HttpContext.Current.Response.Clear();
                System.Web.HttpContext.Current.Response.Write(s);
            }
        }

         Tanımlamış olduğumuz PrintPage event'ı içerisinde öncelikle yazdırılmakta olan sayfanın yüksekliğiini buluyoruz. Sonra ise kolon genişliklerini tutacağımız hashtable içerisinde kayıt yoksa bir döngü ile tüm kolonlar içerisinde dönüyoruz. Bu kısımda önemli olan nokta her bir kolon için o kolonda bulunan değerleri row row dolaşıp MeasureString metodu ile belirlediğimiz font'ta yazılmaları durumunda genişliklerinin ne kadar olacağını bulmaktır. Bu işlemi her kolon için yaptıktan sonra toplam genişliğimizi elde ediyoruz. Elimizde toplam genişlik ve sayfa genişliği var. Biz elimizdeki genişliği sayfa genişliğine sığdırmak zorunda olduğumuzdan bu iki değer arasındaki oranı buluyor ve her kolonun genişliğini bu değerle çarparak küçültüyoruz.
 
        void p_PrintPage(object sender, PrintPageEventArgs e)
        {
            int pageHeight = e.PageBounds.Height - 20;
            if (columnWidths.Count == 0)
            {
                int totalWidth = 0;
                foreach (DataColumn dc in dt.Columns)
                {
                    int width = Convert.ToInt32(e.Graphics.MeasureString(dc.ColumnName, Font).Width);
                    foreach (DataRow dr in dt.Rows)
                    {
                        int dataWidth = Convert.ToInt32(e.Graphics.MeasureString(dr[dc].ToString(), Font).Width);
                        if (dataWidth > width)
                        {
                            width = dataWidth;
                        }
                    }
 
                    width += 5;
                    columnWidths.Add(dc.ColumnName, width);
                    totalWidth += width;
                }
 
                int pageWidth = e.PageBounds.Width - 20;
                decimal ratio = 1;
                if (totalWidth > pageWidth)
                {
                    ratio = Convert.ToDecimal(pageWidth) / Convert.ToDecimal(totalWidth);
                    Hashtable cwidthsTemp = new Hashtable();
                    foreach (DictionaryEntry de in columnWidths)
                    {
                        cwidthsTemp.Add(de.Key, Convert.ToInt32(Math.Floor(Convert.ToDecimal(de.Value) * ratio)));
                    }
                    columnWidths = cwidthsTemp;
                }
 
            }

         Şimdi ise sırada kolon genişliklerini daraltmamızın sonucunda yazının iki satır halinde yazılmasından dolayı yüksekliği artabilecek row'ları bulmakta ve bu rowların yüksekliklerinin ne olduğuna karar vermekte.Bunun için DataTable'da bulunan her row için tüm kolonlarda bulunan değerin bulunduğu kolon genişliği içine sığması koşulunda yüksekliğinin ne olacağını yine MeasureString metodu ile okuyoruz. Çalışmakta olduğumuz row için en yüksek yükseklik değerine sahip olan kolonun yüksekliğini bu row'un indeks numarası ile ilişkilendirerek rowHeıghts hashtable'ına atıyoruz. Daha sonraki adımda ise tablomuzun başlığı için aynı işlemi yapıyoruz.
 
            if (rowheights.Count == 0)
            {
                foreach (DataRow dr in dt.Rows)
                {
                    int height = 0;
                    foreach (DataColumn dc in dt.Columns)
                    {
                        int dataHeight = Convert.ToInt32(e.Graphics.MeasureString(dr[dc].ToString(), Font, Convert.ToInt32(columnWidths[dc.ColumnName])).Height);
                        if (dataHeight > height)
                        {
                            height = dataHeight;
                        }
                    }
                    rowheights.Add(dt.Rows.IndexOf(dr), height);
                }
                int headerHeight = 0;
                foreach (DataColumn dc in dt.Columns)
                {
                    int dataHeaderHeight = Convert.ToInt32(e.Graphics.MeasureString(dc.ColumnName, Font, Convert.ToInt32(columnWidths[dc.ColumnName])).Height);
                    if (dataHeaderHeight > headerHeight)
                    {
                        headerHeight = dataHeaderHeight;
                    }
                }
                rowheights.Add(-1, headerHeight);
            }

         Şimdi ise sıra kolon başlığı için tabloyu oluşturan cell'leri çizmek ve içlerini doldurmakta. Bunun için kolonlar arasında bir döngüde dönerken, kolonun genişliğini columnWidths hashtable'ından okuyoruz. Kolonun yüksekliğini ise rowHeights hashtable'ında kolon başlığının temsil edildiği değe rolan -1 ile okuyoruz. Ayrıca çizeceğimiz dikdörtgenin başlama noktası kendisinden önce çizilmiş olan dikdörtgenlerin genişlikleri toplamı olacağından bu hesabıda yapıyoruz ve Rectangle nesnemizi oluşturuyoruz. Daha sonra bu değerleri her kenardan 1 pixel azaltarak adı rIn olan başka bir Rectangle nesnesi oluşturuyoruz. İlk oluşturduğumuz Rectangle nesnesi dış çerçevenin çizilmesindeikinci oluşturduğumuz ise hücrenin iç renginin doldurulmasında kullanılacak. Sonraki kısımda DrawRectangle ile çerçeveyi çiziyoruz, FillRectangle ile çerçevenin içerisini dolduruyoruz ve DrawString ile kolon başlığını yazıyoruz.
 
            foreach (DataColumn dc in dt.Columns)
            {
                int x = 10;
                for (int i = dt.Columns.IndexOf(dc) - 1; i >= 0; i--)
                {
                    x += Convert.ToInt32(columnWidths[dt.Columns[i].ColumnName]);
                }
                int y = 10;
                int width = Convert.ToInt32(columnWidths[dc.ColumnName]);
                int height = Convert.ToInt32(rowheights[-1]);
                Rectangle r = new Rectangle(x, y, width, height);
                Rectangle rIn = new Rectangle(x + 1, y + 1, width - 2, height - 2);
                e.Graphics.DrawRectangle(Pen, r);
                e.Graphics.FillRectangle(new SolidBrush(Color.Aqua), rIn);
                e.Graphics.DrawString(dc.ColumnName, Font, new SolidBrush(Color.White), rIn);
            }

         Şimdi biraz önce yaptığımız işlemi tüm row'lar için yapıyoruz. Burada dikkat edilmesi gereken iki önemli nokta var. Bu noktalardan biri yazdırılacak olan row'un y koordinatı ile yüksekliğinin toplamı sayfa yüksekliğini geçtiğinde bir sonraki sayfadan devam etmesi gerekliliğidir. Bunun için Event Argumanının HasMorePages propertysinin true yapıyoruz. Bu Property true ise bu metod çalışması bittiğinde yeniden tetiklenecektir. Ve sonraki sayfada hangi row'dan devam edeceğimizi lastRowPrinted isimli değişkende tutyoruz. İkinci önemli nokta ise yazdırılmakta olan row'un indeksinin tek olması durumunda AlternatingRow çift olması durumunda ise Row olarak oluşturulduğudur.
 
            foreach (DataRow dr in dt.Rows)
            {
                if (dt.Rows.IndexOf(dr) > lastRowPrinted)
                {
                    foreach (DataColumn dc in dt.Columns)
                    {
                        int x = 10;
                        for (int i = dt.Columns.IndexOf(dc) - 1; i >= 0; i--)
                        {
                            x += Convert.ToInt32(columnWidths[dt.Columns[i].ColumnName]);
                        }
                        int y = 10 + Convert.ToInt32(rowheights[-1]);
                        for (int i = dt.Rows.IndexOf(dr) - 1; i > lastRowPrinted; i--)
                        {
                            y += Convert.ToInt32(rowheights[i]);
                        }
                        int width = Convert.ToInt32(columnWidths[dc.ColumnName]);
                        int height = Convert.ToInt32(rowheights[dt.Rows.IndexOf(dr)]);
                        if (height + y > pageHeight)
                        {
                            e.HasMorePages = true;
                            lastRowPrinted = dt.Rows.IndexOf(dr) - 1;
                            return;
                        }
 
 
 
                        Rectangle r = new Rectangle(x, y, width, height);
                        Rectangle rIn = new Rectangle(x + 1, y + 1, width - 2, height - 2);
 
 
                        if (dt.Rows.IndexOf(dr) % 2 == 0)
                        {
                            e.Graphics.DrawRectangle(Pen, r);
                            e.Graphics.FillRectangle(new SolidBrush(Color.Gray), rIn);
                            e.Graphics.DrawString(dr[dc].ToString(), Font, new SolidBrush(Color.Black), rIn);
                        }
                        else
                        {
                            e.Graphics.DrawRectangle(Pen, r);
                            e.Graphics.FillRectangle(new SolidBrush(Color.White), rIn);
                            e.Graphics.DrawString(dr[dc].ToString(), Font, new SolidBrush(Color.Black), rIn);
                        }
                    }
                }
            }
        }

         Bu kısım ise web uygulamalarında printing için HTML oluşturmakta kullandığımız metoddur. Çalışma prensibi olarak DataTable üzerinde bulunan her veriyi aralarına <tr> veya <td> tagları ekleyerek bir html tabloya dönüştürmesidir.
 
        private string GenerateHtml()
        {
            int columnCount = 0;
            StringBuilder sb = new StringBuilder();
            sb.Append(@"<table border=1 border-color=Black>");
            sb.Append(@"<tr style=background-color:Aqua;Color:White>");
            foreach (DataColumn dc in dt.Columns)
            {
                sb.Append("<td align=center>");
                sb.Append(dc.ColumnName.ToString());
                sb.Append("</td>");
                columnCount++;
            }
            sb.Append("</tr>");
            if (dt.Rows.Count > 0)
            {
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    if (i % 2 == 0)
                    {
                        sb.Append(@"<tr style=background-color:Gray;color:Black>");
                    }
                    else
                    {
                        sb.Append(@"<tr style=background-color:White;color:Black>");
                    }
                    foreach (DataColumn dc in dt.Columns)
                    {
                        sb.Append("<td>");
                        sb.Append(dt.Rows[i][dc].ToString());
                        sb.Append("</td>");
                    }
                    sb.Append("</tr>");
                }
            }
            else
            {
                sb.Append("<tr><td colspan=" + columnCount.ToString() + ">Kayıt Bulunamadı</td></tr>");
            }
 
            sb.Append("</table>");
            sb.Append("<script language='javascript'>window.print()</script>");
            return sb.ToString();
        }
    }
}

         Bu özelliği uygulamalarımızda kullanmak için tek yapmamız gereken bu metodu bir datatable parametresi geçirerek çağırmaktır. Konu ile ilgili daha çok bilgi almak için ve yapılabilecek diğer geliştirmeleri görmek için daha önceden yazgeliştirde yazdığım ancak sonrasında üzerinde yeni geliştirmeler yaptığım ExtendedDataTable isimli bileşeni codeplex, codeproject'te ExtendedDataTable isimli proje olarak açık kaynak kodu ile inceleyebilirsiniz.
 
         oztamer@hotmail.com
         tamer.oz@yazgelistir.com
         oztamer@hotmail.com
2005 Uygulamasi

2008 Uygulamasi