مطالب
معرفی چند تابع API و استفاده از آنها در #C

نکته : توابع عملیاتی API در صورتیکه کار خود را بدرستی انجام ندهند مقدار صفر، در غیر این صورت مقدار غیر صفر را برمی‌گردانند.

()ShellAbout 

 با استفاده از این تابع API می‌توان پنجره About ویندوز را باز کرد و همچنین میتوان در متن آن تغییراتی را اعمال کرد. 

‎[DllImport("shell32.DLL")]‎
‎  [DllImport("User32.DLL")]‎
public string Lpwindow { get; set; }
   public IntPtr Hicon { get; set; }
       public static extern int ShellAbout(IntPtr hwnd,string szApp,string ‎szotherstuff,IntPtr hicon );‎
        public static extern int FindWindow(string str,string lpwindow);‎

طریقه استفاده از تابع فوق

        private void btnbutton1_Click(object sender, EventArgs e)
          {

          var  Window = (IntPtr)FindWindow(null, Lpwindow);

              ShellAbout(Window, "===WINDOWS===", "HELLO", Hicon);
          }

 () SetSuspendState  

با استفاده از این تابع API می‌توان سیستم را به حالت Hibernate برد. 

‎[DllImport("powrprof.dll")]‎
public static extern int SetSuspendState(int hibernate,int forcecritical,int ‎DisablewakeEvent);‎

طریقه استفاده از تابع فوق 

  private void btnbutton1_Click(object sender, EventArgs e)
          {

              SetSuspendState(1, 0, 0);
          }

()LockWorkStation 

 با اجرای این تابع سیستم به حالت Lock میرود و کاربر برای استفاده از سیستم باید کلمه عبور را وارد کند. 

‎[DllImport(“user32.DLL”)]‎
‎   ‎‎ public static extern int  LockWorkStation();‎

طریقه استفاده از تابع فوق   

  private void btnbutton1_Click(object sender, EventArgs e)
          {
              LockWorkStation();
          }

()FatalAppExit  

 در صورت اجرای این تابع برنامه یک پیغام خطا نمایش میدهد و بعد از بستن این پیام برنامه بسته می‌شود. 

‏ ‏DllImport("kernel32.DLL")]‎
  public static extern int FatalAppExit(int uAction,string ipmesseg);‎

طریقه استفاده از تابع فوق 

   private void btnbutton1_Click(object sender, EventArgs e)
          {
              FatalAppExit(0, "ERROR PROGRAM");
          }

()timeGetTime 

 این تابع مدت زمان روشن بودن سیستم را به میلی ثانیه برمیگرداند. 

‎[DllImport("Winmm.DLL")]‎
public static extern long  timeGetTime();‎

        private void timer1_Tick(object sender, EventArgs e)
        {
            var sd = Convert.ToString(timeGetTime());
            int i = Convert.ToInt32(sd.Substring(0, sd.Length - 3));
            int m;
            if (i < 60)
                textBox1.Text = @"00:00:" + i;
            else if (i >= 60 && i < 3600)
            {
                m = i / 60;
                S = i % 60;
                textBox1.Text = @"00:" + m + @":";
            }
            else
            {
                var h = i / 3600;
                var mm = i % 3600;

                if (mm > 59)
                {
                    m = mm / 60;
                    S = mm % 60;
                    textBox1.Text = h + @":" + m.ToString(CultureInfo.InvariantCulture) + @":" + S;
                }
                else
                {
                    textBox1.Text = h + @":00:" + mm;
                }
            }
        }

طریقه استفاده از تابع فوق  

     private void Form1_Load(object sender, EventArgs e)
        {
          timer1.Start();
         }

مطالب دوره‌ها
بررسی Select For XML
تعدادی افزونه‌ی T-SQL، از نگارش‌های پیشین SQL Server، جهت تولید خروجی XML از یک بانک اطلاعاتی رابطه‌ای، به همراه آن بوده‌اند که در این قسمت آن‌ها را بررسی خواهیم کرد.

پیشنیاز بحث

در ادامه، از بانک اطلاعاتی معروف northwind برای تهیه کوئری‌ها استفاده خواهیم کرد. بنابراین فرض بر این است که این بانک اطلاعاتی را پیشتر به وهله‌ی جاری SQL Server خود افزوده‌اید.


بررسی FOR XML RAW

از نگارش 2005 به بعد، Select for XML علاوه بر خروجی متنی XML، توانایی تولید خروجی از نوع XML را نیز یافته است. در ادامه 4 حالت مختلف خروجی آن‌را بررسی خواهیم کرد.
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML RAW
خروجی For XML Raw کوئری فوق به نحو ذیل است:
 <row CustomerID="ALFKI" OrderID="10643" />
<row CustomerID="ALFKI" OrderID="10692" />
Select for XML در اینجا به صورت خودکار، هر ردیف کوئری را تبدیل به یک المان row نموده و همچنین هر ستون کوئری را تبدیل به ویژگی‌های این المان (attributes) کرده‌است. همچنین باید دقت داشت که خروجی آن یک fragment است و دارای یک root element  مشخص نیست.

برای تغییر حالت خروجی آن می‌توان از حالت ELEMENTS استفاده کرد:
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML RAW, ELEMENTS
اینبار مقادیر هر ردیف خروجی، بجای ظاهر شدن در ویژگی‌ها، به صورت یک المان نمایش داده می‌شود:
 <row>
  <CustomerID>ALFKI</CustomerID>
  <OrderID>10643</OrderID>
</row>

حالت پیشرفته‌تر FOR XML RAW را در ادامه ملاحظه می‌کنید:
 SELECT Customers.CustomerID,
Orders.OrderID
FROM Customers,
Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY
Customers.CustomerID
FOR XML RAW('Customer'), ELEMENTS XSINIL, ROOT('Customers'), XMLSCHEMA('http://MyCustomers')
با استفاده از Root می‌توان Fragment حاصل را تبدیل به Document با یک Root element مشخص کرد. در قسمت Raw نیز می‌توان مقدار پیش فرض row را مقدار دهی کرد.
 <Customers>
  <Customer xmlns="http://MyCustomers">
     <CustomerID>ALFKI</CustomerID>
     <OrderID>10643</OrderID>
  </Customer>
از XSINIL برای مشخص سازی المان‌های نال استفاده می‌شود. اگر XSINIL ذکر نشود، المان‌های نال در خروجی وجود نخواهند داشت.
ذکر XMLSCHEMA، سبب می‌شود تا SQL Server به صورت خودکار XML Schema را بر اساس اطلاعات ستون‌های رابطه‌ای مورد استفاده تولید کند.
این نکات را برای FOR XML AUTO نیز می‌توان بکار برد.


بررسی FOR XML AUTO

حالت دوم بکارگیری Select for XML به همراه عبارت Auto است:
 SELECT Customers.CustomerID, Orders.OrderID
FROM Customers, Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY Customers.CustomerID
FOR XML AUTO, ELEMENTS
با خروجی ذیل:
 <Customers>
  <CustomerID>ALFKI</CustomerID>
  <Orders>
     <OrderID>10643</OrderID>
  </Orders>
  <Orders>
     <OrderID>10692</OrderID>
  </Orders>
</Customers>
در اینجا ابتدا شماره مشتری و سپس اطلاعات تمام خریدهای او ذکر می‌شوند.


بررسی For XML Explicit

اگر بخواهیم خروجی را تبدیل به ترکیبی از المان‌ها و ویژگی‌ها کنیم، می‌توان از For XML Explicit استفاده کرد:
 SELECT 1 AS Tag,
NULL AS Parent,
Customers.CustomerID AS [Customers!1!CustomerID],
NULL AS [Order!2!OrderId]
FROM Customers
UNION ALL
SELECT 2,
1,
Customers.CustomerID,
Orders.OrderID
FROM Customers,
Orders
WHERE  Customers.CustomerID = Orders.CustomerID
ORDER BY
[Customers!1!CustomerID]
FOR XML EXPLICIT
با خروجی:
 <Customers CustomerID="ALFKI">
  <Order OrderId="10643" />
  <Order OrderId="10692" />
  <Order OrderId="10702" />
  <Order OrderId="10835" />
  <Order OrderId="10952" />
  <Order OrderId="11011" />
