عنوان:

‫دریافت پارامترهای یک شیء در وضعیت غیرقابل مشاهده/غیرفعال


نویسنده: هادی مزارعی
تاریخ: ۱۴۰۴/۰۵/۱۶ ۲۲:۰۷
آدرس: www.dntips.ir
  1. پروژه از نوع Windows Form Application هستش
  2. یک TabControl با پنج Tab دارم
  3. در هر یک از برگه‌ها یک DataGridView وجود دارد (نوع شیء مهم نیست و می‌تواند یک Panel نیز باشد)
  4. بعد از بارگذاری فرم، وقتی پارامتر Width گرید‌ که در برگه پنجم (هنوز کاربر فعال نکرده) قرار دارد را دریافت میکنم اشتباه است (500)
  5. اما بعد از کلیک روی برگه پنجم و فعال کردن برگه، مقدار برگشتی صحیح خواهد بود (700)

با توجه به اینکه در زمان طراحی فرم (Design Time)، ابعاد فرم کوچک است ولی در زمان اجرا باید بصورت Mazimize باشد روش زیر را اجرا کردم ولی نتیجه نداد:
private void tabControl1_Resize(object sender, EventArgs e)
{
    foreach (TabPage t in tabControl1.TabPages)
    {
        t.PerformLayout();
        t.Refresh();
        t.Invalidate();
    }
}


مشاهده مطلب اصلی

