پادشاهِ کُدنویسا شو!
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

لگ فرم های ویندوز اپلیکیشن در هنگام form_load در پروژه های Windows Forms سی شارپ

9 بازدید 0 نظر ۱۴۰۵/۰۶/۲۰
وقتی یک فرم دارای کنترل‌های متعدد است یا عملیات سنگینی مانند فراخوانی دیتابیس، خواندن فایل یا محاسبات پیجیده را در متدهای Form_Load یا سازنده (Constructor) اجرا می‌کند، UI Thread بلوکه می‌شود. این بلوکه‌شدن باعث بروز پدیده‌هایی مانند عدم پاسخگویی (Not Responding)، سیاهی یا سفید شدن صفحه هنگام Render، و پرش‌های تصویری (Flicker) می‌شود.

فعال‌سازی قابلیت Double Buffering برای تمامی کنترل‌ها

به‌طور پیش‌فرض، سی‌شارپ کنترل‌ها و فرم را به‌صورت مستقیم روی صفحه رسم می‌کند که منجر به Flickering و سیاهی لحظه‌ای می‌شود. Double Buffering تصویر فرم را ابتدا در یک حافظه پنهان (Off-screen buffer) رسم کرده و پس از اتمام پردازش گرافیکی، کل تصویر را یک‌باره روی مانیتور قرار می‌دهد.

 

الف) فعال‌سازی روی خود فرم

در سازنده فرم (Constructor)، خطوط زیر را اضافه کنید:

public MainForm()
{
    InitializeComponent();

    // فعال‌سازی Double Buffering برای فرم
    this.DoubleBuffered = true;
    this.SetStyle(ControlStyles.OptimizedDoubleBuffer | 
                  ControlStyles.UserPaint | 
                  ControlStyles.AllPaintingInWmPaint, true);
    this.UpdateStyles();
}

ب) اعمال Double Buffering روی تمامی کنترل‌های درون فرم (Panel, TableLayoutPanel, DataGridView)

خاصیت DoubleBuffered در برخی کنترل‌ها مانند Panel یا TableLayoutPanel در سطح protected قرار دارد و از طریق Property Grid در ویژوال استودیو قابل دسترسی نیست. برای رفع این مشکل، می‌توانید از یک کلاس Helper با Reflection برای تمامی کنترل‌های فرم استفاده کنید:

public static class ExtensionMethods
{
    public static void EnableDoubleBuffer(this Control control)
    {
        var propertyInfo = typeof(Control).GetProperty("DoubleBuffered", 
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        propertyInfo?.SetValue(control, true, null);
    }
}

// نحوه استفاده در متد Form_Load یا Constructor:
private void MainForm_Load(object sender, EventArgs e)
{
    foreach (Control c in this.Controls)
    {
        c.EnableDoubleBuffer();
    }
}

 

توقف و بازگشایی رسم UI با SuspendLayout و ResumeLayout

هنگام لود فرم یا اضافه شدن کنترل‌های متعدد، سیستم‌عامل Windows با هر تغییر اندازه یا درج کنترل جدید، عملیات Layout و Paint را مجدداً اجرا می‌کند. با متوقف کردن این پردازش‌ها تا پایان بارگذاری کامل، لود فرم بسیار سریع‌تر و بدون پرش انجام می‌شود.

private void LoadControlsDynamically()
{
    // ۱. متوقف کردن چیدمان و رسم گرافیکی
    this.SuspendLayout();

    try
    {
        // اضافه کردن یا تغییر سایز کنترل‌ها
        for (int i = 0; i < 50; i++)
        {
            Button btn = new Button { Text = $"دکمه {i}", Location = new Point(10, i * 30) };
            this.Controls.Add(btn);
        }
    }
    finally
    {
        // ۲. بازگرداندن وضعیت به حالت عادی و اجرای یک‌باره Layout
        this.ResumeLayout(true);
    }
}

 

مدیریت پیام‌های ویندوز (Override کردن CreateParams)

یکی از موثرترین روش‌ها برای جابه‌جایی و لود فرم‌های سنگین بدون سیاهی و پرش، دستکاری پارامترهای ساخت فرم در سطح API ویندوز است. با افزودن پرچم WS_EX_COMPOSITED (به مقدار 0x02000000) به فرم، سیستم‌عامل مجبور می‌شود تمام کنترل‌های فرزند فرم را به‌صورت یک لایه یکپارچه در حافظه پنهان رندر کند.

کد زیر را درون کلاس فرم خود قرار دهید:

protected override CreateParams CreateParams
{
    get;
    {
        CreateParams cp = base.CreateParams;
        // WS_EX_COMPOSITED: رندر یکپارچه تمام فرزندان فرم در حافظه
        cp.ExStyle |= 0x02000000; 
        return cp;
    }
}

نکته: این روش سیاهی و Flickering ناشی از لود کنترل‌ها را کاملاً از بین می‌برد، اما ممکن است در سیستم‌های قدیمی مصرف حافظه رم را کمی افزایش دهد.

 

آزادسازی UI Thread با به‌کارگیری Async / Await

شایع‌ترین دلیل سیاهی فرم هنگام نمایش، اجرای کدهای زمان‌بر در متد Form_Load یا سازنده فرم است. اجرای کدهایی نظیر کوئری‌های EF Core/SQL Server، خواندن فایل‌ها، یا فراخوانی REST API به‌صورت synchronous، نخ UI را مسدود کرده و مانیتور قادر به دریافت پیام‌های WM_PAINT نخواهد بود.

 

روش اشتباه (Synchronous):

// این کد باعث سیاهی و هنگ کردن لحظه‌ای فرم می‌شود
private void MainForm_Load(object sender, EventArgs e)
{
    var data = repository.GetHeavyDataFromDatabase(); // مسدود شدن UI
    dataGridView1.DataSource = data;
}

روش صحیح (Asynchronous):

private async void MainForm_Load(object sender, EventArgs e)
{
    // فرم نمایش داده می‌شود اما لیست در حال بارگذاری است
    showLoadingSpinner(true);

    // اجرا در پس‌زمینه بدون مسدود کردن UI Thread
    var data = await Task.Run(() => repository.GetHeavyDataFromDatabaseAsync());

    dataGridView1.DataSource = data;
    showLoadingSpinner(false);
}

 

۵. مدیریت صحیح لود اولیه و ترتیبات فراخوانی (Lazy Loading & Form_Shown)

رویداد Form_Load قبل از رندر شدن فرم و روی صفحه قرار گرفتن آن اجرا می‌شود. اگر کدهای سنگین را به رویداد Form_Shown منتقل کنید، فرم ابتدا به‌صورت کاملاً رندرشده به کاربر نمایش داده می‌شود و سپس عملیات بارگذاری دیتابیس یا فایل‌ها آغاز خواهد شد.

 

رویداد

زمان اجرا

کاربرد مناسب

Constructor

هنگام ساخت Instance کلاس

مقداردهی اولیه متغیرها و InitializeComponent

Form_Load

قبل از ظاهر شدن فرم

تنظیمات ظاهری سبک (Theme، فونت، دسترسی‌ها)

Form_Shown

پس از رسم کامل فرم روی مانیتور

کوئری‌های دیتابیس، فراخوانی API و لود داده‌ها

 

private async void MainForm_Shown(object sender, EventArgs e)
{
    // فرم با سرعت بالا ظاهر شده و کاربر سیاهی نمی‌بیند
    await LoadDashboardDataAsync();
}

 

بهینه‌سازی کنترل‌های گرافیکی و ساختار فرم

  • کاهش تعداد کنترل‌های هم‌زمان: استفاده از تعداد بسیار زیاد کنترل (مانند ۵۰ عدد Label یا Panel جداگانه) فشار مضاعفی به GDI+ وارد می‌کند. در صورت امکان، داده‌ها را در کنترل‌های مجتمع مانند DataGridView یا ListView با حالت VirtualMode نمایش دهید.

  • استفاده از BeginUpdate و EndUpdate: در کنترل‌هایی نظیر ListBox ،ListView ،TreeView و ComboBox قبل از افزودن انبوه داده‌ها، رسم را متوقف کنید:

    treeView1.BeginUpdate();
    // افزودن ۱۰۰۰ نود به درخت
    treeView1.Nodes.AddRange(...);
    treeView1.EndUpdate();
    
  • جایگزینی TableLayoutPanel: کنترل TableLayoutPanel در WinForms به دلیل محاسبات پیچیده چیدمان (Layout Engine)، کندترین کنترل در رندر شدن است. در صورت بروز تاخیر، آن را با Panel ساده یا چیدمان دستی/کدنویسی‌شده جایگزین کنید.

  • سخت‌افزار و Hardware Acceleration: فرم‌های WinForms به‌صورت متکی بر پردازنده (CPU) و API قدیمی GDI+ رندر می‌شوند. برای فرم‌های فوق‌العاده سنگین گرافیکی، استفاده از UserControl‌های بهینه‌شده یا مهاجرت بخش‌های گرافیکی به WPF / SkiaSharp پیشنهاد می‌شود.

 

چک‌لیست...

  • متد CreateParams را در فرم خود Override کرده و پایه WS_EX_COMPOSITED را فعال کنید.
  • خاصیت DoubleBuffered = true را برای فرم و کنترل‌های اصلی تعریف کنید.
  • تمام کدهای مربوط به دریافت داده از دیتابیس را به async/await مجهز کرده و از متد Form_Load به Form_Shown منتقل کنید.
  • هنگام افزودن کنترل‌های پویا، کدها را بین SuspendLayout() و ResumeLayout() قرار دهید.

 

لینک استاندارد شده: 2U9

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه