ZetCode

wxWidgets 中的自定义控件

最后修改于 2023 年 10 月 18 日

工具包通常只提供最常见的控件,如按钮、文本控件、滚动条、滑块等。没有一个工具包可以提供所有可能的控件。wxWidgets 有许多不同的控件;更专业的控件由客户端程序员创建。

自定义控件是通过使用工具包提供的绘图工具创建的。有两种可能性:程序员可以修改或增强现有的控件,或者可以从头开始创建自定义控件。

燃烧控件

这是一个我们从头开始创建的控件的例子。这个控件可以在各种媒体刻录应用程序中找到,比如 Nero Burning ROM。

widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <wx/wx.h>

class Widget : public wxPanel
{
public:
  Widget(wxPanel *parent, int id );

  wxPanel *m_parent;


  void OnSize(wxSizeEvent& event);
  void OnPaint(wxPaintEvent& event);  

};

#endif
widget.cpp
#include <wx/wx.h>
#include "widget.h"
#include "burning.h"

int num[] = { 75, 150, 225, 300, 375, 450, 525, 600, 675 };
int asize = sizeof(num)/sizeof(num[1]);

Widget::Widget(wxPanel *parent, int id)
      : wxPanel(parent, id, wxDefaultPosition, wxSize(-1, 30), wxSUNKEN_BORDER)
{
 
  m_parent = parent;

  Connect(wxEVT_PAINT, wxPaintEventHandler(Widget::OnPaint));
  Connect(wxEVT_SIZE, wxSizeEventHandler(Widget::OnSize));

}

void Widget::OnPaint(wxPaintEvent& event)
{
  wxFont font(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
            wxFONTWEIGHT_NORMAL, false, wxT("Courier 10 Pitch"));

  wxPaintDC dc(this);
  dc.SetFont(font);
  wxSize size = GetSize();
  int width = size.GetWidth();

  Burning *burn = (Burning *) m_parent->GetParent();

  int cur_width = burn->GetCurWidth();

  int step = (int) round(width / 10.0);


  int till = (int) ((width / 750.0) * cur_width);
  int full = (int) ((width / 750.0) * 700);


  if (cur_width >= 700) {

      dc.SetPen(wxPen(wxColour(255, 255, 184))); 
      dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
      dc.DrawRectangle(0, 0, full, 30);
      dc.SetPen(wxPen(wxColour(255, 175, 175)));
      dc.SetBrush(wxBrush(wxColour(255, 175, 175)));
      dc.DrawRectangle(full, 0, till-full, 30);

  } else { 

      dc.SetPen(wxPen(wxColour(255, 255, 184)));
      dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
      dc.DrawRectangle(0, 0, till, 30);

  }

  dc.SetPen(wxPen(wxColour(90, 80, 60)));
  for ( int i=1; i <= asize; i++ ) {

  dc.DrawLine(i*step, 0, i*step, 6);
  wxSize size = dc.GetTextExtent(wxString::Format(wxT("%d"), num[i-1]));
  dc.DrawText(wxString::Format(wxT("%d"), num[i-1]), 
      i*step-size.GetWidth()/2, 8);
   }
}

void Widget::OnSize(wxSizeEvent& event)
{
  Refresh();
}
burning.h
#ifndef BURNING_H
#define BURNING_H

#include <wx/wx.h>
#include "widget.h"

class Burning : public wxFrame
{
public:
  Burning(const wxString& title);

  void OnScroll(wxScrollEvent& event);
  int GetCurWidth();

  
  wxSlider *m_slider;
  Widget *m_wid;

  int cur_width;

};

#endif
burning.cpp
#include "burning.h"
#include "widget.h"

int ID_SLIDER = 1;

Burning::Burning(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(350, 200))
{

  cur_width = 75;

  wxPanel *panel = new wxPanel(this, wxID_ANY);
  wxPanel *centerPanel = new wxPanel(panel, wxID_ANY);

  m_slider = new wxSlider(centerPanel, ID_SLIDER, 75, 0, 750, wxPoint(-1, -1), 
      wxSize(150, -1), wxSL_LABELS);

  wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL);

  m_wid = new Widget(panel, wxID_ANY);
  hbox->Add(m_wid, 1, wxEXPAND);

  hbox2->Add(centerPanel, 1, wxEXPAND);
  hbox3->Add(m_slider, 0, wxTOP | wxLEFT, 35);

  centerPanel->SetSizer(hbox3);

  vbox->Add(hbox2, 1, wxEXPAND);
  vbox->Add(hbox, 0, wxEXPAND);

  panel->SetSizer(vbox);
  m_slider->SetFocus();

  Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED, 
      wxScrollEventHandler(Burning::OnScroll)); 

  Centre();

}

void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
 cur_width = m_slider->GetValue();
 m_wid->Refresh();
}


int Burning::GetCurWidth() 
{
 return cur_width;
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "burning.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

    Burning *burning = new Burning(wxT("The Burning Widget"));
    burning->Show(true);

    return true;
}

我们在窗口底部放置一个 wxPanel,并手动绘制整个控件。所有重要的代码都位于控件类的 OnPaint 方法中。该控件以图形方式显示媒体的总容量和可用的剩余空间。该控件由滑块控制。我们自定义控件的最小值是 0,最大值是 750。如果我们达到 700 的值,我们就开始用红色绘制。这表示超刻录。

wxSize size = GetSize();
int width = size.GetWidth();
... 
int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);

我们动态地绘制控件。窗口越大,燃烧控件越大。反之亦然。这就是为什么我们必须计算用于绘制自定义控件的 wxPanel 的大小。till 参数决定要绘制的总大小。这个值来自滑块控件。它是整个区域的一部分。full 参数决定我们开始用红色绘制的点。请注意浮点算术的使用。这是为了实现更高的精度。

实际的绘制包括三个步骤。我们绘制黄色或红色和黄色的矩形。然后我们绘制垂直线,将部件分成几个部分。最后,我们绘制数字,表示介质的容量。

void Widget::OnSize(wxSizeEvent& event)
{
  Refresh();
}

每次调整窗口大小时,我们都会刷新控件。这将导致控件重新绘制自身。

void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
 cur_width = m_slider->GetValue();
 m_wid->Refresh();
}

如果我们滚动滑块的拇指,我们会得到实际值并将其保存到 cur_width 变量中。当绘制燃烧控件时,将使用该值。然后我们导致控件被重绘。

Burning Widget
图:燃烧控件

在 wxWidgets 教程的这一部分中,我们创建了一个自定义控件。