</Customers>
برای استفاده از FOR XML EXPLICIT، باید به ازای هر سطح از سلسله مراتب مورد نظر، یک عبارت select را تهیه کرد که این‌ها نهایتا باید با هم UNION ALL شوند.
به علاوه دو ستون اضافی Tag و Parent نیز باید ذکر شوند. از این دو برای مشخص سازی سلسه مراتب استفاده می‌شوند.
!1! سبب تولید یک ویژگی در سطح اول می‌شود و !2! سبب تولید ویژگی دیگری در سطح دوم.


بررسی  FOR XML PATH

همانطور که مشاهده می‌کنید، نوشتن FOR XML EXPLICIT نسبتا طولانی و پیچیده‌است. برای ساده سازی آن از نگارش 2005 به بعد، روش For XML Path معرفی شده‌است:
 WITH XMLNAMESPACES('http://somens' AS au)
SELECT
  CustomerID AS [@au:CustomerID],
  CompanyName AS [Company/Name],
  ContactName AS [Contact/Name]  
FROM Customers
 FOR XML PATH('Customer')
با خروجی:
 <Customer xmlns:au="http://somens" au:CustomerID="ALFKI">
  <Company>
       <Name>Alfreds Futterkiste</Name>
  </Company>
  <Contact>
      <Name>Maria Anders</Name>
  </Contact>
</Customer>
در اینجا با استفاده از WITH XMLNAMESPACES یک فضای نام جدید را تعریف کرده و سپس نحوه‌ی استفاده از آن‌را توسط یک Alias مشاهده می‌کنید. در اینجا همچنین توسط Aliasها می‌توان یک مسیر مشخص را نیز تعریف کرد. رشته‌ای که در قسمت Path مشخص می‌شود، بیانگر نام المان‌های خروجی است.

یک نکته: اگر کوئری FOR XML PATH را اجرا کنید، نام ستون خروجی به صورت خودکار به XML_F5..6B  تنظیم می‌شود. علت اینجا است که در حالت پیش فرض، نوع خروجی این افزونه، استریم است و نه XML. برای تبدیل آن به نوع XML باید یک Type را اضافه کرد:
 FOR XML PATH('Customer'), Type
در این حالت خروجی FOR XML PATH قابل انتساب به یک متغیر T-SQL از نوع XML خواهد بود.
مطالب
آموزش فایرباگ - #8 - Script Panel
تب Script در FireBug مخصوص دیباگ کردن کدهای JavaScript است. امکاناتی که در این قسمت گنجانده شده بسیار کاربردی بوده و همچنین در بیشتر قسمت‌ها با ابزارهای خطایابی دیگر مشابه است. برای مثال اگر با Visual Studio کار کرده باشید، با نحوه‌ی ایجاد Break Point ، قسمت‌های Watch,Stack و ... آشنا خواهید بود.

این پنل هم مشابه پنل‌های دیگر فایرباگ دارای یک بخش با عنوان Options Menu هست که با راست کلیک کردن بروی عناون یا کلیک بروی مثلث کنار عنوان پنل قابل دسترسی است. تنظیماتی که در اینجا قابل تعیین است عبارتند از:


  • Enabled/Disabled : برای فعال/غیرفعال کردن پنل است. فعال بودن این قسمت ممکن است موجب کاهش سرعت بارگزاری صفحات شود.
  • Track Throw/Catch : در صورت فعال بودن این گزینه، در صورت رویدادن خطا در بلاک try/catch ، دیباگر در آن نقطه از برنامه متوقف شده و کنترل برنامه به شما داده می‌شود. (البته بنده موفق بررسی کامل این قابلیت نشدم. ظاهرا ورژن‌های آخری باگ دارند. مثل غیرفعال شدن کامل همین پنل، "Debugger not activated"! اگر کسی موفقیتی در کار با این مورد داشت، سپاسگذار میشوم اطلاع بدهد.)
  • Show Break Notifications : در صورت فعال بودن این گزینه، هنگام توقف کدی در صفحه، اطلاعاتی در مورد علت توقف آن در بالای پنل ارائه خواهد شد.
توجه داشته باشید که ممکن است در ورژن‌های مختلف، تعداد این گزینه‌ها بیشتر یا کمتر باشد.


 Panel Toolbar

نواری است که ابزارهای پنل بروی آن قرار دارند و به ترتیب عبارنتد از:
  • Break On Next : این دکمه که مشابه آن در اکثر پنل‌ها وجود دارد، هنگام اجرای یک دستور JavaScript آن را متوقف کرده و شما می‌توانید به بررسی آن بپردازید.
  • Script Type Menu : با انتخاب یکی از چهار گزینه موجود می‌توانید نتیجه اسکریپت‌های اضافه شده به صفحه که در قسمت Script Location Menu نمایش داده شده اند را فیلتر کنید. (متاسفانه این گزینه هم مشابه گزینه Track Throw/Catch برای بنده، ورژن 1.11.4 نتیجه ای نداشته است.)
  • Script Location Menu : در این قسمت اسکریپت هایی که در صفحه وجود دارند نمایش داده می‌شوند. مشابه پنل HTML می‌توانید هنگام بازبودن این لیست، با تایپ کردن نتایج را فیلتر کنید.
    همچنین با راست کلیک کردن بروی این قسمت می‌توانید آیتم یا فایل انتخاب شده را در ادیتور مورد نظر باز کنید، در پنل DOM بررسی کنید، فایل را در یک تب جدید باز کنید، آدرس فایل را در حافظه موقت کپی کنید.
  • Execution Control Buttons : این دکمه‌ها زمانی که دیباگر به یک Break Point برسد یا به دلیل فعال بودن دکمه‌های Break On ... متوقف شود، فعال می‌شوند و با کمک آن‌ها می‌توانید عملیات خطایابی را ادامه دهید.
    در جدول زیر این دکمه‌ها و توضیحات تکمیلی هریک را مشاهده می‌کنید:

     نوعدکمه
    Shortcut
    توضیحات
    اجرای مجدد

    Shift + F18
    Stack فعلی را مجدد اجرا می‌کند.
    (Stack: لیستی از توابع به ترتیبی که با فراخوانی آنها تابع جاری فراخونی شده است. در مقاله بعدی توضیحات بیشتری ارائه خواهد شد.)
    ادامه

    F8
    اجرای برنامه را (تا Break Point بعدی) ادامه می‌دهد.
    وارد شدن

    F11
    در صورت رسیدن به یک تابع، با زدن این دکمه دیباگر وارد بنده‌ی آن تابع می‌شود.
    رد شدن

    F10
    اجرای برنامه را در سطح (Scope) فعلی ادامه می‌دهد.
    مشابه قبلی وارد تابع نمی‌شود.
    خارج شدن

    Shift + F11
    کنترل به تابعی که تابع جاری را فراخوانی کرده است بازمیگردد.
    روشی سودمند زمانی که هنگام خطایابی وارد کدهای jQuery یا مثل آن می‌شوید :)
بعد از Script Location Menu قسمتی وجود دارد که وضعیت فعلی Stack را نشان می‌دهد و با کلیک بروی هرکدام، به کد آن قسمت هدایت می‌شوید. ( البته اگر فایرباگ شما در این قسمت باگ نداشته باشد! )

Context Menu
تنها قابلیت جدیدی که در این منو وجود دارد، Run To This Line است. هنگامی که پنل در حالت دیباگ است، با راست کلیک کردن بروی خط مورد نظر و انتخاب گزینه‌ی Run to This Line می‌توانید خطوط میانی را رد کرده و به خط مورد نظر بروید. البته خطوطی که رد می‌شوند ، اجرا می‌شوند.
این کار معادل این است که درخط مورد نظر یک Break Point اضافه کنید و دکمه‌ی F8 را بزنید.



 Breakpoints
توسط Break Point‌ها می‌توانید خطوطی از برنامه را برای خطایابی مشخص کنید. زمانی که دیباگر به نقطه‌ی مورد نظر برسد متوقف شده و می‌توانید به بررسی برنامه بپردازید. می‌توانید با بردن موس بروی متغییرها مقدار آن هارا بررسی کنید یا با کمک قسمت‌های Watch و Stack در پنل جانبی اطلاعات بیشتری در مورد اجرای برنامه در نقطه‌ی جاری بدست آورید.(در مورد پنل‌های جانبی در قسمت بعدی توضیح خواهم داد.) همچنین با کمک دکمه هایی که در جدول فوق توضیح داده شده اند روند اجرای برنامه را خط به خط ادامه دهید.
برای ایجاد یک Break Point، بروی شماره‌ی خط مورد نظر کلیک کنید. نقطه ای قرمز رنگ نمایش داده خواهد شد. البته دقت کنید که همه‌ی خطوط برنامه اجرا نمی‌شوند و نمی‌توانید در آن خطوط از این امکان بهره ببرید. شماره‌ی خطوط فعال با رنگ سبز مشخص شده اند.

امکان مفید دیگری که همراه با این قابلیت ارائه شده است (که در محیط‌های دیگر هم وجود دارد)، عبارت شرطی است. به این صورت که ضمن قرار دادن Break Point در یک نقطه از برنامه، می‌توانید یک شرط هم برای توقف برنامه تعیین کنید.
فرض کنید یک حلقه دارید که 300 بار تکرار می‌شود و مثلا در اجرای 250ام آن مشکلی وجود دارد. در این حالت می‌توانید از این قابلیت استفاده کنید و شرط توقف را i == 250 قرار بدهید.
برای تعیین یک شرط برای یک Break Point، بروی خط مورد نظر راست کلیک کنید و شرط را در قسمت مشخص شده وارد کنید.


امکان مفید دیگری که وجود دارد، Break Point خودکار است. اگر از مقالات قبلی دکمه‌ی Break On All Errors در پنل Console و Break On Mutate در پنل HTML را بخاطر داشته باشید می‌دانید که در هر یک هنگام اجرای یک رخداد مورد نظر، دیباگر در خطی که موجب آن رخداد شده است متوقف شده و می‌توانید کنترل برنامه را بدست بگیرید. در این حالت نیازی به ایجاد Break Point نیست و FireBug بصورت خودکار این کار را انجام می‌دهد.


 Search
این بخش در همه پنل‌ها وجود دارد با این تفاوت که در پنل Script و CSS دو گزینه‌ی Multiple Files و Use Regular Expression وجود دارد که به ترتیب امکان جستجو در فایل‌های js و css اضافه شده به صفحه هستند. قابلیت دیگری هم که فقط در پنل Script وجود دارد، پرش به یک خط در برنامه است به این صورت که با وارد کردن # و یک عدد به عنوان شماره‌ی خط، همان خط نمایش داده می‌شود.


در قسمت بعدی پنل جانبی که شامل سه بخش Watch، Stack و Breakpoints است را بررسی خواهیم کرد.
مطالب
فرمت کردن اطلاعات نمایش داده شده به کمک jqGrid در ASP.NET MVC
پیشنیاز این بحث مطالعه‌ی مطلب «صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC» است و در اینجا جهت کوتاه شدن بحث، صرفا به تغییرات مورد نیاز جهت اعمال بر روی مثال اول اکتفاء خواهد شد.

صورت مساله

می‌خواهیم اطلاعات نمایش داده شده در گرید را به نحوی فرمت کنیم که
الف) اگر id ردیف مساوی 5 بود، رنگ و پس زمینه‌ی آن تغییر کند.
ب) نام محصول، به جزئیات آن لینک شود و این اطلاعات توسط یک jQuery UI Dialog نمایش داده شود.
ج) عدد قیمت با سه رقم جدا کننده همراه باشد.




تکمیل مدل برنامه

مدل قسمت اول صرفا یک محصول بود. مدل قسمت جاری، اطلاعات تولید/تامین کننده آن‌را توسط کلاس Supplier نیز به همراه دارد:
namespace jqGrid02.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public Supplier Supplier { set; get; }
    }

    public class Supplier
    {
public int Id { set; get; }
        public string CompanyName { set; get; }
        public string Address { set; get; }
        public string PostalCode { set; get; }
        public string City { set; get; }
        public string Country { set; get; }
        public string Phone { set; get; }
        public string HomePage { set; get; }
    }
}

کدهای سمت سرور

کدهای سمت سرور مانند متد GetProducts به همراه صفحه بندی و مرتب سازی پویای آن دقیقا مانند قسمت قبل است.
در اینجا فقط یک اکشن متد جدید جهت بازگشت اطلاعات تولید کننده‌ای مشخص با فرمت JSON، اضافه شده‌است:
        public ActionResult GetGetSupplierData(int id)
        {
            var list = ProductDataSource.LatestProducts;
            var product = list.FirstOrDefault(x => x.Id == id);
            if (product == null)
                return Json(null, JsonRequestBehavior.AllowGet);

            return Json(new
                          {
                              product.Supplier.CompanyName,
                              product.Supplier.Address,
                              product.Supplier.PostalCode,
                              product.Supplier.City,
                              product.Supplier.Country,
                              product.Supplier.Phone,
                              product.Supplier.HomePage
                          }, JsonRequestBehavior.AllowGet);
        }

کدهای سمت کلاینت

صفحه دیالوگی که قرار است اطلاعات تولید کننده را نمایش دهد، یک چنین ساختاری دارد:
<div dir="rtl" id="supplierDialog">
    <span id="CompanyName"></span><br /><br />
    <span id="Address"></span><br />
    <span id="PostalCode"></span>, <span id="City"></span><br />
    <span id="Country"></span><br /><br />
    <span id="Phone"></span><br />
    <span id="HomePage"></span>
</div>
و تغییرات گرید برنامه به شرح زیر است:
    <script type="text/javascript">
        function showSupplierDialog(linkElement, supplierId) {
            //request json data
            $.getJSON('@Url.Action("GetGetSupplierData","Home")', { id: supplierId }, function (data) {
                //set values in dialog
                for (var property in data) {
                    if (data.hasOwnProperty(property)) {
                        $('#' + property).text(data[property]);
                    }
                }
                
                //get link position
                var linkPosition = $(linkElement).offset();
                $('#supplierDialog').dialog('option', 'position', [linkPosition.left, linkPosition.top]);
                //open dialog
                $('#supplierDialog').dialog('open');
            });
        }

        $(document).ready(function () {

            $('#supplierDialog').dialog({
                 autoOpen: false, bgiframe: true, resizable: false, title: 'تولید کننده'
            });

            $('#list').jqGrid({
                // .... مانند قبل
                colNames: ['شماره', 'نام محصول', 'قیمت'],
                //columns model
                colModel: [
                    {
                        name: 'Id', index: 'Id', align: 'right', width: 20,
                        formatter: function (cellvalue, options, rowObject) {
                            var cellValueInt = parseInt(cellvalue);
                            if (cellValueInt == 5) {
                                return "<span style='background: brown; color: yellow'>" + cellvalue + "</span>";
                            }
                            return cellvalue;
                        }
                    },
                    {
                        name: 'Name', index: 'Name', align: 'right', width: 300,
                        formatter: function (cellvalue, options, rowObject) {
                            return "<a href='#' onclick='showSupplierDialog(this, " + rowObject[0] + ");'>" + cellvalue + "</a>";
                        }
                    },
                    {
                        name: 'Price', index: 'Price', align: 'center', width: 50,
                        formatter: 'currency',
                        formatoptions:
                        {
                            decimalSeparator: '.', thousandsSeparator: ',', decimalPlaces: 2, prefix: '$'
                        }
                    }
                ],
                // .... مانند قبل
            });
        });
    </script>
