<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Wtl之奇技淫巧篇:一、SDI如何居中顯示視圖

          Wtl的sdi應用,視圖默認鋪滿框架的客戶區。視圖通常用modeless對話框,所有的界面元素都擁擠在左上角,這明顯很丑陋。我們嘗試讓視圖居中顯示,保持原始大小,這是個很典型的問題,看似簡單,諸多細節,逐一解決后,對Wtl的理解程度,馬上能達到通透的水平。

          Wtl比較臭名昭著的一點:沒有官方資料。許多問題只能靠分析源代碼來解決。本文詳細的描述整個解決過程,以及如何快速的閱讀、分析Wtl源代碼。

      一、Google之路:
          本世紀只要有最低智商的人,首先的方式肯定是Google,我們來看看能否通過Google來找到答案。非常遺憾,我們只能找到一篇Mfc領域的文章:

           Making the SDI view smaller than the CFrameWnd:

           http://www.codeproject.com/Articles/13621/Making-the-SDI-view-smaller-than-the-CFrameWnd

           這篇文章講解了在mfc中,怎樣讓視圖"比框架窗口小",他花費了大量的精力解決閃爍的問題...事后我發現他完全走錯了路子:1、閃爍的問題并非因為他所理解的原因;2、他的解決方法,如果將視圖設置得更小一些,仍然會閃爍。使用Wtl center view sdi之類的關鍵詞,無論你怎么搜索,相信你也找不到一篇...任何一篇文章,說明如何處理。你不必再嘗試,因為我整整花費了兩個小時,專注,中途絕對沒有轉移視線。Google大師沒有找到的,你肯定也找不到。偷懶沒用的時候,你只有動用終極手段,讀代碼、理解,然后自己搞定。當然,這種終極手段遠沒有那么辛勞,只要隨時注意大而化之...

       

      二、第一步:創建時居中顯示:
          在CMainFrame類的OnCreate函數中:MESSAGE_HANDLER(WM_CREATE, OnCreate)
          m_hWndClient=m_view.Create(m_hWnd);
          這里m_view創建了視圖窗口,m_hWndClient保留了視圖的句柄。我們在這里居中顯示,試試看...
          m_view.CenterWindow(m_hWnd);//當然,這個實在整個窗體居中,我們可以自行寫函數處理在客戶區居中。為了快速實現,我們暫時忽略細節。
          你會很失望,因為運行之后,這行代碼沒有發生任何作用。原因何在?CMainFrame的基類,肯定對視圖的顯示做了處理,讓對話框鋪滿窗體,需要改變其大小。我們先用一個暴力的方法,讓基類不知道這是視圖:將m_hWndClient=m_view.Create(m_hWnd)修改為m_view.Create(m_hWnd)。再看看...果然,對話框居中顯示,很正常。基類明顯針對m_hWndClent處理,當m_hWndClient為NULL的時候,代碼也肯定做了判斷,因此程序能正常運行。

          我們當然不能用這種粗暴的方式,程序員一般總要裝得紳士一些...那么雅致一點,就意味著大量的工作,我們首先要做的,是找到基類里這部份內容。

       

      三、第二步:CMainFrame的繼承關系
        先簡單閱讀一下向導生成的CMainFrame代碼,方式很簡單,看繼承自哪些類,看消息映射,看函數的名字...除非必要,不要過多的看函數的細節。
        1、CMainFrame類的繼承關系:
            在vs2013中,鼠標指向類名,然后右鍵在快捷菜單中點擊"轉向定義",很容易查出CMainFrame的繼承關系。

      class CMainFrame : 
      public CFrameWindowImpl<CMainFrame>, 
      public CUpdateUI<CMainFrame>,
      public CMessageFilter, public CIdleHandler
      
      template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CFrameWinTraits>
      class ATL_NO_VTABLE CFrameWindowImpl : public CFrameWindowImplBase< TBase, TWinTraits >
      
      template <class TBase = ATL::CWindow, class TWinTraits = ATL::CFrameWinTraits>
      class ATL_NO_VTABLE CFrameWindowImplBase : public ATL::CWindowImplBaseT< TBase, TWinTraits >

          很清晰,CMainFrame<-CFrameWindowImpl<-CFrameWindowImplBase<-ATL::CWindowImplBaseT,到Atl一層我們暫時不用管了...Wtl的提供的框架基類,包括兩層CFrameWindowImpl<-CFrameWindowImplBase,向導創建的CMainFrame和這兩個基類,就是Wtl關于框架類的全部源代碼。

      四、第三步:找到修改視圖大小的地方

      1、查看CMainFrame和兩個基類的消息映射表:
          可以看到CMainframe的映射表最后,鏈接了CFrameWindowImpl。同樣,后者鏈接了CFrameWindowImplBase。解釋一下所謂的鏈接

          CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
          意思是,本類的消息映射表中沒有處理的,將在鏈接的CFrameWindowImpl<CMainFrame>的映射表中繼續響應。本類的消息映射表中處理了的,如果bHandled為false,表示沒有處理完成,消息仍然會在CFrameWindowImpl<CMainFrame>的映射表中繼續響應。如果為bHanded如果為true,則表示消息處理完成,映射表中就不會往下傳遞,即使鏈接了CFrameWindowImplBase,且CFrameWindowImplBase的消息映射表有響應函數,它也不會執行。
          可以直觀的設置斷點,然后單步執行,能看到在消息映射表中從上到下執行的過程。
          因此,消息映射表的順序是非常重要的,如果CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)放在最前面...這個順序會倒過來,派生類就不太好覆蓋基類的處理。
      2、分別查看三個類的消息映射表:

          那么,和創建、位置有關的,我們在CFrameWindowImpl中,看到Onsize函數,視圖的位置、大小就是在這里改變的: 

      OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled){
          if(wParam != SIZE_MINIMIZED)
          {
              T* pT = static_cast<T*>(this);
              pT->UpdateLayout();
          }
          bHandled = FALSE;
          return 1;
      }

       3、理解模板多態:

          這部分代碼比較好理解,這就是"模板多態" T* pT = static_cast<T*>(this);這里按繼承關系,T是我們傳入的CMainFrame類型,將this轉化成CMainFrame類的指針,然后執行pT->UpdateLayout();而UpdateLayout()在CFrameWindowImplBase中實現,因此:
          首先,我們的CMainFrame中沒有重新定義UpdateLayout,但由于CMainFrame歸根揭底是從CFrameWindowImplBase中繼承下來的,擁有這個函數
      所以這種情形下,執行的是CFrameWindowImplBase的UpdateLayout()
          然后,假設我們在CMainFrame里定義了完全同型的UpdateLayout函數,那么,指向CMainFrame指針的pT,當然只會執行我們定義的UpdateLayout,基類定義的函數就成為擺設。這就是所謂的模板多態...基類不知道派生類會有多少種、各自什么名稱,所以繼承的時候要將派生類名稱傳遞給基類
      這也是這種繼承方式的來由:class CMainFrame : public CFrameWindowImpl<CMainFrame>
      我們可以看看UpdateLayout的代碼,繼續動用"轉向定義"


      4、UpdateLayout代碼分析:
          多數情況下,我們在翻看代碼的時候,不必深入函數的實現細節。比如UpdateLayout,從名字上可以看到,是更新窗體的布局。函數在父類CFrameWindowImpl中調用,在祖父類CFrameWindowImplBase實現,調用是采用模板多態。由于CMainFrame和父類中都沒有覆蓋,因此調用的就是祖父類CFrameWindowImplBase中定義的函數。一般情況下這么理解基本就可以了。
          只有在特殊情況下,我們才需要詳細分析某個函數,因為我們要弄清它如何改變視圖大小、我們也要阻止它。
         下面就是祖父類中的UpdateLayout的代碼,注釋比較清晰。

      void UpdateLayout(BOOL bResizeBars = TRUE)
      {
      RECT rect = { 0 };
      GetClientRect(&rect); //獲取整個應用的客戶區rect,這只是除去窗口的標題、邊框之后,剩下的窗體工作區域
      
      // position bars and offset their dimensions
      UpdateBarsPosition(rect, bResizeBars); //該rect減去菜單、工具欄、狀態欄所占區域
      //此處得到的rect是全部客戶區,可以在這個范圍內居中顯示
      
      //如果不要鋪滿視圖,則注釋掉下面的語句,會出現狀態欄殘痕,這是UpdateBarsPosition要處理的
      // resize client window
      if(m_hWndClient != NULL) //這里將客戶區鋪滿。如果注釋掉,則大小變化的時候,狀態欄會出現異常,前面部分區域沒有消除
      ::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
      rect.right - rect.left, rect.bottom - rect.top,
      SWP_NOZORDER | SWP_NOACTIVATE);
      }
      
      void UpdateBarsPosition(RECT& rect, BOOL bResizeBars = TRUE)
      {
      // resize toolbar
      if(m_hWndToolBar != NULL && ((DWORD)::GetWindowLong(m_hWndToolBar, GWL_STYLE) & WS_VISIBLE))
      {
      if(bResizeBars != FALSE)
      {
      ::SendMessage(m_hWndToolBar, WM_SIZE, 0, 0); //相當于調用函數,消息執行完后才執行下一條,是同步代碼,可以理解為調用某個函數
      ::InvalidateRect(m_hWndToolBar, NULL, TRUE);
      }
      RECT rectTB = { 0 };
      ::GetWindowRect(m_hWndToolBar, &rectTB);
      rect.top += rectTB.bottom - rectTB.top;
      }
      
      // resize status bar
      if(m_hWndStatusBar != NULL && ((DWORD)::GetWindowLong(m_hWndStatusBar, GWL_STYLE) & WS_VISIBLE))
      {
      
      //這里沒讓原來區域失效,因為鋪滿地窗體將覆蓋它,但我們若沒有鋪滿窗體,則這里必須同樣失效。
      if(bResizeBars != FALSE)
      ::SendMessage(m_hWndStatusBar, WM_SIZE, 0, 0);
      
      
      RECT rectSB = { 0 };
      ::GetWindowRect(m_hWndStatusBar, &rectSB);
      rect.bottom -= rectSB.bottom - rectSB.top;
      }
      }

       

      五、解決方案一:在CMainFrame中覆蓋UpdateLayout
          我們將代碼拷貝到CMainFrame,注釋掉改變視圖大小的幾行語句

       

      void UpdateLayout(BOOL bResizeBars = TRUE)
      {
      RECT rect = { 0 };
      GetClientRect(&rect); //獲取整個應用的客戶區rect,這只是除去窗口的標題、邊框之后,剩下的窗體工作區域
       
      // position bars and offset their dimensions
      UpdateBarsPosition(rect, bResizeBars); //該rect減去菜單、工具欄、狀態欄所占區域
      //此處得到的rect是全部客戶區,可以在這個范圍內居中顯示
       
      //如果不要鋪滿視圖,則注釋掉下面的語句,會出現狀態欄殘痕,這是UpdateBarsPosition要處理的
      // resize client window
      //if(m_hWndClient != NULL) //這里將客戶區鋪滿。如果注釋掉,則大小變化的時候,狀態欄會出現異常,前面部分區域沒有消除
      //  ::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
      //  rect.right - rect.left, rect.bottom - rect.top,
      //  SWP_NOZORDER | SWP_NOACTIVATE);
      }

            重新運行,顯然,我們看到視圖居中顯示了。效果如下:

          但遺憾的是,當我們將應用最大化,或者改變大小的時候,狀態欄出現了殘留痕跡,視圖原來的位置也沒有擦除,仍然保留殘痕:

          

       

          當然,改變大小后,視圖保持了以前在框架中的位置,沒有居中,這是因為我們沒有在onsize中處理。我們先解決簡單的,為CMainFrame響應WM_ONSIZE消息,在消息映射表加上MESSAGE_HANDLER(WM_SIZE, OnSize),然后消息處理函數:

      LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
      {
      if (m_view.IsWindow())
      {
      m_view.CenterWindow(m_hWnd);
      }
      bHandled = False;
      return 1;
      }

          這里設置bHandled = False;這樣基類的onsize會執行UpdateLayout,這里就不用多此一舉。至于殘留痕跡問題,我們繼續看代碼。

       

      六、第四步:找出主窗體大小改變時,殘痕產生的原因
          上面說的殘痕,很明顯,整個界面都紊亂了,我們首先要找到原因。按照上面同樣的方式,我們可以看到,父類只有個OnSize函數,祖父類消息映射中則處理了兩個:擦除背景的消息,是return 1,也就是說,如果存在視圖,默認的背景擦除就不調用了,這里直接處理。

          這等于屏蔽了背景擦除或者重畫。

      LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
      {
      if(m_hWndClient != NULL) // view will paint itself instead就是說這由視圖來做
      return 1;
      
      bHandled = FALSE;
      return 0;
      }

       

          為什么要屏蔽?因為,視圖鋪滿客戶區的情形下...根本無需擦除背景。同時,前面在UpdateLayout中,狀態欄沒有發消息重畫,也是同樣的原因。所以,這里可以看出,Wtl的Frame類設計的基礎,就是視圖鋪滿客戶區。
          題外話,祖父類中還處理了一個消息OnSetFocus,即程序啟動之后,視圖即獲得焦點

      LRESULT OnSetFocus(UINT, WPARAM, LPARAM, BOOL& bHandled)
      {
      if(m_hWndClient != NULL)
      ::SetFocus(m_hWndClient);
      
      bHandled = FALSE;
      return 1;
      }

       

      七、解決方案之二:解決背景擦除問題,讓祖父類的OnEraseBackground失去作用
          我們為CMainFrame響應WM_ERASEBKGND消息

      MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
      LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
      {
      return DefWindowProc(uMsg, wParam, lParam);
      }

          這里的代碼很簡單,即對于WM_ERASEBKGND消息,調用默認的消息處理函數。注意這是一個宏,調用的也不是win32 api而是基類的函數。由于bHandle默認為true,因此該消息對Chain的父類、祖父類不再可見。這樣,重新運行,不錯,現在視圖能夠正常的居中,無論最大化、改變主窗體大小,都能正常居中,不再有殘影。到此為止,我們的居中小視圖事業...算是圓滿解決?

          No,接下來還剩下一個大的問題,閃爍!
          我們最大化主窗體,或者改變大小,都能看到明顯的閃爍。細節看出態度,態度決定一切...我們,怎么能閃爍不已呢?

       

      八、第五步:找出閃爍產生的原因
          首先,閃爍之Google大計。很痛苦,很痛苦,當百度活得滋潤的時候,你完全是在受它的折磨和蹂躪。當我終于發現bing也能找到不少國外的資料時,你發現它確實很弱智。最終,你還得使用Google,無論你用什么辦法。一時手癢,搜索了一下閃爍,嘩啦啦,鋪天蓋地,無論是Win32、mfc、Wtl、Duilib、Qt...閃爍無所不在。
          同時,解決的方法也無奇不有,雙緩沖?自行處理擦除?局部擦除?盡量避免重畫?一個悲哀的結論是:即使微軟自己的程序,閃爍也幾乎無所不在。
          我悄悄地嘗試了各種方式...對不起,沒有一種能夠消除剛才的閃爍...上面提到的哪篇Mfc居中顯示視圖的文章,用Wtl原樣實現,閃爍還是很明顯,毫無變化。雖然奇怪,后來發現,他的視圖設置得比較大,幾乎鋪滿了框架...而我的視圖很小,遮擋不住啊。
          將這位老先生的視圖改小,我那個...去!這位費勁九牛二虎之力,致力于消除閃爍,號稱比微軟普通軟件都要不閃亮的兄弟...這視圖仍然是閃爍滴,你說這事兒鬧的。

          既然各種方法沒用,我們反過來思考,多數閃爍現象,是因為窗體控件太多,在屏幕不同刷新周期顯示,各種法門大體從快速、一次顯示角度出發,或者減少擦除出發。但我們這里遇到的問題,整個框架,只有一個窗體,也就是我們的視圖,沒道理閃爍。

          我再仔細觀察了一下,閃爍的現象:最大化時,顯示視圖的同時,視圖原來的位置跳動了一下,看到原位置視圖、視圖內的文字都跳動一下然后消失,再正常的顯示居中的視圖本身。這說明什么呢?月黑風高,一道閃電從窗外怯生生的探進頭來...Onsize中居中,此時在正確的位置顯示視圖。但居中之前,很明顯在原來的位置已經顯示了視圖,只是瞬間被擦除。
          瞬間,自動擦除背景、原位置顯示再瞬間消失...這樣怎可能不閃爍?

       

      九、解決方案之三:消除閃爍
          那么...當窗體大小變化時,我們先隱藏之...OnSize先令其居中,然后顯示之...問題豈非解決?

      //Hide it here
      LRESULT OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
      {
      if (m_view.IsWindow())
      {
      m_view.ShowWindow(SW_HIDE);
      }
      bHandled = false; 
      return TRUE;
      }
      
      //center it here
      LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
      {
      if (m_view.IsWindow())
      {
      m_view.CenterWindow(m_hWnd);
      m_view.ShowWindow(SW_SHOW);
      }
      bHandled = False;
      return 1;
      }
      
      //fix position changed here
      //
      LRESULT OnWindowPosChanged(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
      {
      if (m_view.IsWindow() && !m_view.IsWindowVisible())
      {
      m_view.ShowWindow(SW_SHOW);
      }
      bHandled = False;
      return 1;
      }

       

          解決了這個問題后,很悲哀的看到了一篇:duilib啟動程序時會閃一下(一閃而過)的解決方案。這也說明,但凡Win32編程,道理都是互通的。
      這個幾乎是相同的問題...但這哥們只是處理了創建之初,沒有遇到主窗體大小改變的情形。
          所以,設計器中創建modeless對話框的時候,默認visble屬性為false,不是沒有道理的,我們手工的Show...可以避免這類啟動時的問題。最后的效果,在原來居中的情形下,主窗體變大后,正常的居中顯示視圖:

          

       

      十、留下作業?
          我們當然不是打算僅僅使視圖居中,我們還需要切換不同的視圖,這些視圖基本上是modeless對話框,這就是簡單的界面框架,讓wtl能夠實現主要流行界面。那么接下來我們還剩下哪些工作?
          1、將m_view改為指針?這意味著視圖必需自刪除
          2、切換視圖,這需要有通用的方式處理PreTranslateMessage
          3、視圖能夠在框架中指定位置,并隨大小移動?
              這需要提供相對位置、相對大小的函數,或者在CMainFrame中使用CDialogResize
          4、最最重要的是:將上述內容寫成嵌入類?
              這絕對是必要的...但大家能夠看到,我一向遵循從具體到抽象、有必要才抽象的次序。尤其是界面相關的編程中,先實現效果,再抽象就是件很簡 單的事情。嵌入類是什么?前面我們看了兩個基類的代碼,嵌入類就很明顯...使用模板多態的類。我們CMainFrame繼承自該類,并將消息映射Chain到該類...上面出現的大量重載函數、消息映射、消息處理函數,就不用再寫了。當然,要保證消息映射表中,嵌入類在 CFrameWindowImpl之前。

       

      十一、有Wtl的書籍嗎?
          我還真沒找到,即使可憐的如Mfc程序員的Wtl指南、Wtl指導教程之類,都存在版本嚴重滯后的問題,也存在知識陳舊的問題,Win98干我們甚是?
      C++ 11之后,Wtl也成為較好的UI選擇之一,模板編程的思維比較接近。
          如果真沒有...或許空閑的時候,我準備就Wtl最新的版本,結合C++ 11,結合一個具體項目的一部分...用本文的風格來完成?考慮到Wtl的冷僻程度,這或許是個注定虧本的買賣。沒有契機...很遺憾,Wtl的小世界,暫時,仍然要生活在碎片化資料、不同版本資料之中,與Google相伴。

          本文作者:畢丹軍(11084184@qq.com),轉載請略禮貌些,保留出處。

      posted @ 2014-07-21 21:31  玄歌  閱讀(4994)  評論(5)    收藏  舉報
      主站蜘蛛池模板: 伊人久久综合无码成人网| 狠狠色狠狠色综合| 国产老熟女视频一区二区| 一 级做人爱全视频在线看| 亚洲区一区二区三区亚洲| 国产精品视频亚洲二区| 97欧美精品系列一区二区| 国产精品不卡一区二区久久| 亚洲精品久综合蜜| 日本熟妇浓毛| 97免费人妻在线视频| 亚洲区一区二区三区亚洲| 夜夜添狠狠添高潮出水| 精品无码一区二区三区电影| 好吊视频在线一区二区三区| 激情亚洲一区国产精品| 一区二区三区四区精品黄| 最近中文字幕日韩有码| 99久久精品费精品国产一区二| 国产亚洲精品久久久久婷婷图片| 精品国产高清中文字幕| 国产一区二区三区不卡视频| 国产精品久久久国产盗摄| 国产精品一线天在线播放| 国产亚洲AV电影院之毛片| 女人喷液抽搐高潮视频| 精品无人区一区二区三区在线| 国产午夜一区二区在线观看| 自拍偷自拍亚洲精品播放| 国产suv精品一区二区四| 欧美成人精品手机在线| 免费人成网站免费看视频| 精品亚洲国产成人性色av| 欧美成人精品高清在线播放| 2022最新国产在线不卡a| 国产女人喷潮视频免费| 亚洲熟妇久久精品| 国产超碰无码最新上传| 国产丝袜视频一区二区三区| 亚洲禁精品一区二区三区| 无遮高潮国产免费观看|