2011-05-27 37 views
3

我有一個用戶控件和自定義繪畫。從我所知道的構造函數中正確設置樣式。基本代碼:如果窗體沒有焦點,OnPaint沒有被調用

public partial class LineChart2 : UserControl 
{ 
    public LineChart2() 
    { 
     InitializeComponent(); 

     //Set control styles to eliminate flicker on redraw and to redraw on resize 
     this.SetStyle(
      ControlStyles.ResizeRedraw | 
      ControlStyles.UserPaint | 
      ControlStyles.AllPaintingInWmPaint | 
      ControlStyles.DoubleBuffer, 
      true); 

     SetDefaultValues(); 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 
    // breakpoint set here for verification 
     Paint~misc stuff(e.Graphics); 

     base.OnPaint(e); 
    } 

    private void UpdateGraph() 
    { 
    // this is called when the data that the control depends on changes 
     ~update stuff(); 

     this.Invalidate(); 
     //this.Refresh(); 
    } 
} 

控制被包含在標準的WinForm一個面板內。

我試過Invalidate和Refresh。

使用Invalidate()時,只要包含的窗體具有焦點,控件就會正確重繪。繪圖很流暢。當我將焦點切換到另一個表單時,即使事件仍在觸發,繪圖也會停止,並且this.Invalidate()仍然被調用。該表格在屏幕上仍然完全可見。

使用Refresh()時,無論表單是否具有焦點,控件都會重繪,但繪圖不斷閃爍,就好像繞過雙緩衝機制一樣。

那麼,無論焦點如何,我如何獲取Invalidate消息以正確調用OnPaint方法?

回答

1

Documentation說:

調用Invalidate方法不 強制同步漆;強制執行 同步繪圖,在調用Invalidate 方法後調用更新 方法。

您是否試過在Invalidate之後致電Update

+0

調用更新後無效使其行爲就像調用刷新 - 常量閃爍。 – dsmith 2011-05-27 22:00:44

+0

使用這也意味着我不能捕捉事件像鼠標事件,因爲(我認爲)同步調用搶佔它們(圖形每秒更新10-20次,並且控件的鼠標操作剛剛完成如果使用更新,則不起作用)。 – dsmith 2011-05-27 22:16:14

+0

已經發現閃爍的問題。這是因爲每次刷新都有兩次調用Invalidate,一次是清除舊數據,另一次是增加新數據.Update()顯然會迫使這兩次繪製發生,而不是像正常事件隊列那樣將它們摺疊爲一次調用。調整,以便只有一個Invalidate調用修復了事件,並且鼠標事件是可管理的(儘管不像我想的那樣流暢)。我對事件隊列的非處理仍然不滿意,但現在它運行良好。標記已被接受。 – dsmith 2011-06-07 21:17:40

1

您也可以嘗試使Invalidate(true)觸發子控件重繪。

+0

我試過了。沒有效果。也試過無效(這個。ClientRectangle)來優化某個0尺寸的矩形或其他東西,但它也不起作用。 – dsmith 2011-05-27 22:04:49

+0

是否有可能在此時顯示另一個模式對話框?這將禁止繪製消息得到處理。您可以在Invalidate後立即執行比Application.DoEvents()更好的強制操作 - 但這有點冒險。 – 2011-05-27 22:11:34

+0

同時顯示多個表單(MDI應用的一部分),但它們不是模態的。是的,那時Application.DoEvents()完全鎖定了程序。 – dsmith 2011-05-27 22:42:21

0

您不應該強制控制經常重繪(更新或刷新)。 UI可能無法響應,其他控件可能無法更新,因爲您正在將所有UI注意力都放在強制同步刷新上。 正確的方法是僅在UI準備好時才繪製。爲此你需要一個渲染循環。每當UI準備繪製東西時,ApplicationLoopDoWork就會被觸發。這段時間取決於機器速度和正在重繪的內容。

該課程以this post on Tom Miller's Blog爲基礎。

這裏是我用來控制的類。 僅在ApplicationLoopDoWork調用上進行更新。

using System; 
    using System.Runtime.InteropServices; 
    using System.Threading; 
    using System.Windows.Forms; 

    namespace Utilities.UI 
    { 
    /// <summary> 
    /// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible). 
    /// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx 
    /// </summary> 
    public sealed class WinFormsAppIdleHandler 
    { 
     private readonly object _completedEventLock = new object(); 
     private event EventHandler _applicationLoopDoWork; 

     //PRIVATE Constructor 
     private WinFormsAppIdleHandler() 
     { 
      Enabled = false; 
      SleepTime = 10; 

     } 

     /// <summary> 
     /// Singleton from: 
     /// http://csharpindepth.com/Articles/General/Singleton.aspx 
     /// </summary> 
     private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler()); 
     public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } } 

     private bool _enabled = false; 

     /// <summary> 
     /// Gets or sets if must fire ApplicationLoopDoWork event. 
     /// </summary> 
     public bool Enabled 
     { 
      get { return _enabled; } 
      set { 
       if (value) 
        Application.Idle += Application_Idle; 
       else 
        Application.Idle -= Application_Idle; 

       _enabled = value; 
      } 
     } 

     /// <summary> 
     /// Gets or sets the minimum time betwen ApplicationLoopDoWork fires. 
     /// </summary> 
     public int SleepTime { get; set; } 

     /// <summary> 
     /// Fires while the UI is free to work. Sleeps for "SleepTime" ms. 
     /// </summary> 
     public event EventHandler ApplicationLoopDoWork 
     { 
      //Reason of using locks: 
      //http://stackoverflow.com/questions/1037811/c-thread-safe-events 
      add 
      { 
       lock (_completedEventLock) 
        _applicationLoopDoWork += value; 
      } 

      remove 
      { 
       lock (_completedEventLock) 
        _applicationLoopDoWork -= value; 
      } 
     } 

     /// <summary> 
     ///Application idle loop. 
     /// </summary> 
     /// <param name="sender"></param> 
     /// <param name="e"></param> 
     private void Application_Idle(object sender, EventArgs e) 
     { 
      //Try to update interface 
      while (Enabled && IsAppIdle()) 
      { 
       OnApplicationIdleDoWork(EventArgs.Empty); 
       //Give a break to the processor... :) 
       //8 ms -> 125 Hz 
       //10 ms -> 100 Hz 
       Thread.Sleep(SleepTime); 
      } 
     } 

     private void OnApplicationIdleDoWork(EventArgs e) 
     { 
      var handler = _applicationLoopDoWork; 
      if (handler != null) 
      { 
       handler(this, e); 
      } 
     } 

     /// <summary> 
     /// Gets if the app is idle. 
     /// </summary> 
     /// <returns></returns> 
     public static bool IsAppIdle() 
     { 
      bool isIdle = false; 
      try 
      { 
       Message msg; 
       isIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); 
      } 
      catch (Exception e) 
      { 
       //Should never get here... I hope... 
       MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message); 
      } 
      return isIdle; 
     } 

     #region Unmanaged Get PeekMessage 
     // http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx 
     [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously 
     [DllImport("User32.dll", CharSet = CharSet.Auto)] 
     public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); 

     #endregion 
    } 
}