趁此機會做個廣告,http://www.gaclib.net終于上線啦!
GacUI的列表控件的第二個Demo是關于列表項的多選的。跟Windows一樣,我們可以通過鼠標和方向鍵,配合CTRL和SHIFT選擇列表的多個內容。因此這次我實現了一個簡單的“名字選擇窗口”,就跟QQ邀請好友入群的界面一樣,兩個列表,兩個按鈕。先看圖:

列表內容始終是排序的。當我們選中一邊的內容之后,可以按按鈕把內容復制到另一邊。現在我們先來看一看創建和排版這些控件的代碼。這里我用了一個五行三列的表格。左右方列表,中間的第二個第四行放按鈕,第三行64個像素高,按鈕32*32,第一行和第五行平均地充滿剩下的空間:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
// for SortedList, CopyFrom and Select
using namespace vl::collections;
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
class NameSelectorWindow : public GuiWindow
{
private:
GuiTextList* listSource;
GuiTextList* listDestination;
GuiButton* buttonAdd;
GuiButton* buttonRemove;
(略)
public:
NameSelectorWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.ListBox.NameSelector");
GuiTableComposition* table=new GuiTableComposition;
table->SetRowsAndColumns(5, 3);
table->SetCellPadding(3);
table->SetAlignmentToParent(Margin(0, 0, 0, 0));
table->SetRowOption(0, GuiCellOption::PercentageOption(0.5));
table->SetRowOption(1, GuiCellOption::MinSizeOption());
table->SetRowOption(2, GuiCellOption::AbsoluteOption(64));
table->SetRowOption(3, GuiCellOption::MinSizeOption());
table->SetRowOption(4, GuiCellOption::PercentageOption(0.5));
table->SetColumnOption(0, GuiCellOption::PercentageOption(0.5));
table->SetColumnOption(1, GuiCellOption::MinSizeOption());
table->SetColumnOption(2, GuiCellOption::PercentageOption(0.5));
this->GetContainerComposition()->AddChild(table);
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 0, 5, 1);
listSource=g::NewTextList();
listSource->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
// make listSource's horizontal scroll bar disappeared when it is not needed.
listSource->SetHorizontalAlwaysVisible(false);
listSource->SetMultiSelect(true);
listSource->SelectionChanged.AttachMethod(this, &NameSelectorWindow::listSource_SelectionChanged);
cell->AddChild(listSource->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 2, 5, 1);
listDestination=g::NewTextList();
listDestination->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
// make listSource's horizontal scroll bar disappeared when it is not needed.
listDestination->SetHorizontalAlwaysVisible(false);
listDestination->SetMultiSelect(true);
listDestination->SelectionChanged.AttachMethod(this, &NameSelectorWindow::listDestination_SelectionChanged);
cell->AddChild(listDestination->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(1, 1, 1, 1);
buttonAdd=g::NewButton();
buttonAdd->SetText(L">>");
buttonAdd->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonAdd->GetBoundsComposition()->SetPreferredMinSize(Size(32, 32));
buttonAdd->Clicked.AttachMethod(this, &NameSelectorWindow::buttonAdd_Clicked);
buttonAdd->SetEnabled(false);
cell->AddChild(buttonAdd->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(3, 1, 1, 1);
buttonRemove=g::NewButton();
buttonRemove->SetText(L"<<");
buttonRemove->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonRemove->GetBoundsComposition()->SetPreferredMinSize(Size(32, 32));
buttonRemove->Clicked.AttachMethod(this, &NameSelectorWindow::buttonRemove_Clicked);
buttonRemove->SetEnabled(false);
cell->AddChild(buttonRemove->GetBoundsComposition());
}
// Add names into listSource
LoadNames(listSource);
// set the preferred minimum client size
this->GetBoundsComposition()->SetPreferredMinSize(Size(640, 480));
// call this to calculate the size immediately if any indirect content in the table changes
// so that the window can calcaulte its correct size before calling the MoveToScreenCenter()
this->ForceCalculateSizeImmediately();
// move to the screen center
this->MoveToScreenCenter();
}
};
void GuiMain()
{
GuiWindow* window=new NameSelectorWindow;
GetApplication()->Run(window);
delete window;
}
剩下的內容分為三部分。首先是如何在列表沒有選中內容的時候把按鈕變灰。在上面的代碼中我們知道listSource和listDestination都監聽了SelectionChanged事件。事件處理函數的內容如下:
void listSource_SelectionChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
buttonAdd->SetEnabled(listSource->GetSelectedItems().Count()>0);
}
void listDestination_SelectionChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
buttonRemove->SetEnabled(listDestination->GetSelectedItems().Count()>0);
}
代碼內容十分簡潔明了。第二部分是NameSelectorWindow構造函數中調用的LoadNames函數。我們首先把所有的名字都放在一個const wchar_t* DataSource[]數組里面,內容是沒有排過序的:
const wchar_t* DataSource[]=
{
L"Weierstrass",
L"Cantor",
L"Bernoulli",
L"Fatou",
L"Green",
L"S.Lie",
L"Euler",
L"Gauss",
L"Sturm",
L"Riemann",
L"Neumann",
L"Caratheodory",
L"Newton",
L"Jordan",
L"Laplace",
L"Wiener",
L"Thales",
L"Maxwell",
L"Riesz",
L"Fourier",
L"Noether",
L"Kepler",
L"Kolmogorov",
L"Borel",
L"Sobolev",
L"Dirchlet",
L"Lebesgue",
L"Leibniz",
L"Abel",
L"Lagrange",
L"Ramanujan",
L"Ljapunov",
L"Holder",
L"Poisson",
L"Nikodym",
L"H.Hopf",
L"Pythagoras",
L"Baire",
L"Haar",
L"Fermat",
L"Kronecker",
L"E.Laudau",
L"Markov",
L"Wronski",
L"Zermelo",
L"Rouche",
L"Taylor",
L"Urysohn",
L"Frechet",
L"Picard",
L"Schauder",
L"Lipschiz",
L"Liouville",
L"Lindelof",
L"de Moivre",
L"Klein",
L"Bessel",
L"Euclid",
L"Kummer",
L"Ascoli",
L"Chebyschev",
L"Banach",
L"Hilbert",
L"Minkowski",
L"Hamilton",
L"Poincare",
L"Peano",
L"Zorn",
};
然后LoadNames函數如下所示:
void LoadNames(GuiTextList* list)
{
// Use linq for C++ to create sorted TextItem(s) from DataSource
CopyFrom(
list->GetItems(),
FromArray(DataSource)
>>OrderBy(_wcsicmp)
>>Select<const wchar_t*, list::TextItem>(
[](const wchar_t* name){return list::TextItem(name);}
)
);
}
首先我們用FromArray函數從const wchar_t* DataSource[]數組創建出一個IEnumerable<const wchar_t*>,然后用_wcsicmp進行排序,最后把每一個const wchar_t* name都用list::TextItem(name)轉變成一個列表項。
最后一步比較復雜,就是如何在移動列表的內容的時候還保持兩個列表的內容是排序的。首先,從一個排序列表中刪除東西,結果肯定是排序的。其次,把一堆新的名字插入一個列表,最簡單的方法就是把所有的東西連起來重新排序一遍,然后放回去:
static list::TextItem GetTextItem(GuiTextList* list, int index)
{
return list->GetItems()[index];
}
static int CompareTextItem(list::TextItem a, list::TextItem b)
{
return _wcsicmp(a.GetText().Buffer(), b.GetText().Buffer());
}
static int ReverseCompareInt(int a, int b)
{
return b-a;
}
void MoveNames(GuiTextList* from, GuiTextList* to)
{
CopyFrom(
to->GetItems(),
to->GetItems()
>>Concat(
from->GetSelectedItems()>>Select(Curry(GetTextItem)(from))
)
>>OrderBy(CompareTextItem)
);
List<int> selectedItems;
CopyFrom(
selectedItems.Wrap(),
from->GetSelectedItems()
>>OrderBy(ReverseCompareInt)
);
FOREACH(int, index, selectedItems.Wrap())
{
from->GetItems().RemoveAt(index);
}
}
void buttonAdd_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
MoveNames(listSource, listDestination);
}
void buttonRemove_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
MoveNames(listDestination, listSource);
}
我們可以看到MoveNames函數里面做了三件事情。
第一件事情就是把to列表中的內容,和from列表中選中的內容Concat起來,也就是一個連著一個組成一個沒有排序過的IEnumerable<list::TextItem>,然后用CompareTextItem進行排序。其中Curry(GetTextItem)(item)的意思是,將item作為GetTextItem的第一個參數,把“填補剩下的參數”的這個過程產生一個新的函數,就跟bind1st函數的意思是一樣的。所以經過這個Select,就可以吧GetSelectedItems()返回的所有選中的item的下標,給轉換成原原本本的列表項。這樣將to里面的列表項和from里面選中的列表項所有的內容合起來排序,最后放回to列表里面,這樣東西就挪過去了。
第二件事情就是要把from->GetSelectedItems()的內容都復制下來,然后逆序。
第三件事情,就是遍歷逆序后的GetSelectedItems的結果,一個一個刪除掉選中的項目了。列表控件只要內容一變化,選中就會全部清空,因此如果不先保留GetSelectedItems的結果的話,刪除了一個列表項之后,GetSelectedItems就會變空,這樣就會出bug。
這個Demo就介紹到這里了。GacUI下一個列表控件的Demo將是關于虛擬模式的。也就是說,這次我們不一個一個Add進去了,而是直接告訴列表控件一共有多少個項目,然后列表要顯示的時候回調一下獲得真正的內容。這個功能是很有用的,特別是當內容特別多,我們沒辦法一下子準備好的時候,可以讓界面很快的出來,讓用戶感覺起來,程序運行的很流暢。
posted on 2012-05-25 21:54
陳梓瀚(vczh) 閱讀(3633)
評論(12) 編輯 收藏 引用 所屬分類:
GacUI