- همانطور که ملاحظه می‌کنید، توسط خاصیت formatter می‌توان عناصر در حال نمایش را فرمت کرد و بر روی نحوه‌ی نمایش نهایی آن‌ها تاثیرگذار بود.
در حالت ستون Id، از یک formatter سفارشی استفاده شده‌است. در اینجا این فرمت کننده به صورت یک callback عمل کرده و پیش از رندر نهایی اطلاعات، مقدار سلول جاری را توسط cellvalue در اختیار ما قرار می‌دهد. در این بین هر نوع فرمتی را که نیاز است می‌توان اعمال کرد و سپس یک رشته را بازگشت می‌دهیم. این رشته در سلول جاری درج خواهد شد.
- اگر مانند ستون Name، نیاز به مقادیر سایر سلول‌ها نیز وجود داشت، می‌توان از آرایه‌ی rowObject استفاده کرد. برای مثال در این حالت، یک لینک که کلیک بر روی آن سبب فراخوانی تابع showSupplierDialog می‌شود، در سلول‌های ستون Name درج خواهند شد. اولین rowObject که در اینجا مورد استفاده است، به ستون اول یا همان Id محصول اشاره می‌کند.
- در ستون Price از یک سری formatter از پیش تعریف شده استفاده شده‌است. نمونه‌ای از آن را در قسمت اول در ستون نمایش وضعیت موجود بودن محصول با تنظیم formatter: checkbox مشاهده کرده‌اید. در اینجا از یک formatter توکار دیگر به نام currency برای کار با مقادیر پولی استفاده شده‌است به همراه تنظیمات خاص آن.
- متد showSupplierDialog طوری تنظیم شده‌است که پس از دریافت Id یک محصول، آن‌را به سرور ارسال کرده و مشخصات تولید کننده‌ی آن‌را با فرمت JSON دریافت می‌کند. سپس در حلقه‌ای که مشاهده می‌کنید، خواص شیء جاوا اسکریپتی دریافتی استخراج و به spanهای supplierDialog انتساب داده می‌شوند. جهت سهولت کار، Id این spanها دقیقا مساوی Id خواص شیء دریافتی از سرور، درنظر گرفته شده‌اند.
- در مورد راست به چپ نمایش داده شدن عنوان دیالوگ، تغییرات CSS ایی لازم است که در قسمت اول بیان شدند.


برای مطالعه بیشتر
لیست کامل فرمت کننده‌های توکار
فرمت کننده‌های سفارشی


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid02.zip
 
مطالب
نمایش فرم‌های مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
اصول نمایش اطلاعات مودال به کمک bootstrap در مطلب «استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر» بررسی شدند.
در این قسمت قصد داریم یک فرم Ajaxایی را در ASP.NET MVC به همراه تمام مسایل اعتبارسنجی، پردازش اطلاعات و غیره را به کمک Twitter Bootstrap و jQuery Ajax پیاده سازی کنیم.


تهیه افزونه jquery.bootstrap-modal-ajax-form.js

از این جهت که مباحث مرتبط با نمایش و پردازش فرم‌های مودال Ajaxایی به کمک Twitter Bootstrap اندکی نکته دار و طولانی هستند، بهتر است این موارد را به شکل یک افزونه، کپسوله کنیم. کدهای کامل افزونه jquery.bootstrap-modal-ajax-form.js را در ادامه ملاحظه می‌کنید:
// <![CDATA[
(function ($) {
    $.bootstrapModalAjaxForm = function (options) {
        var defaults = {
            renderModalPartialViewUrl: null,
            renderModalPartialViewData: null,
            postUrl: '/',
            loginUrl: '/login',
            beforePostHandler: null,
            completeHandler: null,
            errorHandler: null
        };
        var options = $.extend(defaults, options);

        var validateForm = function (form) {
            //فعال سازی دستی اعتبار سنجی جی‌کوئری
            var val = form.validate();
            val.form();
            return val.valid();
        };

        var enableBootstrapStyleValidation = function () {
            $.validator.setDefaults({
                highlight: function (element, errorClass, validClass) {
                    if (element.type === 'radio') {
                        this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                    } else {
                        $(element).addClass(errorClass).removeClass(validClass);
                        $(element).closest('.control-group').removeClass('success').addClass('error');
                    }
                    $(element).trigger('highlited');
                },
                unhighlight: function (element, errorClass, validClass) {
                    if (element.type === 'radio') {
                        this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                    } else {
                        $(element).removeClass(errorClass).addClass(validClass);
                        $(element).closest('.control-group').removeClass('error').addClass('success');
                    }
                    $(element).trigger('unhighlited');
                }
            });
        }

        var enablePostbackValidation = function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        }

        var processAjaxForm = function (dialog) {
            $('form', dialog).submit(function (e) {
                e.preventDefault();

                if (!validateForm($(this))) {
                    //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود
                    return false;
                }

                //در اینجا می‌توان مثلا دکمه‌ای را غیرفعال کرد
                if (options.beforePostHandler)
                    options.beforePostHandler();

                //اطلاعات نباید کش شوند
                $.ajaxSetup({ cache: false });
                $.ajax({
                    url: options.postUrl,
                    type: "POST",
                    data: $(this).serialize(),
                    success: function (result) {
                        if (result.success) {
                            $('#dialogDiv').modal('hide');
                            if (options.completeHandler)
                                options.completeHandler();
                        } else {
                            $('#dialogContent').html(result);
                            if (options.errorHandler)
                                options.errorHandler();
                        }
                    }
                });
                return false;
            });
        };

        var mainContainer = "<div id='dialogDiv' class='modal hide fade in'><div id='dialogContent'></div></div>";
        enableBootstrapStyleValidation(); //اعمال نکات خاص بوت استرپ جهت اعتبارسنجی یکپارچه با آن
        $.ajaxSetup({ cache: false });
        $.ajax({
            type: "POST",
            url: options.renderModalPartialViewUrl,
            data: options.renderModalPartialViewData,
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            complete: function (xhr, status) {
                var data = xhr.responseText;
                var data = xhr.responseText;
                if (xhr.status == 403) {
                    window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا می‌شود
                }
                else if (status === 'error' || !data) {
                    if (options.errorHandler)
                        options.errorHandler();
                }
                else {
                    var dialogContainer = "#dialogDiv";
                    $(dialogContainer).remove();
                    $(mainContainer).appendTo('body');

                    $('#dialogContent').html(data); // دریافت پویای اطلاعات مودال دیالوگ
                    $.validator.unobtrusive.parse("#dialogContent"); // فعال سازی اعتبارسنجی فرمی که با ایجکس بارگذاری شده                            
                    enablePostbackValidation();
                    // و سپس نمایش آن به صورت مودال
                    $('#dialogDiv').modal({
                        backdrop: 'static', //با کلیک کاربر روی صفحه، صفحه مودال بسته نمی‌شود
                        keyboard: true
                    }, 'show');
                    // تحت نظر قرار دادن این فرم اضافه شده
                    processAjaxForm('#dialogContent');
                }
            }
        });
    };
})(jQuery);
// ]]>
توضیحات:
- توابع enableBootstrapStyleValidation و enablePostbackValidation در مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» بررسی شدند.
- این افزونه با توجه به مقدار renderModalPartialViewUrl، یک partial view را از برنامه ASP.NET MVC درخواست می‌کند.
- سپس این partial view را به صورت خودکار به صفحه اضافه کرده و آن‌را به صورت modal نمایش می‌دهد.
- پس از افزودن فرم Ajaxایی دریافتی، مسایل اعتبارسنجی را به آن اعمال کرده و سپس دکمه submit آن‌را تحت کنترل قرار می‌دهد.
- در زمان submit، ابتدا بررسی می‌کند که آیا فرم معتبر است و اعتبارسنجی آن بدون مشکل است؟ اگر اینچنین است، اطلاعات فرم را به آدرس postUrl به صورت Ajaxایی ارسال می‌کند.


کدهای مدل برنامه
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mvc4TwitterBootStrapTest.Models
{
    public class User
    {
        public int Id { set; get; }

        [DisplayName("نام")]
        [Required(ErrorMessage="لطفا نام را تکمیل کنید")]
        public string Name { set; get; }

        [DisplayName("نام خانوادگی")]
        [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")]
        public string LastName { set; get; }
    }
}
در اینجا یک مدل ساده‌را به همراه ویژگی‌های اعتبارسنجی و نام‌های نمایشی خواص ملاحظه می‌کنید.


کدهای کنترلر برنامه

using System.Web.Mvc;
using Mvc4TwitterBootStrapTest.Models;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class ModalFormAjaxController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش صفحه اولیه
        }

        [HttpPost] //برای این حالت امن‌تر است
        //[AjaxOnly]
        public ActionResult RenderModalPartialView()
        {
            //رندر پارشال ویوو صفحه مودال به همراه اطلاعات مورد نیاز آن
            return PartialView(viewName: "_ModalPartialView", model: new User { Name = "", LastName = "" });
        }

        [HttpPost]
        //[AjaxOnly]
        public ActionResult Index(User user) //ذخیره سازی اطلاعات
        {
            if (this.ModelState.IsValid)
            {
                //todo: SaveChanges;
                return Json(new { success = true });
            }

            this.ModelState.AddModelError("", "خطایی رخ داده است");
            return PartialView("_ModalPartialView", user);
        }
    }
}
کدهای کنترلر برنامه در این حالت از سه قسمت تشکیل می‌شود:
الف) متد Index حالت HttpGet که صفحه ابتدایی را نمایش خواهد داد.
ب) متد RenderModalPartialView یک partial view اضافه شده به برنامه به نام _ModalPartialView.cshtml را بازگشت می‌دهد. این partial view در حقیقت همان فرمی است که قرار است به صورت مودال نمایش داده شود و پردازش آن نیز Ajaxایی باشد.
ج) متد Index حالت HttpPost که نهایتا اطلاعات فرم مودال را دریافت خواهد کرد. اگر پردازش موفقیت آمیز بود، نیاز است همانند کدهای فوق return Json صورت گیرد. در غیراینصورت مجددا همان partial view را بازگشت دهید.


کدهای Index.cshtml

@{
    ViewBag.Title = "Index";
    var renderModalPartialViewUrl = Url.Action("RenderModalPartialView", "ModalFormAjax");
    var postDataUrl = Url.Action("Index", "ModalFormAjax");
}
<h2>
    Index</h2>
<a href="#" class="btn btn-primary" id="btnCreate">ثبت اطلاعات</a>

@section JavaScript
{
    <script type="text/javascript">
        $(function () {           
            $('#btnCreate').click(function (e) {
                e.preventDefault(); //می‌خواهیم لینک به صورت معمول عمل نکند

                $.bootstrapModalAjaxForm({
                    postUrl: '@postDataUrl',
                    renderModalPartialViewUrl: '@renderModalPartialViewUrl',
                    renderModalPartialViewData: {},
                    loginUrl: '/login',
                    beforePostHandler: function () {                       
                    },
                    completeHandler: function () {
                        // Refresh: برای حالتیکه نیاز به به روز رسانی کامل صفحه زیرین باشد
                        // location.reload();
                    },
                    errorHandler: function () {
                    }
                });
            });
        });             
    </script>
}
این کدها متناظر هستند با کدهای view اکشن متد Index در حالت Get.
- در اینجا یک لینک ساده در صفحه قرار گرفته و به کمک کلاس btn مجموعه bootstrap به شکل یک دکمه مزین شده است.
- در ادامه نحوه استفاده از افزونه‌ای را که در ابتدای بحث طراحی کردیم، ملاحظه می‌کنید. کار با آن بسیار ساده است و تنها باید مسیرهای ارسال اطلاعات نهایی به سرور یا postDataUrl و مسیر دریافت partial view رندر شده یا renderModalPartialViewUrl به آن معرفی شود. سایر مسایل آن خودکار است.


کدهای _ModalPartialView.cshtml یا همان فرم مودال برنامه

@model Mvc4TwitterBootStrapTest.Models.User
<div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
        &times;</button>
    <h5>
        افزودن کاربر جدید</h5>
</div>
@using (Html.BeginForm("Index", " ModalFormAjax", FormMethod.Post, new { @class = "modal-form" }))
{
    <div class="modal-body">
        @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })
        <fieldset class="form-horizontal">
            <legend>مشخصات کاربر</legend>
            <div class="control-group">
                @Html.LabelFor(model => model.Name, new { @class = "control-label" })
                <div class="controls">
                    @Html.EditorFor(model => model.Name)
                    @Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })
                </div>
            </div>
            <div class="control-group">
                @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
                <div class="controls">
                    @Html.EditorFor(model => model.LastName)
                    @Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })
                </div>
            </div>
        </fieldset>
    </div>
        
    <div class="modal-footer">
        <button class="btn btn-primary" type="submit">
            ارسال</button>
        <button class="btn" data-dismiss="modal" aria-hidden="true">
            انصراف</button>
    </div>
}
در اینجا اطلاعات فرمی را ملاحظه می‌کنید که قرار است به صورت مودال نمایش داده شود. نحوه طراحی آن بر اساس نکات form-horizontal است. همچنین divهای modal-header، modal-body و modal-footer نیز به این فرم ویژه اضافه شده‌اند تا به خوبی توسط bootstrap پردازش گردد.
حاصل نهایی این مبحث را در دو شکل ذیل ملاحظه می‌کنید. صفحه index نمایش دهنده یک دکمه و در ادامه باز شدن یک فرم مودال، پس از کلیک بر روی دکمه ثبت اطلاعات.


 
نظرات مطالب
ASP.NET MVC #21
سلام ، باتشکر از آموزش خوبتون.
قسمت آخر آموزشتون (فعال سازی اعتبار سنجی سمت کلاینت) رو نتونستم متوجه بشم.
من این ویو رو توسط Ajax.ActionLink فراخونی کردم. برای اینکه اعتبار سنجی کلاینت اون کار کنه کدهای زیر کافیه؟
@model MvcApplication1.Models.Com
<script>
    var onSuccess = function (result) {
        $.validator.unobtrusive.parse("#result");
    };
</script>
<div id="result" >
@using (Ajax.BeginForm(
    "SendCommentAction",
    "Home",
    null,
    new AjaxOptions { 
        OnSuccess = "onSuccess",
        UpdateTargetId = "result"
    },
    null)
)
{
    @Html.TextBoxFor(x=>x.Contect) <br/>
@Html.ValidationMessageFor(x=>x.Contect)
<br/>
@Html.ValidationSummary()
    <input type="submit" value="Save" />
} 
</div>
نظرات مطالب
کامپوننت‌ها در Vue.js
نکته تکمیلی: هفت روش برای ساخت کامپوننت در  vue.js

1- استفاده از Strings
بصورت پیش فرض templateها بصورت رشته در جاوااسکریپت تعریف میشوند. تعریف تمپلت‌ها به این شکل ناخوانا می‌باشد و توصیه نمیشود
Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  '
})