نظرات

  • وحید نصیری در ۱۴۰۴/۰۵/۱۷ ۱۰:۰۱
    وقتی فرم در حالت Maximize اجرا می‌شود، کنترل‌های موجود در تب‌هایی که هنوز فعال نشده‌اند (غیرقابل مشاهده هستند) ممکن است به درستی رندر نشده و ابعاد آن‌ها به‌روز نشود. این رفتار به دلیل بهینه‌سازی رندرینگ در ویندوز فرم‌ها رخ می‌دهد، زیرا کنترل‌های موجود در تب‌های غیرفعال تا زمان فعال شدن کاملاً بارگذاری نمی‌شوند.

    راه‌حل‌های پیشنهادی
    برای رفع این مشکل، باید اطمینان حاصل کنید که کنترل‌های موجود در تب‌های غیرفعال نیز به درستی بارگذاری و اندازه‌گیری شوند. روش‌های زیر را می‌توانید امتحان کنید:

    1. اجبار به بارگذاری تمام تب‌ها در زمان لود فرم
    قبل از خواندن پارامترهای کنترل‌ها (مثل Width)، می‌توانید تمام تب‌ها را به صورت برنامه‌ریزی‌شده فعال کنید تا رندر شوند و سپس به تب اولیه برگردید. این کار باعث می‌شود کنترل‌های داخل تب‌ها به درستی بارگذاری شوند.
    private void Form1_Load(object sender, EventArgs e)
    {
        // فرم را به حالت Maximize تنظیم کنید
        this.WindowState = FormWindowState.Maximized;
    
        // تمام تب‌ها را به ترتیب فعال کنید تا رندر شوند
        foreach (TabPage tab in tabControl1.TabPages)
        {
            tabControl1.SelectedTab = tab; // فعال کردن تب
        }
    
        // بازگشت به تب اولیه
        tabControl1.SelectedIndex = 0;
    
        // حالا می‌توانید Width را بررسی کنید
        MessageBox.Show(dataGridViewInTab5.Width.ToString()); // باید مقدار صحیح (700) را نمایش دهد
    }
    توضیح: با فعال کردن هر تب به صورت برنامه‌ریزی‌شده، کنترل‌های داخل آن تب (مثل DataGridView) رندر شده و ابعاد آن‌ها به‌روز می‌شود.

    2. استفاده از Layout Engine برای به‌روزرسانی کنترل‌ها
    روش دیگری که می‌توانید استفاده کنید، اجبار به به‌روزرسانی Layout تمام تب‌ها و کنترل‌های داخل آن‌هاست. این کار را می‌توانید با استفاده از PerformLayout به شکل زیر انجام دهید:
    private void Form1_Load(object sender, EventArgs e)
    {
        // فرم را به حالت Maximize تنظیم کنید
        this.WindowState = FormWindowState.Maximized;
    
        // به‌روزرسانی Layout برای تمام تب‌ها
        foreach (TabPage tab in tabControl1.TabPages)
        {
            tabControl1.SelectedTab = tab; // فعال کردن تب برای اطمینان از رندر
            tab.PerformLayout(); // اجبار به محاسبه Layout
            foreach (Control ctrl in tab.Controls)
            {
                ctrl.PerformLayout(); // اجبار به محاسبه Layout برای کنترل‌های داخل تب
            }
        }
    
        // بازگشت به تب اولیه
        tabControl1.SelectedIndex = 0;
    
        // بررسی Width
        MessageBox.Show(dataGridViewInTab5.Width.ToString()); // باید مقدار صحیح (700) را نمایش دهد
    }

    3. استفاده از SuspendLayout و ResumeLayout
    اگر کنترل‌های زیادی دارید و نگران عملکرد هستید، می‌توانید از SuspendLayout و ResumeLayout برای بهینه‌سازی فرآیند استفاده کنید:
    private void Form1_Load(object sender, EventArgs e)
    {
        // فرم را به حالت Maximize تنظیم کنید
        this.WindowState = FormWindowState.Maximized;
    
        // توقف موقت Layout
        tabControl1.SuspendLayout();
    
        // فعال کردن تمام تب‌ها برای رندر
        foreach (TabPage tab in tabControl1.TabPages)
        {
            tabControl1.SelectedTab = tab;
            tab.PerformLayout();
        }
    
        // بازگشت به تب اولیه
        tabControl1.SelectedIndex = 0;
    
        // از سرگیری Layout
        tabControl1.ResumeLayout(true);
    
        // بررسی Width
        MessageBox.Show(dataGridViewInTab5.Width.ToString()); // باید مقدار صحیح (700) را نمایش دهد
    }

    4. استفاده از SizeChanged یا Resize به درستی
    روش شما در استفاده از رویداد Resize خوب است، اما ممکن است به دلیل زمان‌بندی اجرا (قبل از رندر کامل کنترل‌ها) کار نکند. به جای آن، می‌توانید این کار را در رویداد Shown فرم انجام دهید که پس از نمایش کامل فرم اجرا می‌شود:
    private void Form1_Shown(object sender, EventArgs e)
    {
        // فرم به حالت Maximize تنظیم شده است
        this.WindowState = FormWindowState.Maximized;
    
        // به‌روزرسانی Layout برای تمام تب‌ها
        foreach (TabPage tab in tabControl1.TabPages)
        {
            tabControl1.SelectedTab = tab;
            tab.PerformLayout();
        }
    
        // بازگشت به تب اولیه
        tabControl1.SelectedIndex = 0;
    
        // بررسی Width
        MessageBox.Show(dataGridViewInTab5.Width.ToString()); // باید مقدار صحیح (700) را نمایش دهد
    }

    نکات مهم
    • چرا مشکل رخ می‌دهد؟ ویندوز فرم‌ها برای بهینه‌سازی، کنترل‌های داخل تب‌های غیرفعال را تا زمان فعال شدن رندر نمی‌کنند. به همین دلیل، پارامترهایی مثل Width ممکن است مقدار پیش‌فرض یا نادرست (مثل 500) را برگردانند.
    • چرا روش شما کار نکرد؟ فراخوانی PerformLayout، Refresh و Invalidate به تنهایی کافی نیست، زیرا تب‌های غیرفعال هنوز رندر نشده‌اند. باید ابتدا تب را فعال کنید تا کنترل‌های داخل آن بارگذاری شوند.
    • کارایی: اگر تعداد تب‌ها یا کنترل‌ها زیاد است، استفاده از SuspendLayout و ResumeLayout می‌تواند عملکرد را بهبود ببخشد.
    • آلترناتیو: اگر نیازی به دسترسی فوری به ابعاد کنترل‌ها ندارید، می‌توانید این بررسی را به زمانی موکول کنید که کاربر تب را فعال می‌کند (مثلاً در رویداد TabControl.SelectedIndexChanged).

    تست نهایی
    برای اطمینان از کارکرد صحیح، کد زیر را در رویداد Form1_Load یا Form1_Shown قرار دهید و بررسی کنید که آیا Width صحیح (700) برگردانده می‌شود یا خیر:
    private void Form1_Load(object sender, EventArgs e)
    {
        this.WindowState = FormWindowState.Maximized;
        tabControl1.SuspendLayout();
        foreach (TabPage tab in tabControl1.TabPages)
        {
            tabControl1.SelectedTab = tab;
            tab.PerformLayout();
        }
        tabControl1.SelectedIndex = 0;
        tabControl1.ResumeLayout(true);
        MessageBox.Show(dataGridViewInTab5.Width.ToString()); // باید 700 باشد
    }
    • هادی مزارعی در ۱۴۰۴/۰۵/۱۷ ۱۰:۴۸
      ممنون جناب نصیری. راه حل پیشنهادی جواب داد.

      چرا به مشخصات یک شیء غیرقابل مشاهده نیاز داشتم؟
      1. اصل ماجرا مربوط به کنترل CesLoadScreen بود که کنترل‌های CesGridView و CesGridViewPro در درون خودشان این صفحه را در زمان دریافت DataSource نمایش و در پایان حذف می‌کردند (البته با تنظیم کاربر).
      2. در یکی از فرم‌های برنامه با تغییر مقدار ComboBox باید داده‌های مربوط به هر DataGridView در تمام Tabها بروزرسانی شود و چون CesLoadScreen موقعیت DGV رو نداشت، در موقعیت و اندازه ناصحیح نمایش داده می‌شد (بصورت Modal در فرم جاری نمایش داده می‌شد). و این کار باعث می‌شد کاربر چندین LoadScreen که بدون هدف در صفحه وجود داشت را مشاهده کند که قطعا حس خوبی را تجربه نمی‌کرد.


      راه حل نهایی
      آنچه که در این مطلب توسط Jimi ذکر شده، دلیل اصلی را نبود Handle برای کنترل‌هایی که هنوز در Tabهای غیرفعال وجود دارند عنوان کرده. بنابراین در کدهای برنامه و در جایی که باید CesLoadScreen نمایش داده شود متد IsVisible دو فاکتور را بررسی خواهد کرد:
      1. آیا کنترل مورد نظر دارای Handle می‌باشد یا خیر (IsHandleCreated).
      2. آیا در صورت دارا بودن Handle، کنترل در صفحه قابل رویت است؟ چون ممکن است توسط هر شیء یا فرم و... پوشانده شود.

      با اصلاح کد از ایجاد غیر ضروری LoadScreen (با وجود در دسترس بودن ابعاد نهایی) جلوگیری شده است
      public static Form Create(
          Control control,
          bool coverParentContainer = true,
          bool coverParentForm = false,
          string title = "Loading...",
          double opacity = 0.5)
      {
          var frm = new CesLoadScreen();
          frm._title = title;
          frm.Opacity = opacity;
      
          if (!IsVisible(control))
              return frm;
      
          SetLoadingScreenSize(frm, coverParentContainer, coverParentForm, control);
          control.Resize += (s, e) => SetLoadingScreenSize(frm, coverParentContainer, coverParentForm, control);
      
          frm.Show(control.FindForm());
          Application.DoEvents();
      
          return frm;
      }
      
      public static bool IsVisible(Control control)
      {
          Rectangle screenBounds = Screen.FromControl(control).Bounds;
          Rectangle controlBounds = control.RectangleToScreen(control.ClientRectangle);
      
          bool isOnScreen = screenBounds.IntersectsWith(controlBounds);
      
          if (!control.IsHandleCreated || !isOnScreen)
              return false;
      
          if (!control.Visible)
              return false;
      
          return true;
      }