何度か作っているが少し真面目に作った。wxSizer等は使っていない。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include "MyItemPanel.hpp" //! @brief 自作リストコントロール class MyObjectListWidget : public wxPanel { wxPanel* _viewArea; //!< リスト表示エリア wxScrollBar* _scrollBar; //!< スクロールバー int _itemHeight; //!< 一つのアイテムの高さ //! @brief 作成したパネルのポインタ一覧 std::vector< ItemPanel* > _createdPanels; //! @brief パネルを生成するオブジェクト std::shared_ptr<MyObjectListItemAccessor> _itemaccessor; int _activedItemIndex; //!< 現在アクティブなアイテムのインデックス //!< パネルを表示 void SetItemPanels(); //!< ウィジェット生成直後の動作 void PostCreate(); public: MyObjectListWidget( wxWindow* parent, std::shared_ptr<MyObjectListItemAccessor> controller, int panelheight); //! @brief パネル一枚の高さを取得 int GetPanelHeight()const { return _itemHeight; } //! @brief panelindex番のパネルが画面から見切れていたら、それが見えるように全体のパネルの配置をずらして調節する void FitPosition(int panelindex); //! @brief アクティブなアイテムを指定 void SetActiveItemIndex(int itemindex) { _activedItemIndex = itemindex; } //! @brief アクティブなアイテムを取得 int GetActiveItemIndex()const { return _activedItemIndex; } //! @brief アイテムの総数を取得 int Count()const; //! @brief 表示可能なアイテム数を計算 int GetDisplayableItemCount()const; //! @brief パネルの表示状態の更新 // 使用するパネルのみ表示し、表示するパネルのアイテム番号を指定し、そのパネルの表示内容を更新する void UpdateItemPanelList(); //! @brief サイズ変更イベント void OnSize(wxSizeEvent& event); //! @brief スクロール void OnScroll(wxScrollEvent& event); };
#include "MyObjectListWidget.hpp" // アイテムの総数を取得 int MyObjectListWidget::Count()const { return _itemaccessor->Size(); } // 表示可能なアイテム数を計算 int MyObjectListWidget::GetDisplayableItemCount()const { return _viewArea->GetSize().y / GetPanelHeight() + 1; } // パネルを表示 void MyObjectListWidget::SetItemPanels() { const int displayable = GetDisplayableItemCount(); const int created = _createdPanels.size(); const int panelHeight = GetPanelHeight(); if (created == displayable) { ; } else if (created < displayable) { for (size_t i = 0; i < created; i++) { _createdPanels[i]->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); if (!_createdPanels[i]->IsShown()) { _createdPanels[i]->Show(); } } // 表示可能なパネル数>定義済みパネル数の場合は // 足りていない分のパネルを追加 for (size_t i = created; i < displayable; i++) { ItemPanel* itemPanel = _itemaccessor->CreatePanel(_viewArea); itemPanel->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); _createdPanels.push_back(itemPanel); } } else { // 表示可能なパネル数<定義済みパネル数の場合は // 余ったパネルを非表示 for (size_t i = displayable; i < created; i++) { _createdPanels[i]->Hide(); } } // スクロールバーの現在位置 const int scrollpos = _scrollBar->GetThumbPosition(); // 表示しているパネル全てについての処理 // サイズのみ更新 for (size_t i = 0; i < displayable; i++) { _createdPanels[i]->SetSize(0, i * panelHeight, _viewArea->GetSize().x, panelHeight); if (!_createdPanels[i]->IsShown()) { _createdPanels[i]->Show(); } _createdPanels[i]->SetPanelInfo(this,i, scrollpos + i); _createdPanels[i]->UpdatePanel(); } } void MyObjectListWidget::FitPosition(int panelindex) { const int panelHeight = GetPanelHeight(); int fixpixel = 0; // アイテムの位置が上方向にはみ出している場合 if(_createdPanels[panelindex]->GetPosition().y < 0){ // 修正するピクセル数を計算 fixpixel = _createdPanels[panelindex]->GetPosition().y; } // アイテムの位置が下方向にはみ出している場合 else if (_createdPanels[panelindex]->GetPosition().y + panelHeight > _viewArea->GetSize().y) { // 修正するピクセル数を計算 fixpixel = _createdPanels[panelindex]->GetPosition().y + panelHeight - _viewArea->GetSize().y; } // 全てのパネルの位置を修正 for (size_t i = 0; i < _createdPanels.size(); i++) { _createdPanels[i]->SetPosition( wxPoint( _createdPanels[i]->GetPosition().x, _createdPanels[i]->GetPosition().y - fixpixel)); } // 必要な部分だけを再描画 _viewArea->Refresh(); _viewArea->Update(); } void MyObjectListWidget::UpdateItemPanelList() { SetItemPanels(); _viewArea->Layout(); } void MyObjectListWidget::PostCreate() { UpdateItemPanelList(); _scrollBar->SetScrollbar( 0, GetDisplayableItemCount(), _itemaccessor->Size(), 5, true); } MyObjectListWidget::MyObjectListWidget( wxWindow* parent, std::shared_ptr<MyObjectListItemAccessor> controller, int panelheight) : wxPanel(parent, wxID_ANY), _itemaccessor(controller), _itemHeight(panelheight) { // レイアウトマネージャーを作成 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); // ViewAreaパネルを作成して追加 _viewArea = new wxPanel(this, wxID_ANY); sizer->Add(_viewArea, 1, wxEXPAND); //_viewAreaの背景を黒 _viewArea->SetBackgroundColour(wxColour(0, 0, 0)); // スクロールバーを作成して追加 _scrollBar = new wxScrollBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL); sizer->Add(_scrollBar, 0, wxEXPAND); // レイアウトを設定 SetSizer(sizer); // サイズ変更時に表示内容の確認と更新 Bind(wxEVT_SIZE, &MyObjectListWidget::OnSize, this); // スクロールイベント Bind(wxEVT_SCROLL_CHANGED, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_LINEUP, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_LINEDOWN, &MyObjectListWidget::OnScroll, this); Bind(wxEVT_SCROLL_THUMBTRACK, &MyObjectListWidget::OnScroll, this); CallAfter(&MyObjectListWidget::PostCreate); Layout(); } // サイズ変更イベント void MyObjectListWidget::OnSize(wxSizeEvent& event) { // ウィンドウの高さが変わった場合、表示可能データ件数が変わり、 // スクロールバーの最大値が変わるため、再設定 // このとき、現在の表示位置を変えないように注意する _scrollBar->SetScrollbar( _scrollBar->GetThumbPosition(), GetDisplayableItemCount(), _itemaccessor->Size(), 5, true); UpdateItemPanelList(); event.Skip(); } // スクロール void MyObjectListWidget::OnScroll(wxScrollEvent& event) { UpdateItemPanelList(); event.Skip(); }
#pragma once #include <wx/wx.h> // 自作リストコントロールで一つのアイテムを表示するためのパネル class ItemPanel :public wxPanel { private: // このパネルが表示上で上から何番目かを表す int panelIndex = 0; class MyObjectListWidget* m_basepanel; size_t _item_index = 0; // このアイテムが指し示すアイテムのインデックス public: ItemPanel(wxWindow* parent); //! @brief 自作リストコントロールへのポインタを取得 class MyObjectListWidget* GetBasePanel() { return m_basepanel; } //! @brief このアイテムのインデックスを取得 size_t GetItemIndex()const { return _item_index; } //! @brief このパネルが表示上で上から何番目かを取得 int GetPanelIndex()const { return panelIndex; } //! @brief このアイテムパネルに各種情報をセット void SetPanelInfo(class MyObjectListWidget* base, int panelindex, size_t itemindex); //! @brief このパネルの表示内容を更新 void UpdatePanel(); /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// virtual size_t Size()const = 0; //! @brief itemindex番のアイテムが表示可能か、つまりインデクスの範囲内かをチェック //! @param itemindex アイテムのインデックス //! @return true: 表示可能 false: 表示不可 virtual bool IsValidItem(const size_t itemindex) = 0; //! @brief パネル一枚の表示を更新 //! @param panelIndex パネルのインデックス //! @param itemindex アイテムのインデックス //! @details スクロールが発生するたびにパネル内のウィジェットの表示内容を //! itemindex番の内容に更新する virtual void UpdatePanelItem(int panelIndex, size_t itemindex) = 0; //! @brief パネル一枚の表示を更新(インデクス範囲外の場合) //! @param panelIndex パネルのインデックス //! @details スクロールが発生して、パネルの表示内容を更新した際に、 //! itemindex番の内容が存在しない場合に呼び出される virtual void UpdatePanelOutOfRange(int panelIndex) = 0; //! @brief パネル一枚の再描画の内容を記述する //! @param dc 描画コンテキスト //! @param panelIndex パネルのインデックス //! @param itemindex アイテムのインデックス //! @details パネルのOnPaintイベント発生時に呼び出される。 //! 背景色などを変える場合はここに記載 virtual void PaintValid(wxDC& dc, int panelIndex, size_t itemindex) = 0; //! @brief パネル一枚の再描画の内容を記述する(インデクス範囲外の場合) //! @param dc 描画コンテキスト //! @param panelIndex パネルのインデックス //! @details パネルのOnPaintイベント発生時に呼び出される。 //! 背景色などを変える場合はここに記載 //! 特に表示内容がない場合は、背景色を変えるなどして、パネルが無いように見せる virtual void PaintOutOfRange(wxDC& dc, int panelIndex) = 0; void OnPaint(wxPaintEvent& event); //! @brief パネルをクリックしたときの挙動。 //! 有効なパネル(表示可能なアイテム)をクリックした場合は、OnEventLeftClickを呼び出す) //! 無効なパネル(表示不可能なアイテム)をクリックした場合は、OnEventLeftClickOutOfRangeを呼び出す virtual void OnLeftClick(wxMouseEvent& event); //! @brief 有効なパネルをクリックしたときの挙動 virtual void OnEventLeftClick(wxMouseEvent& event, int panelIndex, size_t itemindex); //! @brief 無効なパネルをクリックしたときの挙動 virtual void OnEventLeftClickOutOfRange(wxMouseEvent& event, int panelIndex); }; //! @brief パネルとパネルに表示するデータを関連付けるためのクラス //! @details パネルの生成時、各パネルはデータへアクセスできなければならない。 //! CreatePanel関数でパネルを作成するときに、データのポインタなどを用いて各パネルがデータへアクセスできるようにする. class MyObjectListItemAccessor { public: //! @brief パネルを生成する virtual ItemPanel* CreatePanel(wxWindow* parent) = 0; virtual ~MyObjectListItemAccessor() {}; //! @brief データの要素数を取得 virtual size_t Size()const = 0; };
#include "MyItemPanel.hpp" #include "MyObjectListWidget.hpp" ItemPanel::ItemPanel(wxWindow* parent) : m_basepanel(nullptr), wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxFULL_REPAINT_ON_RESIZE, wxPanelNameStr) { Bind(wxEVT_PAINT, &ItemPanel::OnPaint, this); Bind(wxEVT_LEFT_DOWN, &ItemPanel::OnLeftClick, this); } void ItemPanel::SetPanelInfo(class MyObjectListWidget* base, int panelindex, size_t itemindex) { panelIndex = panelindex; _item_index = itemindex; m_basepanel = base; } void ItemPanel::UpdatePanel() { if (IsValidItem(_item_index) == true) { UpdatePanelItem(panelIndex, _item_index); } else { UpdatePanelOutOfRange(panelIndex); } } void ItemPanel::OnPaint(wxPaintEvent& event) { ItemPanel* panel = (ItemPanel*)event.GetEventObject(); MyObjectListWidget* parent = panel->GetBasePanel(); wxPaintDC dc(panel); if (IsValidItem(panel->GetItemIndex()) == true) { PaintValid( dc, panel->GetPanelIndex(), panel->GetItemIndex() ); } else { PaintOutOfRange( dc, panel->GetPanelIndex() ); } } void ItemPanel::OnLeftClick(wxMouseEvent& event) { MyObjectListWidget* parent = this->GetBasePanel(); if (parent) { if (IsValidItem(this->GetItemIndex()) == false) { OnEventLeftClickOutOfRange( event, this->GetPanelIndex() ); } else { OnEventLeftClick( event, this->GetPanelIndex(), this->GetItemIndex() ); parent->FitPosition(this->GetPanelIndex()); } } } void ItemPanel::OnEventLeftClick(wxMouseEvent& event, int panelIndex, size_t itemindex) { MyObjectListWidget* parent = this->GetBasePanel(); parent->SetActiveItemIndex(itemindex); } void ItemPanel::OnEventLeftClickOutOfRange(wxMouseEvent& event, int panelIndex) { }
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #include"MyObjectListWidget.hpp" ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <string> // データ struct MyData { std::string name; int index; };
//! @brief リストの各項目を表示するパネル class MyItemPanel :public ItemPanel { std::weak_ptr<std::vector<MyData>> m_pdata; wxStaticText* _label; public: MyItemPanel(wxWindow* parent) : ItemPanel(parent){ _label = new wxStaticText(this, wxID_ANY, "<unknown>"); _label->SetPosition(wxPoint(10, 10)); // ラベルの背景色とテキスト色を設定 _label->SetBackgroundColour(wxColour(255, 255, 255)); // 白色の背景 _label->SetForegroundColour(wxColour(0, 0, 0)); // 黒色のテキスト _label->Show(); } void setData(std::weak_ptr<std::vector<MyData>> data) { m_pdata = data; } virtual size_t Size()const override { return m_pdata.lock()->size(); }; virtual bool IsValidItem(const size_t itemindex)override { auto datalist = m_pdata.lock(); if (itemindex < datalist->size()) return true; return false; } virtual void UpdatePanelItem(int panelIndex, size_t itemindex)override { auto data = m_pdata.lock(); if (_label) { _label->SetLabel( wxString::Format( "%d Item %s [%d]", panelIndex, (*data)[itemindex].name.c_str(), (*data)[itemindex].index ) ); } // パネルの再描画 Refresh(); }; virtual void UpdatePanelOutOfRange(int panelIndex)override { auto data = m_pdata.lock(); if (_label) { _label->SetLabel("out of range"); } // パネルの再描画 Refresh(); } virtual void PaintValid(wxDC& dc, int panelIndex, size_t itemindex)override { dc.SetPen(wxPen(wxColour(0, 0, 0), 1)); if (itemindex == this->GetBasePanel()->GetActiveItemIndex()) { dc.SetBrush(wxBrush(wxColour(255, 0, 0))); } else { dc.SetBrush(wxBrush(wxColour(200, 200, 255))); } wxSize size = GetSize(); dc.DrawRectangle(0, 0, size.x, size.y); } virtual void PaintOutOfRange(wxDC& dc, int panelIndex)override { dc.SetPen(wxPen(wxColour(255, 0, 200), 2)); dc.SetBrush(wxBrush(wxColour(255, 0, 200))); wxSize size = GetSize(); dc.DrawRectangle(0, 0, size.x, size.y); } };
//! @brief リストの各項目を生成するクラス class MyOLAccessor :public MyObjectListItemAccessor { std::weak_ptr<std::vector<MyData>> m_data; public: MyOLAccessor(std::weak_ptr<std::vector<MyData>> data) : m_data(data) {} virtual ItemPanel* CreatePanel(wxWindow* parent)override { auto ptr = new MyItemPanel(parent); ptr->setData(m_data); return ptr; } virtual size_t Size()const override{ return m_data.lock()->size(); } };
// ウィンドウ作成 class MyFrame : public wxFrame { std::shared_ptr<std::vector<MyData>> m_data; public: void PostCreate() { m_data = std::make_shared<std::vector<MyData>>(); for(size_t i = 0; i < 13; i++) { m_data->push_back({ "name " + std::to_string(i), (int)i }); } MyObjectListWidget* objList = new MyObjectListWidget( this, std::make_shared<MyOLAccessor>(m_data), // データへアクセス 80 // アイテムの高さ ); objList->SetSize(0, 0, 200, 200); this->Layout(); // レイアウトの更新 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);