2- استفاده از Template literals
در ES6  با استفاده از کاراکتر backticks میتوان یک تمپلت را در چند خط تعریف نمود و خوانایی بهتری نسبت به روش اول (استفادهاز Strings) دارد. 
Vue.component('custom-input', {
   template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

3-استفاده از X-Template
در این روش تمپلیت را درون تگ scriptای که بصورت x-template مشخص شده، درون یک فایل مجزا قرار میدهیم. تنها ایراد این روش جدا نوشتن تمپلیت در فایلی جداگانه و بیرون از کامپوننت می‌باشد.
<script type="x-template" id="my-template" src="template.js"> </script>
<div id="app"></div>

new Vue({
  el: '#app',
  template: '#my-template'
})

4- روش  Inline Template
با اضافه کردن attributeای با نام inline-template مشخص میکنیم که یک کامپوننت می‌باشد (چیزی شبیه slot). نسبت به روش  X-Template بهتر می‌باشد؛ زیرا تعریف کامپوننت در همان فایل جاری انجام میشود و نیازی به جداسازی و زمانی برای بارگذاری یک فایل جاوااسکریپتی نیست.
 <div id="app">
    <gallery inline-template>
....

Vue.component('gallery', {
...

5- استفاده از Render functions
اگر به نوشتن کد جاوااسکریپت تسلط و علاقه دارید، میتوانید این روش را انتخاب کنید و تعریف تمپلیت در اینجا بصورت ایجاد objectای از جاوااسکریپت میباشد. نوشتن چنین تمپلیتی استفاده کامل از قابلیت جاوااسکریپت را امکان پذیر می‌سازد
const template = `
<ul>
  <li v-for="item in items">
   {{ item }}
  </li>
</ul>`;

const compiledTemplate = Vue.compile(template);

new Vue({
  el: '#app',
  data() {
    return {
      items: ['Item1', 'Item2']
    }
  },
  render(createElement) {
    return compiledTemplate.render.call(this, createElement);
  }
});

6- استفاده از JSX
استفاده از این روش برای نوشتن کد کمتر ترجیحا بهتر است و بعنوان یک روش خوب محسوب میشود. البته بصورت مستقیم توسط مرورگر قابلیت رندر و اجرایی ندارد و برای این منظور از babel استفاده میکنیم.
به فرض نوشتن کد زیر توسط روش Render functions
new Vue({
  el: '#app',
  data: {
    msg: 'Show the message'
  },
  methods: {
    hello () {
      alert('Here is the message')
    }
  },
  render (createElement) {
    return createElement(
      'span',
      {
        class: { 'my-class': true },
        on: {
          click: this.hello
        }
      },
      [ this.msg ]
    );
  },
});
با استفاده از روش JSX  تبدیل به کد خواناتر زیر میشود
new Vue({
  el: '#app',
  data: {
    msg: 'Show the message.'
  },
  methods: {
    hello () {
      alert('This is the message.')
    }
  },
  render(h) {
    return (
      <span class={{ 'my-class': true }} on-click={ this.hello } >
        { this.msg }
      </span>
    )
  }
});

7- استفاده از Single File Components
در این روش هر کامپوننت دارای سه قسمت برای تعریف template , script , style  می‌باشد و محبوب‌ترین روش برای ایجاد کامپوننت می‌باشد و درصورت پیچیده شدن و زیاد شدن حجم پروژه روش مناسبی به نظر میرسد. در این مقاله نیز از همین روش استفاده شده است.

نتیجه گیری:
اینکه از چه روشی برای ایجاد کامپوننت استفاده میکنید، کاملا بستگی به اندازه پروژه، توانایی (مانند روش JSX) و علاقه شما دارد. ولی بطور کلی استفاده از روش Single File Components بیشتر مورد توجه می‌باشد.

مطالب
استخراج متن از فایل‌های PDF توسط iTextSharp
پیشنیاز
نحوه ذخیره شدن متن در فایل‌های PDF

حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجی‌های متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.

دو کلاس متفاوت برای استخراج متن از فایل‌های PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;

namespace TestReaders
{
    class Program
    {
        private static void readPdf1()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
               var text = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy());
                File.WriteAllText("page-" + i + "-text.txt", text);
            }
            reader.Close();
        }

        static void Main(string[] args)
        {
            readPdf1();
        }
    }
}
مثال فوق، متن موجود در تمام صفحات یک فایل PDF را در فایل‌های txt جداگانه‌ای ثبت می‌کند. برای نمونه اگر از PDF پیشنیاز یاد شده استفاده کنیم، خروجی آن به نحو زیر خواهد بود:
 Test
ld Wor llo He
Hello People
علت آن نیز پیشتر بررسی گردید. متن، در این فایل ویژه در مختصات خاصی ترسیم شده است. حاصل از دیدگاه خواننده نهایی بسیار خوانا است؛ اما خروجی hello world متنی جالبی از آن استخراج نمی‌شود. SimpleTextExtractionStrategy دقیقا بر اساس همان عملگر‌های Tj و همچنین منابع صفحه، عبارات را یافته و سر هم می‌کند.


ب) LocationTextExtractionStrategy

همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());
                File.WriteAllText("page-" + i + "-text.txt", text);
            }
            reader.Close();
        }
کلاس LocationTextExtractionStrategy هوشمند‌تر عمل کرده و بر اساس عملگرهای هندسی یک فایل PDF، سعی می‌کند جملات و حروف را کنار هم قرار دهد و در نهایت خروجی متنی بهتری را تولید کند. برای نمونه اینبار خروجی متنی حاصل به صورت زیر خواهد بود:
 Test
Hello World
Hello People
این خروجی با آنچه که در صفحه نمایش داده می‌شود تطابق دارد.


استخراج متون فارسی از فایل‌های PDF توسط iTextSharp

روش‌های فوق با PDFهای فارسی هم کار می‌کنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
        static void writePdf2()
        {
            using (var document = new Document(PageSize.A4))
            {
                var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create));
                document.Open();

                FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
                var tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H);

                ColumnText.ShowTextAligned(
                            canvas: writer.DirectContent,
                            alignment: Element.ALIGN_CENTER,
                            phrase: new Phrase("تست می‌شود", tahoma),
                            x: 100,
                            y: 100,
                            rotation: 0,
                            runDirection: PdfWriter.RUN_DIRECTION_RTL,
                            arabicOptions: 0);                
            }

            Process.Start("test.pdf");
        }
از متد فوق، برای تولید یک فایل PDF که متنی فارسی را نمایش می‌دهد استفاده خواهیم کرد. اگر متد readPdf2 را که به همراه LocationTextExtractionStrategy تعریف شده است، بر روی فایل حاصل فراخوانی کنیم، خروجی آن به صورت زیر خواهد بود:
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
برای تبدیل آن به یونیکد خواهیم داشت:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());                
                text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text));
                File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8);
            }
            reader.Close();
        }
اکنون خروجی ثبت شده در فایل متنی حاصل به صورت زیر است:
 ﺩﻮﺷﻲﻣ ﺖﺴﺗ
دقیقا به همان نحوی است که iTextSharp و اکثر تولید کننده‌های PDF فارسی از آن استفاده می‌کنند و اصطلاحا چرخاندن حروف یا تولید Glyph mirrors صورت می‌گیرد. روش‌های زیادی برای چرخاندن حروف وجود دارند. در ادامه از روشی استفاده خواهیم کرد که خود ویندوز در کارهای داخلی‌اش از آن استفاده می‌کند:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;

namespace TestReaders
{
    [SuppressUnmanagedCodeSecurity]
    class GdiMethods
    {
        [DllImport("GDI32.dll")]
        public static extern bool DeleteObject(IntPtr hgdiobj);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern uint GetCharacterPlacement(IntPtr hdc, string lpString, int nCount, int nMaxExtent, [In, Out] ref GcpResults lpResults, uint dwFlags);

        [DllImport("GDI32.dll")]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GcpResults
    {
        public uint lStructSize;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string lpOutString;
        public IntPtr lpOrder;
        public IntPtr lpDx;
        public IntPtr lpCaretPos;
        public IntPtr lpClass;
        public IntPtr lpGlyphs;
        public uint nGlyphs;
        public int nMaxFit;
    }

    public class UnicodeCharacterPlacement
    {
        const int GcpReorder = 0x0002;
        GCHandle _caretPosHandle;
        GCHandle _classHandle;
        GCHandle _dxHandle;
        GCHandle _glyphsHandle;
        GCHandle _orderHandle;

        public Font Font { set; get; }

        public string Apply(string lines)
        {
            if (string.IsNullOrWhiteSpace(lines))
                return string.Empty;

            return Apply(lines.Split('\n')).Aggregate((s1, s2) => s1 + s2);
        }

        public IEnumerable<string> Apply(IEnumerable<string> lines)
        {
            if (Font == null)
                throw new ArgumentNullException("Font is null.");

            if (!hasUnicodeText(lines))
                return lines;

            var graphics = Graphics.FromHwnd(IntPtr.Zero);
            var hdc = graphics.GetHdc();
            try
            {
                var font = (Font)Font.Clone();
                var hFont = font.ToHfont();
                var fontObject = GdiMethods.SelectObject(hdc, hFont);
                try
                {
                    var results = new List<string>();
                    foreach (var line in lines)
                        results.Add(modifyCharactersPlacement(line, hdc));
                    return results;
                }
                finally
                {
                    GdiMethods.DeleteObject(fontObject);
                    GdiMethods.DeleteObject(hFont);
                    font.Dispose();
                }
            }
            finally
            {
                graphics.ReleaseHdc(hdc);
                graphics.Dispose();
            }
        }

        void freeResources()
        {
            _orderHandle.Free();
            _dxHandle.Free();
            _caretPosHandle.Free();
            _classHandle.Free();
            _glyphsHandle.Free();
        }

        static bool hasUnicodeText(IEnumerable<string> lines)
        {
            return lines.Any(line => line.Any(chr => chr >= '\u00FF'));
        }

        void initializeResources(int textLength)
        {
            _orderHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _dxHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _caretPosHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned);
            _classHandle = GCHandle.Alloc(new byte[textLength], GCHandleType.Pinned);
            _glyphsHandle = GCHandle.Alloc(new short[textLength], GCHandleType.Pinned);
        }

