我想像 Windows 一樣創建控制按鈕(最小化、最大化和關閉)。
最終目標是創建類似于 Microsoft Word 標題欄的內容。

我知道如何創建 wxButton,也知道如何為其設定圖示。但是我不知道如何使用本機作業系統圖示或主題。
wxButton* closeButton = new wxButton(this, wxID_ANY, "x"); // how to tell that be like OS close button!
在 WinAPI 中,有一個名為
// wxWidgets "Hello World" Program
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/renderer.h>
#include <wx/artprov.h>
class MyApp: public wxApp
{
public:
virtual bool OnInit();
};
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
};
wxIMPLEMENT_APP( MyApp );
bool MyApp::OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show( true );
return true;
}
wxBitmap getButtonBitmap( wxWindow* win, wxTitleBarButton type, const wxColour& bg, int flags = 0 )
{
const wxSize sizeBmp = wxArtProvider::GetSizeHint( wxART_BUTTON );
wxBitmap bmp( sizeBmp );
wxMemoryDC dc( bmp );
dc.SetBackground( bg );
dc.Clear();
wxRendererNative::Get().DrawTitleBarBitmap( win, dc, sizeBmp, type, flags );
return bmp;
}
MyFrame::MyFrame()
: wxFrame( NULL, wxID_ANY, "Hello World" )
{
wxWindow* win = this;
wxColour color = win->GetBackgroundColour();
// minimize button
wxBitmapButton* minimizeButton = new wxBitmapButton( win, wxID_ANY,
getButtonBitmap( win, wxTITLEBAR_BUTTON_ICONIZE, color ),
wxPoint( 0, 0 ), wxDefaultSize, wxBORDER_NONE );
minimizeButton->SetBitmapPressed( getButtonBitmap( win, wxTITLEBAR_BUTTON_ICONIZE, color, wxCONTROL_PRESSED ) );
minimizeButton->SetBitmapCurrent( getButtonBitmap( win, wxTITLEBAR_BUTTON_ICONIZE, color, wxCONTROL_CURRENT ) );
// maximize button
wxBitmapButton* maximizeButton = new wxBitmapButton( win, wxID_ANY,
getButtonBitmap( win, wxTITLEBAR_BUTTON_MAXIMIZE, color ),
wxPoint( 30, 0 ), wxDefaultSize, wxBORDER_NONE );
maximizeButton->SetBitmapPressed( getButtonBitmap( win, wxTITLEBAR_BUTTON_MAXIMIZE, color, wxCONTROL_PRESSED ) );
maximizeButton->SetBitmapCurrent( getButtonBitmap( win, wxTITLEBAR_BUTTON_MAXIMIZE, color, wxCONTROL_CURRENT ) );
// close Button
wxBitmapButton* closeButton = new wxBitmapButton( win, wxID_ANY,
getButtonBitmap( win, wxTITLEBAR_BUTTON_CLOSE, color ),
wxPoint( 60, 0 ), wxDefaultSize, wxBORDER_NONE );
closeButton->SetBitmapPressed( getButtonBitmap( win, wxTITLEBAR_BUTTON_CLOSE, color, wxCONTROL_PRESSED ) );
closeButton->SetBitmapCurrent( getButtonBitmap( win, wxTITLEBAR_BUTTON_CLOSE, color, wxCONTROL_CURRENT ) );
}
uj5u.com熱心網友回復:
繪制類似于標題欄上的按鈕的程序比簡單地使用該DrawThemeBackground函式要復雜一些。這是一個演示,部分展示了如何使用 wxWidgets 執行此操作:

#include "wx/wx.h"
#include <wx/dcclient.h>
#include <wx/mstream.h>
#include <wx/dcmemory.h>
#include <wx/rawbmp.h>
#include <wx/msw/wrapwin.h>
#include <uxtheme.h>
#include <Vssym32.h>
#include <map>
// Helper data types
struct BGInfo
{
wxRect BgRect;
wxRect SizingMargins;
wxRect ContentMargins;
int TotalStates;
};
struct ButtonInfo
{
wxRect ButtonRect;
int TotalStates;
};
enum class DPI
{
dpi96 = 0,
dpi120,
dpi144,
dpi196
};
enum class Button
{
Close = 0,
Min,
Max,
Restore,
Help
};
// Helper functions
void MarginsToRect(const MARGINS& m, wxRect& r)
{
r.SetLeft(m.cxLeftWidth);
r.SetRight(m.cxRightWidth);
r.SetTop(m.cyTopHeight);
r.SetBottom(m.cyBottomHeight);
}
void RectTowxRect(const RECT & r, wxRect& r2)
{
r2.SetLeft(r.left);
r2.SetTop(r.top);
r2.SetRight(r.right-1);
r2.SetBottom(r.bottom-1);
}
wxBitmap ExtractAtlas(const wxBitmap& atlas, int total, int loc)
{
int bgheight = atlas.GetHeight();
int individualHeight = bgheight/total;
int bgWidth = atlas.GetWidth();
int atlasOffset = individualHeight*loc;
wxRect bgRect = wxRect(wxPoint(0,atlasOffset),
wxSize(bgWidth,individualHeight));
return atlas.GetSubBitmap(bgRect);
}
void TileBitmap(const wxBitmap& bmp, wxDC& dc, const wxRect& r)
{
dc.SetClippingRegion(r);
for ( int y = 0 ; y < r.GetHeight() ; y = bmp.GetHeight() )
{
for ( int x = 0 ; x < r.GetWidth() ; x = bmp.GetWidth() )
{
dc.DrawBitmap(bmp, r.GetLeft() x, r.GetTop() y, true);
}
}
dc.DestroyClippingRegion();
}
void TileTo(const wxBitmap& in, const wxRect& margins, wxBitmap& out, int w, int h)
{
// Theoretically we're supposed to split the bitmap into 9 pieces based on
// the sizing margins and leave the 8 outside pieces as unchanged as
// possible and the fill the remainder with the center piece. However doing
// that doesn't look actual control buttons. So I'm going to just tile
// the center bitmap to fill the whole space.
int ml = margins.GetLeft();
int mr = margins.GetRight();
int mt = margins.GetTop();
int mb = margins.GetBottom();
int bw = in.GetWidth();
int bh = in.GetHeight();
wxBitmap center = in.GetSubBitmap(wxRect(wxPoint( ml,mt),wxSize(bw-ml-mr,bh-mb-mt)));
// Create and initially transparent bitmap.
unsigned char* data = reinterpret_cast<unsigned char*>(malloc(3*w*h));
unsigned char* alpha = reinterpret_cast<unsigned char*>(malloc(w*h));
memset(alpha, 0, w*h);
wxImage im(w, h, data, alpha);
wxBitmap bmp(im);
wxMemoryDC dc(bmp);
TileBitmap(center, dc, wxRect(wxPoint(0,0),wxSize(w,h)));
dc.SelectObject(wxNullBitmap);
out = bmp;
}
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
void OnPaintImagePanel(wxPaintEvent&);
void OnListSelection(wxCommandEvent&);
void BuildItemToDraw();
void LoadThemeData();
wxListBox* m_typeBox, *m_dpiBox, *m_stateBox;
wxPanel* m_imagePanel;
wxBitmap m_fullAtlas;
wxBitmap m_itemToDraw;
BGInfo m_closeInfo;
BGInfo m_otherInfo;
std::map<std::pair<DPI,Button>,ButtonInfo> m_themeMap;
};
MyFrame::MyFrame():wxFrame(NULL, wxID_ANY, "Windows Control Button Demo", wxDefaultPosition,
wxSize(400, 300))
{
// Start all the image handlers. Only the PNG handler is actually needed.
::wxInitAllImageHandlers();
// Build the UI.
wxPanel* bg = new wxPanel(this, wxID_ANY);
wxStaticText* typeText = new wxStaticText(bg,wxID_ANY,"Type:");
m_typeBox = new wxListBox(bg,wxID_ANY);
wxStaticText* dpiText = new wxStaticText(bg,wxID_ANY,"dpi:");
m_dpiBox = new wxListBox(bg,wxID_ANY);
wxStaticText* stateText = new wxStaticText(bg,wxID_ANY,"State:");
m_stateBox = new wxListBox(bg,wxID_ANY);
m_imagePanel = new wxPanel(bg,wxID_ANY);
wxBoxSizer* mainSzr = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* boxSzr = new wxBoxSizer(wxHORIZONTAL);
boxSzr->Add(typeText, wxSizerFlags().Border(wxALL));
boxSzr->Add(m_typeBox, wxSizerFlags().Border(wxTOP|wxRIGHT|wxBOTTOM));
boxSzr->Add(dpiText, wxSizerFlags().Border(wxALL));
boxSzr->Add(m_dpiBox, wxSizerFlags().Border(wxTOP|wxRIGHT|wxBOTTOM));
boxSzr->Add(stateText, wxSizerFlags().Border(wxALL));
boxSzr->Add(m_stateBox, wxSizerFlags().Border(wxTOP|wxRIGHT|wxBOTTOM));
mainSzr->Add(boxSzr,wxSizerFlags());
mainSzr->Add(m_imagePanel,wxSizerFlags(1).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));
bg->SetSizer(mainSzr);
// Set the needed event handlers for the controls.
m_imagePanel->Bind(wxEVT_PAINT, &MyFrame::OnPaintImagePanel, this);
m_typeBox->Bind(wxEVT_LISTBOX, &MyFrame::OnListSelection, this);
m_dpiBox->Bind(wxEVT_LISTBOX, &MyFrame::OnListSelection, this);
m_stateBox->Bind(wxEVT_LISTBOX, &MyFrame::OnListSelection, this);
// Concigure the controls.
m_typeBox->Append("Close");
m_typeBox->Append("Help");
m_typeBox->Append("Max");
m_typeBox->Append("Min");
m_typeBox->Append("Restore");
m_dpiBox->Append("96");
m_dpiBox->Append("120");
m_dpiBox->Append("144");
m_dpiBox->Append("192");
m_stateBox->Append("Normal");
m_stateBox->Append("Hot");
m_stateBox->Append("Pressed");
m_stateBox->Append("Inactive");
m_typeBox->Select(0);
m_dpiBox->Select(0);
m_stateBox->Select(0);
// Load the theme data and finish setting up.
LoadThemeData();
BuildItemToDraw();
}
void MyFrame::LoadThemeData()
{
HINSTANCE handle = LoadLibraryEx(L"C:\\Windows\\Resources\\Themes\\aero\\aero.msstyles",
0, LOAD_LIBRARY_AS_DATAFILE);
if ( handle == NULL )
{
return;
}
HTHEME theme = OpenThemeData(reinterpret_cast<HWND>(this->GetHandle()),L"DWMWindow");
VOID* PBuf = NULL;
DWORD BufSize = 0;
GetThemeStream(theme, 0,0, TMT_DISKSTREAM, &PBuf, &BufSize, handle);
wxMemoryInputStream mis(PBuf,static_cast<int>(BufSize));
wxImage im(mis, wxBITMAP_TYPE_PNG);
if ( !im.IsOk() )
{
return;
}
wxBitmap b2(im);
m_fullAtlas = wxBitmap(im);;
MARGINS m;
RECT r;
int BUTTONACTIVECAPTION = 3;
int BUTTONACTIVECLOSE = 7;
int BUTTONCLOSEGLYPH96 = 11;
int BUTTONRESTOREGLYPH192 = 30;
// Store some of the theme info for the parts BUTTONACTIVECAPTION
// and BUTTONACTIVECLOSE.
GetThemeRect(theme, BUTTONACTIVECAPTION, 0, TMT_ATLASRECT, &r);
RectTowxRect(r,m_otherInfo.BgRect);
GetThemeMargins(theme,NULL, BUTTONACTIVECAPTION,0, TMT_CONTENTMARGINS,NULL, &m);
MarginsToRect(m,m_otherInfo.ContentMargins);
GetThemeMargins(theme,NULL, BUTTONACTIVECAPTION,0, TMT_SIZINGMARGINS,NULL, &m);
MarginsToRect(m,m_otherInfo.SizingMargins);
GetThemeInt(theme, BUTTONACTIVECAPTION, 0, TMT_IMAGECOUNT, &(m_otherInfo.TotalStates));
GetThemeRect(theme, BUTTONACTIVECLOSE, 0, TMT_ATLASRECT, &r);
RectTowxRect(r,m_closeInfo.BgRect);
GetThemeMargins(theme,NULL, BUTTONACTIVECLOSE,0, TMT_CONTENTMARGINS,NULL, &m);
MarginsToRect(m,m_closeInfo.ContentMargins);
GetThemeMargins(theme,NULL, BUTTONACTIVECLOSE,0, TMT_SIZINGMARGINS,NULL, &m);
MarginsToRect(m,m_closeInfo.SizingMargins);
GetThemeInt(theme, BUTTONACTIVECLOSE, 0, TMT_IMAGECOUNT, &(m_closeInfo.TotalStates));
// Since the part numbers for BUTTONCLOSEGLYPH96..BUTTONRESTOREGLYPH192
// are all sequential and the dpis all run from 96 to 192 in the same
// order, we can use a for loop to store
for ( int i = BUTTONCLOSEGLYPH96 ; i <= BUTTONRESTOREGLYPH192 ; i )
{
int j = i-BUTTONCLOSEGLYPH96;
Button b = static_cast<Button>(j/4);
DPI dpi = static_cast<DPI>(j%4);
std::pair<DPI,Button> item;
ButtonInfo info;
item = std::make_pair(dpi,b);
GetThemeRect(theme, i, 0, TMT_ATLASRECT, &r);
RectTowxRect(r,info.ButtonRect);
GetThemeInt(theme, i, 0, TMT_IMAGECOUNT, &(info.TotalStates));
m_themeMap.insert(std::make_pair(item,info));
}
CloseThemeData(theme);
FreeLibrary(handle);
}
void MyFrame::OnPaintImagePanel(wxPaintEvent&)
{
wxPaintDC dc(m_imagePanel);
dc.Clear();
if ( m_itemToDraw.IsOk() )
{
dc.DrawBitmap(m_itemToDraw,0,0,true);
}
}
void MyFrame::OnListSelection(wxCommandEvent&)
{
BuildItemToDraw();
}
void MyFrame::BuildItemToDraw()
{
BGInfo bginfo;
Button b = static_cast<Button>(m_typeBox->GetSelection());
DPI dpi = static_cast<DPI>(m_dpiBox->GetSelection());
int state = m_stateBox->GetSelection();
if ( b == Button::Close )
{
bginfo = m_closeInfo;
}
else
{
bginfo = m_otherInfo;
}
wxBitmap bgAtlas = m_fullAtlas.GetSubBitmap(bginfo.BgRect);
int totalbgs = bginfo.TotalStates;
wxBitmap bg = ExtractAtlas(bgAtlas, totalbgs, state);
std::pair<DPI,Button> item = std::make_pair(dpi,b);
auto it = m_themeMap.find(item);
if ( it != m_themeMap.end() )
{
ButtonInfo info = it->second;
wxBitmap itemAtlas = m_fullAtlas.GetSubBitmap(info.ButtonRect);
wxBitmap item = ExtractAtlas(itemAtlas, info.TotalStates, state);
wxRect contentmargins = bginfo.ContentMargins;
wxRect Sizingmargins = bginfo.SizingMargins;
int width = item.GetWidth() contentmargins.GetLeft() contentmargins.GetRight();
int height = item.GetHeight() contentmargins.GetTop() contentmargins.GetBottom();
if ( bg.GetWidth() > width )
{
width = bg.GetWidth();
}
if ( bg.GetHeight() > height )
{
height = bg.GetHeight();
}
wxBitmap bmp(width,height,32);
TileTo(bg,Sizingmargins, bmp, width, height);
wxMemoryDC dc(bmp);
int leftOffset = (width-item.GetWidth())/2;
int topOffset = (height - item.GetHeight())/2;
dc.DrawBitmap(item,leftOffset,topOffset, true);
dc.SelectObject(wxNullBitmap);
m_itemToDraw = bmp;
}
m_imagePanel->Refresh();
m_imagePanel->Update();
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
這只是部分答案,因為,
- 這依賴于數字零件編號,就像
BUTTONACTIVECAPTION我只是輸入代碼一樣。這些數字最終是從檔案 Aero.msstyles 中提取的,理論上如果 Microsoft 更改該檔案,代碼中的數字可能是錯誤的。完整的答案會查看該檔案并從中提取正確的數字,以便始終確保它使用的是正確的數字。但是這樣做超出了這個答案的范圍。 - 我不確定如何獲得按鈕的大小。在我的系統上,關閉按鈕的寬度為 45 像素,高度為 29 像素。但是我在任何主題資料中都沒有看到這些數字。
繪制這些按鈕的技巧是首先必須將主題檔案作為 dll 打開。可以使用條目從注冊表中提取主題檔案的名稱HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ThemeManager\DllName。在上面的代碼中,我只是將其硬編碼為“C:\Windows\Resources\Themes\aero\aero.msstyles”,但最好從注冊表中提取它而不是硬編碼檔案名。
一旦主題打開,特別的技巧是呼叫
如您所見,此 png 包含一堆控制按鈕的圖片。我們需要使用 GetThemeRect 函式來學習這個 png 中與我們想要繪制的部分相呼應的矩形。
But now we run into a problem. The theme class we need to use is "DWMWindow". This class is completely undocumented and the only way to learn its parts is to use a program like
.
From the program, we can see that the part numbers we are interested in are:
int BUTTONACTIVECAPTION = 3;
int BUTTONACTIVECLOSE = 7;
int BUTTONCLOSEGLYPH96 = 11;
int BUTTONCLOSEGLYPH120 = 12;
int BUTTONCLOSEGLYPH144 = 13;
int BUTTONCLOSEGLYPH192 = 14;
int BUTTONHELPGLYPH96 = 15;
int BUTTONHELPGLYPH120 = 16;
int BUTTONHELPGLYPH144 = 17;
int BUTTONHELPGLYPH192 = 18;
int BUTTONMAXGLYPH96 = 19;
int BUTTONMAXGLYPH120 = 20;
int BUTTONMAXGLYPH144 = 21;
int BUTTONMAXGLYPH192 = 22;
int BUTTONMINGLYPH96 = 23;
int BUTTONMINGLYPH120 = 24;
int BUTTONMINGLYPH144 = 25;
int BUTTONMINGLYPH192 = 26;
int BUTTONRESTOREGLYPH96 = 27;
int BUTTONRESTOREGLYPH120 = 28;
int BUTTONRESTOREGLYPH144 = 29;
int BUTTONRESTOREGLYPH192 = 30;
With those part numbers we can use the GetThemeRect function know which parts of the png to use for the item we want to draw.
There's still some final problems. The rectangles GetThemeRect returns give for part BUTTONCLOSEGLYPH96 = 11 looks like this:

This is called an atlas, and each of the 4 pieces in that subrectangle corresponds to the states normal, hot, pushed, and disabled. However, again since the class is undocumented, the only way to know that is to look at the output from msstyleEditor or getting it from the theme is some other way. Fortunately we can use the GetThemeInt with the TMT_IMAGECOUNT property identifier to get the number of images in the atlas so at least we know how many pieces to cut it into.
There are a few more pieces of information we can pull from the theme data. The GetThemeMargins with the TMT_SIZINGMARGINS property id should tell us how to tile the background images into larger sizes. However in my experiments, the numbers from those margins don't seem to give good results. Consequently, in the code above I just tiled the center part to fill the whole background. In addition, using the TMT_CONTENTMARGINS property id should tell us where to place the glyphs on the background. But again, in my experiments, those positions didn't look good. So in the code above, I just centered the glyphs on the background.
Putting all of this together, we can finally draw the close, min, max, and restore buttons as they appear on a title bar.
uj5u.com熱心網友回復:
您應該能夠使用這些按鈕的本機外觀繪制位圖。然后在 wxButton 上使用這些位圖。
有一些限制,但 Windows 應該對它們有最好的支持。見wxRendererNative:: DrawTitleBarBitmap()。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/328728.html
