<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 5.1 理解設備上下文 在wxWidgets中,所有的繪畫相關的動作,都是由設備上下文完成的。每一個設備上下文都是wxDC的一個派生類。從來就沒有直接在窗口上繪畫這種事情,每次在窗口上繪畫,都要先創建一個窗口繪畫設備上下文,然后在這個上下文上繪畫。其它一些設備上下文是和bitmap圖片或者打印機綁定的,你也可以設計自己的設備上下文。這樣作一個最大的好處就是,你的繪畫的代碼是可以共享的,如果你的代碼使用一個wxDC類型的參數,頂多再增加一個用于縮放的分辨率,那么這段代碼就可以被同時用于在窗口繪制和在打印機上繪制。 讓我們先來描述一下設備上下文的主要屬性。 一個設備上下文擁有一個座標體系,它的原點通常在畫布的左上角。不過這個位置可以通過調用SetDeviceOrigin函數改變,這樣的效果就相當于對隨后在這個上下文上的作的畫進行平移。這種使用方法在對wxScrolledWindow進行繪畫的時候非常常見。你還可以調用 SetAxisOrientation來改變坐標系的放向,比如說你可以讓Y軸是從下到上的放向而不是默認的從上到下的放向。 邏輯單位和設備單位是有區別的。設備單位是設備本地的單位,對于計算機屏幕來說,設備單位是一個象素,而對于一個打印機來說,設備單位是由它的分辨率決定的,這個分辨率可以用GetSize(用來取得設備單位的一頁的大小)或者GetSizeMM(用來取得以毫米為單位的一頁的大小)來獲得。 設備上下文的映射模式定義了邏輯單位到設備單位的轉換標準。要注意某些設備上下文(典型的例子比如:wxPostScriptDC)只支持wxMM_TEXT映射模式。下表列出了目前支持的映射模式: | wxMM_TWIPS | 每個邏輯單位為1/20點, 或者1/1440英寸. | |:--- |:--- | | wxMM_POINTS | 每個邏輯單位為1點, 或1/72英寸. | | wxMM_METRIC | 每個邏輯單位為1毫米. | | wxMM_LOMETRIC | 每個邏輯單位為1/10毫米. | | wxMM_TEXT | 每個邏輯單位為1象素.這是默認的映射模式. | 你可以通過SetUserScale函數給邏輯單位指定一個縮放比例,這個比例乘以映射模式中指定的單位,可以得到實際邏輯單位和設備單位之間的關系,比如在wxMM_TEXT映射模式下,如果用戶縮放比例為(1.0,1.0),那么邏輯單位和設備單位就是一樣的,這也是設備上下文中映射模式和用戶縮放比例的缺省值。 設備上下文還可以通過SetClippingRegion函數指定一個區域,這個區域以外的部分將不被顯示,可以使用 DestroyClippingRegion函數清除設備上下文當前指定的區域。舉個例子,為了將一個字符串顯示的范圍限制在某個矩形區域以內,你可以先指定這個矩形區域為這個設備上下文的當前區域,然后調用函數在這個設備上下文上繪制這個字符串,即使這個字符串很長,可能會超出這個矩形區域,超出的部分也將被截掉不被顯示,然后再調用函數清除這個區域就可以了。 和現實世界一樣,為了繪畫,你需要先選擇繪畫的工具,如果你要畫線,那么你需要選擇畫筆,要填充一個區域,你需要選擇畫刷,而當前字體,文本前景色和背景色等,則會決定你畫布上的文本怎樣顯示。晚些時候我們會詳細討論這些。我們先來看看目前都支持那些設備上下文: 可用的設備上下文 下面列出了你可以使用的設備上下文: * wxClientDC. 用來在一個窗口的客戶區繪畫。 * wxBufferedDC. 用來代替wxClientDC來進行雙緩沖區繪畫。 * wxWindowDC. 用來在窗口的客戶區和非客戶區(比如標題欄)繪畫.這個設備上下文極少使用而且也不是每個平臺都支持。 * wxPaintDC. 僅用在重繪事件的處理函數中,用來在窗口的客戶區繪畫。 * wxBufferedPaintDC. 和wxPaintDC類似,不過采用雙緩沖區進行繪畫。 * wxScreenDC. 用來直接在屏幕上繪畫。 * wxMemoryDC. 用來直接在圖片上繪畫。 * wxMetafileDC. 用來創建一個圖元文件(只支持Windows和Mac OS X). * wxPrinterDC. 用來在打印機上繪畫。 * wxPostScriptDC. 用來在PostScript文件上或者在支持PostScript的打印機上繪畫。 接下來我們會描述一下怎樣來創建和使用這些設備上下文。打印機設備上下文將會在本章稍后的“使用打印機的架構”小節中進行更詳細的討論。 使用wxClientDC在窗口客戶區進行繪畫 wxClientDC用來在非重繪事件處理函數中對窗口的客戶區進行繪制。例如,如果你想作一個信手涂鴉的程序,你可以在你的鼠標事件處理函數中創建一個wxClientDC。這個設備上下文也可以用于擦除背景事件處理函數。 下面的例子演示了怎樣使用鼠標在窗口上隨便亂畫: ``` BEGIN_EVENT_TABLE(MyWindow, wxWindow) EVT_MOTION(MyWindow::OnMotion) END_EVENT_TABLE() void MyWindow::OnMotion(wxMouseEvent& event) { if (event.Dragging()) { wxClientDC dc(this); wxPen pen(*wxRED, 1); // red pen of width 1 dc.SetPen(pen); dc.DrawPoint(event.GetPosition()); dc.SetPen(wxNullPen); } } ``` 在第19章,“使用文檔和視圖”中,實現了一個更具有可用性的涂鴉工具,它使用線段代替了點,并且實現了重做和撤消的操作。而且還存儲了所有的線段,以便在windows需要重繪的時候重繪所有的線段。而使用上面的代碼,你所作的畫在下次windows重繪的時候將會消失。當然你還可以使用CaptureMouse和ReleaseMouse函數,以便在鼠標按下的時候,直接把鼠標限制在你的繪畫窗口的客戶區范圍內。 一種替代wxClientDC的方法是使用wxBufferedDC,后者將你的所有繪畫的結果保存在內存中,然后當自己被釋放的時候一次性把所有的內容傳輸到窗口上。這樣作的結果是使得整個繪畫過程更平滑,如果你不希望用戶看到繪畫的結果一點一點的更新,你可以使用這種方法,使用 wxBufferedDC和使用wxClientDC的代碼是完全一樣的,另外為了提高效率,你可以在wxBufferedDC的構造函數中傳遞一個已經創建好的bitmap對象,以避免在每次創建wxBufferedDC的時候創建一個新的圖片。 擦除窗口背景 窗口類通常會收到兩種和繪畫相關的事件:wxPaintEvent事件用來繪制窗口客戶區的主要圖形,而wxEraseEvent事件則用來通知應用程序擦除背景。如果你只攔截并處理了wxPaintEvent事件,則默認的wxEraseEvent事件處理函數會用最近一次的 wxWindow::SetBackgroundColour函數調用設置的顏色或者別的合適的顏色清除整個背景。 這也許看上去有些混亂,但是這種把背景和前景分開的方法可以更好的支持那些使用這種架構的平臺(比如windows)上的控件。舉個例子,如果你希望在一個窗口上使用某種紋路的背景,如果你在OnPaint函數中是平鋪紋理貼圖,你可能會看到一些閃爍,因為在繪畫之前,系統進行了清除背景的動作,在這種背景和前景分離的架構下,你可以攔截wxEraseEvent事件,然后在其處理函數中什么事情都不做,或者你干脆在擦除背景事件處理函數中平鋪你的背景圖,而在前景繪制事件的處理函數中繪制前景(當然,這樣也會帶來一些雙緩沖繪畫方面的問題,我們接下來會談到). 在某些平臺上。僅僅是攔截wxEraseEvent事件仍然不足以阻止所有的系統默認的重繪動作,讓你的窗口的背景不只是一個純色背景的最安全的方法是調用wxWindow::SetBackgroundStyle然后傳遞wxBG_STYLE_CUSTOM參數。這將告訴 wxWidgets把所有背景重繪的動作留給應用程序自己處理。 如果你真的決定實現自己的背景擦除事件處理函數,你可以先調用wxEraseEvent::GetDC來嘗試返回一個已經創建的設備上下文,如果返回的值為空,再創建一個wxClientDC設備上下文。這將允許wxWidgets不在擦除背景事件中固定創建一個設備上下文,象前面例子中提到的那樣,擦除背景事件處理函數中可能根本不會用到設備上下文,因此在事件中創建一個設備上下文可能是不必要的。下面的代碼演示了怎樣在擦除背景事件使用平鋪圖片的方法繪制窗口背景: ``` BEGIN_EVENT_TABLE(MyWindow, wxWindow) EVT_ERASE_BACKGROUND(MyWindow::OnErase) END_EVENT_TABLE() void MyWindow::OnErase(wxEraseEvent& event) { wxClientDC* clientDC = NULL; if (!event.GetDC()) clientDC = new wxClientDC(this); wxDC* dc = clientDC ? clientDC : event.GetDC() ; wxSize sz = GetClientSize(); wxEffects effects; effects.TileBitmap(wxRect(0, 0, sz.x, sz.y), *dc, m_bitmap); if (clientDC) delete clientDC; } ``` 和重繪窗口事件一樣,wxEraseEvent::GetDC返回的設備上下文已經設置了區域,使得只有需要重繪的部分才會被重繪。 使用wxPaintDC在窗口上繪畫 如果你定義了一個窗口重畫事件處理函數,則必須在這個處理函數中產生一個wxPaintDC設備上下文(即使你根本不使用它),并且使用它來進行你需要的繪畫動作。產生這個對象將告訴wxWidgets的窗口體系這個窗口的需要重畫的區域已經被重畫了,這樣窗口系統就不會重復的發送重畫消息給這個窗口了。在重畫事件中,你還可以調用wxWindow::GetUpdateRegion函數來獲得需要重畫的區域,或者使用wxWindow:: IsExposed函數來判斷某個點或者某個矩形區域是否需要重畫,然后優化代碼使得僅在這個范圍內的內容被重畫,雖然在重畫事件中創建的 wxPaintDC設備上下文會自動將自己限制在需要重畫的區域內,不過你自己知道需要重畫的區域的話,可以對代碼進行相應的優化。 重畫事件是由于用戶和窗口系統的交互造成的,但是它也可以通過調用wxWindow::Refresh和wxWindow:: RefreshRect函數手動產生。如果你準確的知道窗口的哪個部分需要重畫,你可以指定只重畫那一部分區域以便盡可能的減少閃爍。這樣作的一個問題是,并不能保證窗口在調用Refresh函數以后會馬上重畫。如果你真的需要立刻調用你的重畫事件處理函數,比如說你在進行一個很耗時的計算,需要即時顯示一些進度,你可以在調用Refresh或者RefreshRect以后調用wxWindow::Update函數。 下面的代碼演示了如何在窗口正中位置畫一個黑邊紅色的矩形區域,并且會判斷這個區域是否位于需要更新的區域范圍內以便決定是否需要重畫。 ``` BEGIN_EVENT_TABLE(MyWindow, wxWindow) EVT_PAINT(MyWindow::OnPaint) END_EVENT_TABLE() void MyWindow::OnPaint(wxPaintEvent& event) { wxPaintDC dc(this); dc.SetPen(*wxBLACK_PEN); dc.SetBrush(*wxRED_BRUSH); // 獲取窗口大小 wxSize sz = GetClientSize(); // 要繪制的矩形的大小 wxCoord w = 100, h = 50; // 將我們的矩形設置在窗口正中, // 但是不為負數的位置 int x = wxMax(0, (sz.xw)/2); int y = wxMax(0, (sz.yh)/2); wxRect rectToDraw(x, y, w, h); // 只有在需要的時候才重畫以便提高效率 if (IsExposed(rectToDraw)) DrawRectangle(rectToDraw); } ``` 注意在默認情況下,當窗口大小改變時,只有那些需要重畫的地方才會被更新,指定wxFULL_REPAINT_ON_RESIZE窗口類型可以覆蓋這種默認情況以使得整個窗口都被刷新。在我們上面的例子中,就需要指定這種情況,因為我們矩形的位置是根據窗口大小計算出來的,如果窗口變大而我們只更新需要更新的部位,則可能在原來的窗口中留下半個矩形或者在屏幕上出現兩個矩形,這和我們的初衷是不一致的。 wxBufferedPaintDC是wxPaintDC的雙緩沖區版本。只需要簡單的將重繪事件處理函數中的wxPaintDC換成 wxBufferedPaintDC就可以了,它會首先將所有的圖片畫在一個內存位圖上,然后在自己被釋放的時候一次性將其畫在窗口上以減小閃爍。 正象我們前面提到的那樣,另外一個減少閃爍的方法,是把背景和前景統一在窗口重畫事件處理函數中,而不是將它們分開處理,配合 wxBufferedPaintDC,那么所有的繪畫動作在完成之前都是在內存中進行的,這樣在窗口被重繪之前你將看不到窗口背景被更新。你需要增加一個空的背景擦除事件處理函數,并且使用SetBackgroundStyle函數設置背景類型為wxBG_STYLE_CUSTOM以便告訴某些系統不要自動擦除背景。在wxScrolledWindow中你還有注意需要對繪畫設備上下文的原點進行平移,并據此重新計算你自己的圖片位置。(譯者注:在 wxScrolledWindow窗口中的wxPaintDC的原點是當前窗口滾動位置下的原點,這通常不是我們所需要的,因為我們繪畫通常要基于整個滾動窗口本身的原點,調用PrepareDC函數可以將其設置成滾動窗口本身的原點,在繪畫的時候可以通過CalcUnscrolledPosition函數將當前客戶區中的某個點轉換成相對于整個滾動窗口區的座標,如下面的代碼演示的那樣)下面的代碼演示了怎樣在一個繼承自 wxScrolledWindow的窗口類中進行繪畫并且盡可能的避免出現閃爍。 ``` #include "wx/dcbuffer.h" BEGIN_EVENT_TABLE(MyCustomCtrl, wxScrolledWindow) EVT_PAINT(MyCustomCtrl::OnPaint) EVT_ERASE_BACKGROUND(MyCustomCtrl::OnEraseBackground) END_EVENT_TABLE() //重畫事件處理函數 void MyCustomCtrl::OnPaint(wxPaintEvent& event) { wxBufferedPaintDC dc(this); // 平移設備座標以便我們不需要關心當前滾動窗口的位置 PrepareDC(dc); // 在重畫繪制函數中繪制背景 PaintBackground(dc); // 然后繪制前景 ... } /// 繪制背景 void MyCustomCtrl::PaintBackground(wxDC& dc) { wxColour backgroundColour = GetBackgroundColour(); if (!backgroundColour.Ok()) backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE); dc.SetBrush(wxBrush(backgroundColour)); dc.SetPen(wxPen(backgroundColour, 1)); wxRect windowRect(wxPoint(0, 0), GetClientSize()); //我們需要平移當前客戶區矩形的座標以便將其轉換成相對于整個滾動窗口而不是當前窗口的座標 //因為在前面我們已經對設備上下文進行了PrepareDC的動作。 CalcUnscrolledPosition(windowRect.x, windowRect.y, & windowRect.x, & windowRect.y); dc.DrawRectangle(windowRect); } // 空函數 只為了防止閃爍 void MyCustomCtrl::OnEraseBackground(wxEraseEvent& event) { } ``` 為了提高性能,當你使用wxBufferedPaintDC時,你可以維護一個足夠大的(比如屏幕大小的)位圖,然后將其傳遞給 wxBufferedPaintDC的構造函數作為第二個參數,這可是避免每次使用wxBufferedPaintDC的時候創建和釋放一個位圖。 wxBufferedPaintDC通常會從其緩沖區中拷貝整個客戶區(用戶可見部分)大小,在滾動窗口中,其內部創建的設備上下文并不會隨著PrepareDC的調用平移其座標系。不過你可以通過在wxBufferedPaintDC的構造函數中指定 wxBUFFER_VIRTUAL_AREA(默認為wxBUFFER_CLIENT_AREA)參數來避免這一問題。不過這種情況下,你需要提供一個和整個滾動窗口的虛擬大小一樣的緩沖區,而這通常效率是很低的,應該盡量避免。另外一個需要注意的是對于設置為wxBUFFER_CLIENT_AREA的 wxBufferedPaintDC到目前為止還不支持縮放(SetUserScale)。 你可以在隨書光盤的examples/chap12/thumbnail例子的wxThumbnailCtrl控件中,找到使用wxBufferedPaintDC的完整的例子。 使用wxMemoryDC在位圖上繪圖 內存設備上下文是和一個位圖綁定的設備上下文,在這個設備上下文上的所有繪畫都將畫在那個位圖上面。使用的方法是先使用默認的構造函數創建一個內存設備上下文,然后使用SelectObject函數將其和一個位圖綁定,在完成所有的繪畫以后再調用SelectObject函數參數為 wxNullBitmap來移除綁定的位圖,代碼如下所示: ``` wxBitmap CreateRedOutlineBitmap() { wxMemoryDC memDC; wxBitmap bitmap(200, 200); memDC.SelectObject(bitmap); memDC.SetBackground(*wxWHITE_BRUSH); memDC.Clear(); memDC.SetPen(*wxRED_PEN); memDC.SetBrush(*wxTRANSPARENT_BRUSH); memDC.DrawRectangle(wxRect(10, 10, 100, 100)); memDC.SelectObject(wxNullBitmap); return bitmap; } ``` 你也可以使用Blit函數將內存設備上下文中的某一個區域拷貝到別的設備上下文上,在本章稍后的地方我們會對此進行介紹。 使用wxMetafileDC創建圖元文件 wxMetafileDC適用于Windows和Mac OS X,它在這兩個平臺上分別提供了繪制Windows圖元文件和Mac PICT的畫布。允許在wxMetafile對象上繪畫,這個對象保留了一組繪畫動作記錄,可以被其它應用程序使用或者通過wxMetafile:: Play函數將其繪制在別的設備上下文上。 使用wxScreenDC訪問屏幕 使用wxScreenDC可以在整個屏幕的任何位置繪畫。通常這在給拖放操作提供可見響應的時候比較有用(比如拖放分割窗口的分割條的時候)。處于性能方面的考慮。你可以將其操作的屏幕區域限制在一個矩形區域(通常是程序窗口所在的區域)內。當然,除了在屏幕上繪畫,我們還可以把繪畫從屏幕上拷貝到其它設備上下文中,以便實現屏幕捕獲。因為無法限制別的應用程序的行為,所以wxScreenDC類通常在當前應用程序的窗口內工作的最好。 下面是將屏幕捕獲到位圖文件的例子: ``` wxBitmap GetScreenShot() { wxSize screenSize = wxGetDisplaySize(); wxBitmap bitmap(screenSize.x, screenSize.y); wxScreenDC dc; wxMemoryDC memDC; memDC.SelectObject(bitmap); memDC.Blit(0, 0, screenSize.x, screenSize.y, & dc, 0, 0); memDC.SelectObject(wxNullBitmap); return bitmap; } ``` 使用wxPrinterDC和wxPostScriptDC實現打印 wxPrinterDC用來實現打印機接口。在windows和Mac平臺上,這個接口實現到標準打印接口的映射。在其它類Unix系統上,沒有標準的打印接口,因此需要使用wxPostScriptDC代替(除非打開了Gnome打印支持,參考接下來的小節,“在類Unix系統上使用 GTK+進行打印”)。 可以通過多種途徑創建wxPrinterDC對象,你還可以傳遞設置了紙張類型,橫向或者縱向,打印份數等參數的 wxPrintData對象給它。一個簡單的創建wxPrinterDC設備上下文的方法是顯示一個wxPrintDialog對話框,在用戶選擇各種參數以后,使用wxPrintDialog::GetPrintDC的方法獲取一個對應的wxPrinterDC對象。作為更高級的用法,你可以從 wxPrintout定義一個自己的派生類,以便更精確定義打印以及打印預覽的行為,然后把它的一個實例傳遞給wxPrinter對象(在后面小節中還會詳細介紹)。 如果你要打印的內容主要是文本,你可以考慮使用wxHtmlEasyPrinting類,以便忽略wxPrinterDC和 wxPrintout排版的細節:你只需要按照wxWidgets'實現的那些HTML的語法編寫HTML文件,然后創建一個 wxHtmlEasyPrinting對象用來實現打印和預覽,這通常可以節省你幾天到幾周的時候來對那些文本,表格和圖片進行排版。詳情請參考第12 章,“高級窗口類”。 wxPostScriptDC用來打印到PostScript文件。雖然這種方式主要應用在類Unix系統上,不過在別的系統上你一樣可以使用它。用這種方式打印需要先產生PostScript文件,而且還要保證你擁有一個支持打印PostScript文件的打印機。 你可以通過傳遞wxPrintData參數,或者傳遞一個文件名,一個bool值以確定是否顯示一個打印設置對話框和一個父窗口來創建一個wxPostScriptDC,如下所示: ``` #include "wx/dcps.h" wxPostScriptDC dc(wxT("output.ps"), true, wxGetApp().GetTopWindow()); if (dc.Ok()) { // 告訴它在哪里找到AFM字體文件。 dc.GetPrintData().SetFontMetricPath(wxGetApp().GetFontPath()); // 設置分辨率(每英寸多少個點,默認720) dc.SetResolution(1440); // 開始繪畫 ... } ``` wxPostScriptDC的一個特殊的地方在于你不能直接使用GetTextExtent來獲取文本大小的信息。你必須先用 wxPrintData::SetFontMetricPath指定AFM(Adobe Font Metric)文件的路徑,就象上面例子中的那樣。你可以從下面的路徑下載GhostScript AFM文件。 ftp://biolpc22.york.ac.uk/pub/support/gs_afm.tar.gz
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看