        string modifyCharactersPlacement(string text, IntPtr hdc)
        {
            var textLength = text.Length;
            initializeResources(textLength);
            try
            {
                var gcpResult = new GcpResults
                {
                    lStructSize = (uint)Marshal.SizeOf(typeof(GcpResults)),
                    lpOutString = new String('\0', textLength),
                    lpOrder = _orderHandle.AddrOfPinnedObject(),
                    lpDx = _dxHandle.AddrOfPinnedObject(),
                    lpCaretPos = _caretPosHandle.AddrOfPinnedObject(),
                    lpClass = _classHandle.AddrOfPinnedObject(),
                    lpGlyphs = _glyphsHandle.AddrOfPinnedObject(),
                    nGlyphs = (uint)textLength,
                    nMaxFit = 0
                };
                var result = GdiMethods.GetCharacterPlacement(hdc, text, textLength, 0, ref gcpResult, GcpReorder);
                return result != 0 ? gcpResult.lpOutString : text;
            }
            finally
            {
                freeResources();
            }
        }
    }
}
از کلاس فوق در هر برنامه‌ای که راست به چپ را به نحو صحیحی پشتیبانی نمی‌کند، می‌توان استفاده کرد؛ خصوصا برنامه‌های گرافیکی.
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
        private static void readPdf2()
        {
            var reader = new PdfReader("test.pdf");
            int intPageNum = reader.NumberOfPages;
            for (int i = 1; i <= intPageNum; i++)
            {
                var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy());
                text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text));
                text = new UnicodeCharacterPlacement
                {
                    Font = new System.Drawing.Font("Tahoma", 12)
                }.Apply(text);
                File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8);
            }
            reader.Close();
        }
اگر خروجی متد اصلاح شده فوق را بررسی کنیم، دقیقا به «تست می‌شود» خواهیم رسید.

سؤال: آیا این روش با تمام PDFهای فارسی کار می‌کند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer می‌تواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام می‌دهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.
مطالب
کامپوننت‌ها در AngularJS 1.5
در نسخه‌های  AngularJS 1.x عموماً با کمک کنترلرها و دایرکتیوها، می‌توانیم ویژگی‌های جدیدی را به اپلیکیشن‌هایمان اضافه کنیم؛ از دایرکتیوها برای ایجاد عناصر سفارشی HTML می‌توانستیم (می‌توانیم) استفاده کنیم. مشکل دایرکتیوها این است که برای ایجاد یک عنصر سفارشی ساده باید تنظیمات زیادی را انجام دهیم. در نسخه‌ی AngularJS 1.5 یک API جدید با نام کامپوننت معرفی شده است و این قابلیت، مدل ساده‌ی برنامه‌نویسی در کنترلرها و همچنین قدرت دایرکتیوها را در اختیارمان قرار خواهد داد. سینتکس این API خیلی شبیه به استفاده از کامپوننت‌ها در Angular 2.0 است. این یک مزیت مهم محسوب می‌شود؛ زیرا امکان مهاجرت از نسخه‌ی 1.5 به نسخه‌ی 2 را خیلی ساده خواهد کرد.

نحوه‌ی تعریف یک کامپوننت در AngularJS 1.5
همانند کنترلر و دایرکتیو، برای تعریف یک کامپوننت نیز باید از module API استفاده کنیم:

بنابراین برای ایجاد یک کامپوننت می‌توانیم به اینصورت عمل کنیم:

var app = angular.module("dntModule", []);
app.component("pmApp", {
  template: `Hello this is a simple component`
});

همانطور که مشاهده می‌کنید تابع component دو پارامتر را از ورودی دریافت خواهد کرد؛ نام کامپوننت و یک شیء برای تعیین تنظیمات کامپوننت. نام کامپوننت در اینجا به صورت camel case تعریف شده است؛ که در واقع یک convention برای Angular است. در این‌حالت برای استفاده‌ی از کامپوننت باید به اینصورت عمل کنیم:

<pm-app></pm-app>

در قسمت تنظیمات کامپوننت، در ساده‌ترین حالت یک template تعیین شده‌است که بیانگر نحوه‌ی رندر شدن یک کامپوننت می‌باشد. در اینحالت وقتی انگیولار به تگ فوق برسد، یک کامپوننت با نام pmApp را بارگذاری خواهد کرد.


ایجاد یک کامپوننت ساده

در ادامه می‌خواهیم یک کامپوننت ساده را جهت نمایش یکسری URL درون صفحه طراحی کنیم. ساختار صفحه index.html به صورت زیر خواهد بود:

<html ng-app="DNT">
<head>
    <meta charset="UTF-8">
    <title>Using Angular Component</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-3">
                <dnt-widget></dnt-widget>
            </div>
        </div>
    </div>
    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/app.js"></script>
    <script src="scripts/components/dnt-widget.component.js"></script>
</body>
</html>

در اینجا ابتدا توسط دایرکتیو ng-app، به Angular، ماژول‌مان را معرفی کرده‌ایم. سپس مداخل بوت‌استرپ و کتابخانه‌ی font-awesome را مشاهده می‌کنید. در ادامه، کتابخانه‌ی Angular و همچنین فایل app.js جهت معرفی ماژول برنامه معرفی شده‌است. در نهایت نیز یک فایل در مسیر ذکر شده برای قرار دادن کدهای کامپوننت در مسیر scripts/components اضافه شده‌است.

همانطور که ملاحظه می‌کنید، کامپوننت‌مان به صورت یک تگ سفارشی، درون صفحه قرار گرفته است:

<dnt-archive></dnt-archive>

در ادامه باید به Angular، نحوه‌ی تعریف این کامپوننت را اعلام کنیم. بنابراین یک فایل جاوا اسکریپتی را با نام dnt-widget.component، با محتویات زیر ایجاد کنید:

(function () {    
    "use strict";    
    var app = angular.module("DNT");    
    function DntArchiveController() {
      var model = this;      
      model.panel = {
          title: "Panel Title",
          items: [
              {
                  title: "Dotnettips", url: "https://www.dntips.ir"
              },
              {
                  title: "Google", url: "http://www.google.con"
              },
              {
                  title: "Yahoo", url: "http://www.yahoo.con"
              }
          ]
      };  
    };
    
    app.component("dntWidget", {
        templateUrl: '/scripts/components/dnt-widget.component.html',
        controllerAs: "model",
        controller: DntArchiveController
    });
} ());

توضیح کدهای فوق:

همانطور که مشاهده می‌کنید، برای پارمتر دوم کامپوننت، سه پراپرتی را تعیین کرده‌ایم:

templateUrl: به کمک این پراپرتی به Angular گفته‌ایم که محتوای قالب این کامپوننت، درون یک فایل HTML مجزا قرار دارد و به صورت linked template می‌باشد.

controllerAs: یکی از مزایای استفاده از کامپوننت‌ها، استفاده از controller as syntax می‌باشد. لازم به ذکر است اگر این پراپرتی را مقداردهی نکنیم، به صورت پیش‌فرض مقدار ctrl$ در نظر گرفته خواهد شد.

controller: مزیت دیگر کامپوننت‌ها، استفاده از کنترلرها است. با استفاده از این پراپرتی، یک کنترلر را برای کامپوننت‌مان رجیستر کرده‌ایم. در نتیجه زمانیکه‌ی Angular می‌خواهد کامپوننت‌مان را نمایش دهد، تابع تعریف شده برای این پراپرتی، جهت ایجاد یک controller instance فراخوانی خواهد شد. بنابراین هر پراپرتی یا تابعی که برای این controller instance تعریف کنیم، به راحتی درون ویوی آن جهت اعمال بایندینگ در دسترس خواهد بود (در نتیجه نیازی به scope$ نخواهد بود).

درون کنترلر نیز برای راحتی کار و همچنین به عنوان یک best practice، مقدار this را توسط یک متغیر با نام model، کپچر کرده‌ایم. در اینجا یک شیء را با نام panel نیز به مدل اضافه کرده‌ایم.


محتویات تمپلیت:

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">
            <span class="fa fa-archive"></span>
            {{ model.panel.title}}
        </h3>
    </div>
    <ul class="list-group">
        <li class="list-group-item" ng-repeat="item in model.panel.items">
            <span class="fa fa-industry"></span>
            <a href="{{ item.url }}">{{ item.title }}</a>
        </li>
    </ul>
</div>

ویوی کامپوننت پیچیدگی خاصی ندارد. همانطور که مشاهده می‌کنید یک پنل بوت‌استرپی را ایجاد کرده‌ایم که مقدار عنوان آن و همچنین آیتم‌های آن، از شیء اتچ شده به مدل دریافت خواهند شد. بنابراین اکنون اگر برنامه را اجرا کنید، خروجی کامپوننت را به اینصورت مشاهده خواهید کرد:



همانطور که مشاهده می‌کنید استفاده از کامپوننت‌ها در Angular 1.5 در مقایسه با ایجاد دایرکتیوها و کنترلر‌ها خیلی ساده‌تر است. در واقع امکانات این API جدید تنها به مثال فوق ختم نمی‌شود؛ بلکه این API یک سیستم مسیریابی جدید را نیز معرفی کرده است که در قسمت‌های بعدی به آن نیز خواهیم پرداخت.


جهت تکمیل بحث نیز یک تقویم شمسی ساده را در اینجا قرار داده‌ام. می‌توانید جهت مرور بحث جاری به کدهای آن مراجعه کنید. البته هدف از تعریف این پروژه تنها یک مثال ساده برای معرفی کامپوننت‌ها بود و طبیعتاً باگ‌های زیادی دارد. اگر مایل بودید می‌توانید در توسعه‌ی آن مشارکت نمائید.


کدهای این قسمت را نیز از اینجا می‌توانید دریافت کنید.

مطالب
نگاهی به درون سیستم Binding در WPF و یافتن مواردی که هنوز در حافظه‌اند
در WPF، زیر ساخت‌های ComponentModel توسط کلاسی به نام PropertyDescriptor، منابع Binding موجود در قسمت‌های مختلف برنامه را در جدولی عمومی ذخیره و نگهداری می‌کند. هدف از آن، مطلع بودن از مواردی است که نیاز دارند توسط مکانیزم‌هایی مانند INotifyPropertyChanged و DependencyProperty ها، اطلاعات اشیاء متصل را به روز کنند.
در این سیستم، کلیه اتصالاتی که Mode آن‌ها به OneTime تنظیم نشده است، به صورت اجباری دارای یک valueChangedHandlers متصل توسط سیستم PropertyDescriptor خواهند بود و در حافظه زنده نگه داشته می‌شوند؛ تا بتوان در صورت نیاز، توسط سیستم binding اطلاعات آن‌ها را به روز کرد.
همین مساله سبب می‌شود تا اگر قرار نیست خاصیتی برای نمونه توسط مکانیزم INotifyPropertyChanged اطلاعات UI را به روز کند (یک خاصیت معمولی دات نتی است) و همچنین حالت اتصال آن به OneTime نیز تنظیم نشده، سبب مصرف حافظه بیش از حد برنامه شود.
اطلاعات بیشتر
A memory leak may occur when you use data binding in Windows Presentation Foundation

راه حل آن هم ساده است. برای اینکه valueChangedHandler ایی به خاصیت ساده‌ای که قرار نیست بعدها UI را به روز کند، متصل نشود، حالت اتصال آن‌را باید به OneTime تنظیم کرد.


سؤال: در یک برنامه بزرگ که هم اکنون مشغول به کار است، چطور می‌توان این مسایل را ردیابی کرد؟

برای دستیابی به اطلاعات کش Binding در WPF، باید به Reflection متوسل شد. به این ترتیب در برنامه جاری، در کلاس PropertyDescriptor به دنبال یک کلاس خصوصی تو در توی دیگری به نام ReflectTypeDescriptionProvider خواهیم گشت (این اطلاعات از طریق مراجعه به سورس دات نت و یا حتی برنامه‌های ILSpy و Reflector قابل استخراج است) و سپس در این کلاس خصوصی داخلی، فیلد خصوصی propertyCache آن‌را که از نوع  HashTable است استخراج می‌کنیم:
 var reflectTypeDescriptionProvider = typeof(PropertyDescriptor).Module.GetType("System.ComponentModel.ReflectTypeDescriptionProvider");
var propertyCacheField = reflectTypeDescriptionProvider.GetField("_propertyCache",
BindingFlags.Static | BindingFlags.NonPublic);


اکنون به لیست داخلی Binding نگهداری شونده توسط WPF دسترسی پیدا کرده‌ایم. در این لیست به دنبال مواردی خواهیم گشت که فیلد valueChangedHandlers به آن‌ها متصل شده است  و در حال گوش فرا دادن به سیستم binding هستند (سورس کامل و طولانی این مبحث را در پروژه پیوست شده می‌توانید ملاحظه کنید).


یک مثال: تعریف یک کلاس ساده، اتصال آن و سپس بررسی اطلاعات درونی سیستم Binding

فرض کنید یک کلاس مدل ساده به نحو ذیل تعریف شده است:
namespace WpfOneTime.Models
{
    public class User
    {
        public string Name { set; get; }
    }
}
سپس این کلاس به صورت یک List، توسط ViewModel برنامه در اختیار View متناظر با آن قرار می‌گیرد:
using WpfOneTime.Models;
using System.Collections.Generic;

namespace WpfOneTime.ViewModels
{
    public class MainWindowViewModel
    {
        public IList<User> Users { set; get; }

        public MainWindowViewModel()
        {
            Users = new List<User>();
            for (int i = 0; i < 1000; i++)
            {
                Users.Add(new User { Name = "name " + i });
            }
        }
    }
}
تعاریف View برنامه نیز به نحو زیر است:
<Window x:Class="WpfOneTime.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModels="clr-namespace:WpfOneTime.ViewModels"        
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ViewModels:MainWindowViewModel x:Key="vmMainWindowViewModel" />
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">        
        <ListBox ItemsSource="{Binding Users}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>
همه چیز در آن معمولی به نظر می‌رسد. ابتدا به ViewModel برنامه دسترسی یافته و  DataContext را با آن مقدار دهی می‌کنیم. سپس اطلاعات این لیست را توسط یک ListBox نمایش خواهیم داد.
خوب؛ اکنون اگر اطلاعات HashTable داخلی سیستم Binding را در مورد View فوق بررسی کنیم به شکل زیر خواهیم رسید:


بله. تعداد زیادی خاصیت Name زنده و موجود در حافظه باقی هستند که تحت ردیابی سیستم Binding می‌باشند.
در ادامه، نکته‌ی ابتدای بحث را جهت تعیین حالت Binding به OneTime، به View فوق اعمال می‌کنیم (یک سطر ذیل باید تغییر کند):
 <TextBlock Text="{Binding Name, Mode=OneTime}" />
در این حالت اگر نگاهی به سیستم ردیابی WPF داشته باشیم، دیگر خبری از اشیاء زنده دارای خاصیت Name در حال ردیابی نیست:


به این ترتیب می‌توان در لیست‌های طولانی، به مصرف حافظه کمتری در برنامه WPF خود رسید.
بدیهی است این نکته را تنها در مواردی می‌توان اعمال کرد که نیاز به به‌روز رسانی‌های ثانویه اطلاعات UI در کدهای برنامه وجود ندارند.


چطور از این نکته برای پروفایل یک برنامه موجود استفاده کنیم؟

کدهای برنامه را از انتهای بحث دریافت کنید. سپس دو فایل ReflectPropertyDescriptorWindow.xaml و ReflectPropertyDescriptorWindow.xaml.cs آن‌را به پروژه خود اضافه نمائید و در سازنده پنجره اصلی برنامه، کد ذیل را فراخوانی نمائید:
 new ReflectPropertyDescriptorWindow().Show();
کمی با برنامه کار کرده و منتظر شوید تا لیست نهایی اطلاعات داخلی Binding ظاهر شود. سپس مواردی را که دارای HandlerCount بالا هستند، مدنظر قرار داده و بررسی نمائید که آیا واقعا این اشیاء نیاز به valueChangedHandler متصل دارند یا خیر؟ آیا قرار است بعدها UI را از طریق تغییر مقدار خاصیت آن‌ها به روز نمائیم یا خیر. اگر خیر، تنها کافی است نکته Mode=OneTime را به این Bindingها اعمال نمائیم.

دریافت کدهای کامل پروژه این مطلب
WpfOneTime.zip