GacUI在结束了文本框的介绍之后,开始进入列表的介绍。列表内容丰富,包含各种预定义的列表控件、用来显示和操作大量对象的虚拟模式、MVC分离、修改列表样式等内容。今天先从文本列表的简单操作开始。这个Demo展示了如何对列表进行添加和删除。窗口里面有一个列表,然后有添加和删除两个按钮,分别用于把文本框的内容添加到列表内,和删除掉选中的列表项的。在这个Demo里面只允许列表项单选,并且水平滚动条默认不出现。先看图:
空间如何布局,我就不再赘述了,明显是一个四行三列的表格。代码如下:
#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 NameEditorWindow : public GuiWindow
{
private:
GuiTextList* listBox;
GuiSinglelineTextBox* textBox;
GuiButton* buttonAdd;
GuiButton* buttonRemove;
void buttonAdd_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// add the specified name at the end of the list box
listBox->GetItems().Add(textBox->GetText());
textBox->SelectAll();
textBox->SetFocus();
}
void buttonRemove_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// remove the selected items using item index
listBox->GetItems().RemoveAt(listBox->GetSelectedItems()[0]);
}
void listBox_SelectionChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// disable the button if no item is selected
buttonRemove->SetEnabled(listBox->GetSelectedItems().Count()>0);
}
public:
NameEditorWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.ListBox.NameEditor");
GuiTableComposition* table=new GuiTableComposition;
table->SetRowsAndColumns(4, 3);
table->SetCellPadding(3);
table->SetAlignmentToParent(Margin(0, 0, 0, 0));
table->SetRowOption(0, GuiCellOption::MinSizeOption());
table->SetRowOption(1, GuiCellOption::MinSizeOption());
table->SetRowOption(2, GuiCellOption::MinSizeOption());
table->SetRowOption(3, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(0, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(1, GuiCellOption::MinSizeOption());
table->SetColumnOption(2, GuiCellOption::MinSizeOption());
this->GetContainerComposition()->AddChild(table);
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 0, 4, 1);
listBox=g::NewTextList();
listBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
listBox->SetHorizontalAlwaysVisible(false);
listBox->SelectionChanged.AttachMethod(this, &NameEditorWindow::listBox_SelectionChanged);
cell->AddChild(listBox->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 1, 1, 1);
GuiLabel* label=g::NewLabel();
label->SetText(L"Name to add: ");
label->GetBoundsComposition()->SetAlignmentToParent(Margin(0, -1, 0, 0));
cell->AddChild(label->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 2, 1, 1);
textBox=g::NewTextBox();
textBox->GetBoundsComposition()->SetPreferredMinSize(Size(120, 23));
textBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
cell->AddChild(textBox->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(1, 1, 1, 2);
buttonAdd=g::NewButton();
buttonAdd->SetText(L"Add");
buttonAdd->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonAdd->Clicked.AttachMethod(this, &NameEditorWindow::buttonAdd_Clicked);
cell->AddChild(buttonAdd->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(2, 1, 1, 2);
buttonRemove=g::NewButton();
buttonRemove->SetText(L"Delete");
buttonRemove->SetEnabled(false);
buttonRemove->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonRemove->Clicked.AttachMethod(this, &NameEditorWindow::buttonRemove_Clicked);
cell->AddChild(buttonRemove->GetBoundsComposition());
}
// set the preferred minimum client size
this->GetBoundsComposition()->SetPreferredMinSize(Size(480, 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 NameEditorWindow;
GetApplication()->Run(window);
delete window;
}
这里需要注意的几点就是,为了实现在列表没有选中内容的时候禁用删除按钮,我们需要监听GuiTextList::SelectionChanged事件。核心的代码就是下面这几行:
void buttonAdd_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// add the specified name at the end of the list box
listBox->GetItems().Add(textBox->GetText());
textBox->SelectAll();
textBox->SetFocus();
}
void buttonRemove_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// remove the selected items using item index
listBox->GetItems().RemoveAt(listBox->GetSelectedItems()[0]);
}
void listBox_SelectionChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// disable the button if no item is selected
buttonRemove->SetEnabled(listBox->GetSelectedItems().Count()>0);
}
GuiTextList控件的GetItems函数返回所有的列表项。这个对象有Add、Insert、Clear、IndexOf、Remove、RemoveAt、Contains、Count等函数,可以用来操作列表项。GuiTextList还有GetSelectedItems函数(其实是定义在GuiSelectableListControl里面的),可以用来获得所有选中的列表项的下标(从0开始)。每当列表内容被修改的时候,GetSelectedItems的结果就会被自动清空。
下一个Demo将是关于如何处理允许多选的列表的操作方法。
posted @
2012-05-23 04:42 陈梓瀚(vczh) 阅读(2327) |
评论 (4) |
编辑 收藏
这是
GacUI关于文本框高亮的最后一个Demo了。这个Demo是关于XML着色的。XML着色比起C++着色更难,主要是因为在类似<book name="C++ Primer">这样的代码里面,book和name的颜色不一样,<和book的颜色也不一样(参考Visual Studio)。这种时候单纯依靠正则表达式来区分颜色是不够的,我们还需要引入一个新的状态机。这个状态机只有三个状态,用来区分tag name,attribute name和text三种颜色。状态机是手写的,并且GacUI提供了一个回调来写这个只有寥寥几行状态机。先看图:
跟C++一样,XML着色首先是由正则表达式组成的。XML的正则表达式比较简单,只有符号、name、字符串、CData、注释和其它一些简单的东西:
1 class XmlColorizer : public GuiTextBoxRegexColorizer
2 {
3 public:
4 XmlColorizer()
5 {
6 text::ColorEntry entry=win7::Win7GetTextBoxTextColor();
7 SetDefaultColor(entry);
8
9 entry.normal.text=Color(0, 128, 0);
10 AddToken(L"/<!--([^/-]|-[^/-]|--[^>])*--/>", entry);
11
12 entry.normal.text=Color(128, 0, 255);
13 AddToken(L"/<!/[CDATA/[([^/]]|/][^/]]|/]/][^>])*/]/]/>", entry);
14
15 entry.normal.text=Color(0, 0, 0);
16 AddToken(L"\"[^\"]*\"", entry);
17
18 entry.normal.text=Color(0, 0, 255);
19 AddToken(L"[<>=]", entry);
20
21 entry.normal.text=Color(255, 0, 0);
22 AddToken(L"[a-zA-Z0-9_/-:]+", entry);
23
24 entry.normal.text=Color(163, 21, 21);
25 AddExtraToken(entry);
26
27 Setup();
28 }
29
30 void ColorizeTokenContextSensitive(const wchar_t* text, vint start, vint length, vint& token, int& contextState)override
31 {
32
33 }
34
35 int GetContextStartState()override
36 {
37
38 }
39 }; 其次要对三种地方的[a-zA-Z0-9_/-:]进行着色。Tag的名字用褐色,attribute的名字用红色,而普通文本用黑色。因此我们可以做一个状态机,初始状态为0。如果读进了<,状态变成1。1遇到了一个Tag名字之后变为2。从2开始所有的名字就只能是attribute的名字了。我们只考虑正确的情况,错误的代码着色出了问题不仅没有坏处,还可以提醒程序员有什么地方写错了。之后遇到了>变回0,在0的状态下所有的东西都是普通文本,所以名字就都是黑色的。因此上面ColorizeTokenContextSensitive函数中就需要填入这个逻辑。GetContextStartState返回0,作为第一行的起始状态。代码如下:
void ColorizeTokenContextSensitive(const wchar_t* text, vint start, vint length, vint& token, int& contextState)override
{
// 0 < 1 name 2 att > 0
switch(token)
{
case 3:
if(length==1)
{
switch(text[start])
{
case '<':
contextState=1;
break;
case '>':
contextState=0;
break;
}
}
break;
case 4:
switch(contextState)
{
case 0:
token=-1;
break;
case 1:
token=5;
contextState=2;
break;
}
break;
}
}
int GetContextStartState()override
{
return 0;
}
这个函数里面有几个魔法数字,其实都是关于Token的编号的。构造函数里面我们使用AddToken将一个颜色关联到正则表达式上,使用AddExtraToken创建一个没有正则表达式关联的颜色。所以在这个状态机里面,所有的颜色都用Token的序号来表示。无论是使用AddToken还是AddExtraToken,第一个颜色编号为0,第二个颜色编号为1。因此case 3指的是[<>=],而case 4指的是[a-zA-Z0-9_/-:]+。而case 4里面的token=5则表示在状态为1的时候,名字都用AddExtraToken指定的那个褐色进行染色。缺省的名字(也就是id为4的token)是红色,所以不需要对contextState为2的时候进行处理。
这样我们就完成了XML的着色。GacUI接下来的几个Demo将会是关于ListBox、ListView和TreeView的,敬请期待。
posted @
2012-05-20 00:41 陈梓瀚(vczh) 阅读(2233) |
评论 (2) |
编辑 收藏
GacUI终于把
上一篇文章中提到的自动采用正则表达式进行高亮的Demo做出来了。这次实现的是C++ colorizer。而XML colorizer不仅需要正则表达式,还需要一个人工维护的状态,这个等到下一个Demo再提及。先看图
在不需要人工维护状态,仅通过正则表达式就可以着色的时候,编写一个colorizer变得十分的简单。这个Colorizer虽然不是一定非得通过继承来实现,但是这个Demo还是使用了继承。首先编写一个类,继承自GuiTextBoxRegexColorizer,然后在构造函数里面填写下面的代码:
class CppColorizer : public GuiTextBoxRegexColorizer
{
public:
CppColorizer()
{
text::ColorEntry entry=win7::Win7GetTextBoxTextColor();
SetDefaultColor(entry);
entry.normal.text=Color(128, 0, 255);
AddToken(L"/d+(./d*)?([eE][+/-]?/d+)?", entry);
entry.normal.text=Color(163, 21, 21);
AddToken(L"\"([^\"]|\\\\/.)*\"", entry);
entry.normal.text=Color(0, 128, 0);
AddToken(L"////[^\r\n]*", entry);
AddToken(L"///*(//|[*]*[^*//])*/*+//", entry);
entry.normal.text=Color(0, 0, 255);
AddToken(L"#[a-zA-Z0-9_]*", entry);
AddToken(CppKeywords, entry);
AddToken(L"[a-zA-Z0-9_]+", GetDefaultColor());
Setup();
}
};
然后只需要把它绑定到文本框里面就可以了。在这个Demo里面,我们在下拉框的事件里面添加下面的代码:
void comboSelector_SelectedIndexChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
switch(comboSelector->GetSelectedIndex())
{
case 0:(略)
case 1:(略)
case 2:
textBox->SetColorizer(new CppColorizer);
textBox->SetText(
L"#include <iostream>\r\n"
L"using namespace std;\r\n"
L"\r\n"
L"int main()\r\n"
L"{\r\n"
L"\t//This is a comment\r\n"
L"\t/**This*is/another\r\n"
L"\tcomment**/\r\n"
L"\tcout<<\"Hello, world!\"<<endl;\r\n"
L"\treturn 0;\r\n"
L"}\r\n"
);
break;
default:
textBox->SetColorizer(0);
}
}
当然这样是不完整的,因为CppColorizer里面还有一个CppKeywords的常量。这实际上是一个正则表达式,里面用“|”字符把所有C++的关键字连了起来。内容抄自MSDN的C++ Language Reference:
1 const wchar_t* CppKeywords=
2 L"__abstract|"
3 L"__alignof|"
4 L"__asm|"
5 L"__assume|"
6 L"__based|"
7 L"__box|"
8 L"__cdecl|"
9 L"__declspec|"
10 L"__delegate|"
11 L"__event|"
12 L"__except|"
13 L"__fastcall|"
14 L"__finally|"
15 L"__forceinline|"
16 L"__gc|"
17 L"__hook|"
18 L"__identifier|"
19 L"__if_exists|"
20 L"__if_not_exists|"
21 L"__inline|"
22 L"__int16|"
23 L"__int32|"
24 L"__int64|"
25 L"__int8|"
26 L"__interface|"
27 L"__leave|"
28 L"__m128d|"
29 L"__m128|"
30 L"__m128i|"
31 L"__m64|"
32 L"__multiple_inheritance|"
33 L"__nogc|"
34 L"__noop|"
35 L"__pin|"
36 L"__property|"
37 L"__raise|"
38 L"__sealed|"
39 L"__single_inheritance|"
40 L"__stdcall|"
41 L"__super|"
42 L"__try|"
43 L"__except|"
44 L"__finally|"
45 L"__try_cast|"
46 L"__unaligned|"
47 L"__unhook|"
48 L"__uuidof|"
49 L"__value|"
50 L"__virtual_inheritance|"
51 L"__w64|"
52 L"__wchar_t|"
53 L"wchar_t|"
54 L"abstract|"
55 L"array|"
56 L"bool|"
57 L"break|"
58 L"case|"
59 L"catch|"
60 L"char|"
61 L"class|"
62 L"const_cast|"
63 L"const|"
64 L"continue|"
65 L"decltype|"
66 L"default|"
67 L"delegate|"
68 L"delete|"
69 L"do|"
70 L"double|"
71 L"dynamic_cast|"
72 L"else|"
73 L"enum|"
74 L"event|"
75 L"explicit|"
76 L"extern|"
77 L"false|"
78 L"finally|"
79 L"float|"
80 L"for|"
81 L"friend|"
82 L"gcnew|"
83 L"generic|"
84 L"goto|"
85 L"if|"
86 L"initonly|"
87 L"inline|"
88 L"int|"
89 L"interface|"
90 L"interior_ptr|"
91 L"literal|"
92 L"long|"
93 L"mutable|"
94 L"namespace|"
95 L"new|"
96 L"new|"
97 L"nullptr|"
98 L"operator|"
99 L"private|"
100 L"property|"
101 L"property|"
102 L"protected|"
103 L"public|"
104 L"register|"
105 L"reinterpret_cast|"
106 L"return|"
107 L"sealed|"
108 L"short|"
109 L"signed|"
110 L"sizeof|"
111 L"static_assert|"
112 L"static_cast|"
113 L"static|"
114 L"struct|"
115 L"switch|"
116 L"template|"
117 L"this|"
118 L"__thiscall|"
119 L"throw|"
120 L"true|"
121 L"try|"
122 L"typedef|"
123 L"typeid|"
124 L"typeid|"
125 L"typename|"
126 L"union|"
127 L"unsigned|"
128 L"using|"
129 L"virtual|"
130 L"void|"
131 L"volatile|"
132 L"while";
使用GacUI为文本框着色已经变得如此简单。
posted @
2012-05-17 09:03 陈梓瀚(vczh) 阅读(2180) |
评论 (2) |
编辑 收藏
摘要: GacUI的高亮Demo做了一半。现在的进度是,可以手写着色器的状态转换函数,但是自动从正则表达式产生着色器的状态转换函数部分还没有集成进GacUI。这篇博客还是照旧,看图、看Demo代码,说着色原理。 这次的Demo要做一个可以动态切换着色器的小程序,里面包含INI、XML和C++三种着色器。现在只实现了INI一中,手写的...
阅读全文
posted @
2012-05-11 08:58 陈梓瀚(vczh) 阅读(3235) |
评论 (1) |
编辑 收藏
摘要: GacUI添加了一个新的Demo。这个Demo用了几个按钮(之所以不用菜单是因为不想让Demo一下子包含太多新东西)来实现剪贴板操作、只读控制和行跳转等功能。在剪贴板里面的内容是文字的时候,Paste按钮会被Enable。这个过程是自动的,也就是说,你在画图里面复制了一个图片,这个按钮也会变灰。Cut和Copy按钮仅在文本框有文字被选中的时候可用,因此相应了文...
阅读全文
posted @
2012-05-05 02:37 陈梓瀚(vczh) 阅读(5280) |
评论 (5) |
编辑 收藏
GacUI新增了一个Demo。这里模拟了一个简单到过头了的编辑程序。界面是一个标签页,第一页里面只有一个按钮:Add Page。点中了他之后,其它页包含一个用来关掉自己的按钮,和一个多行的文本框。
这个Demo要展示的其中一个问题是,在按下关闭按钮的时候,由于那个Page会被移除并删除,会导致按钮自己也被删除。但是事件发生过后,实际上还有很多事情要做的。所以这里展示了如何使用GacUI进行“延迟执行”,在事件结束之后再删除自己。为了方便,这个Demo使用了C++11(但是库的实现并不依赖与C++11)。先上图:
然后我们来看代码:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
class TextBoxPage : public GuiTabPage
{
private:
static int pageCounter;
GuiButton* closeButton;
GuiMultilineTextBox* textBox;
void closeButton_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// deleteing the tab page will also delete the button, because the button is in the page
// when an event is processing, the button is not going to be deleted
// because there are many works to do after this event
// and maybe someone has already added another event handler to this button
// so it use GetApplication()->InvokeInMainThread to send a function to the queue
// so that this function will be executed after this input message (an input message raises multiple events)
// to the user, this page is closed after cliking this button
GetApplication()->InvokeInMainThread([this]()
{
// remove the page and delete it
this->GetOwnerTab()->RemovePage(this);
delete this;
});
}
void OnPageContainerReady(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// create a table to place a button and a text box
GuiTableComposition* table=new GuiTableComposition;
table->SetRowsAndColumns(2, 1);
table->SetRowOption(0, GuiCellOption::MinSizeOption());
table->SetRowOption(1, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(0, GuiCellOption::PercentageOption(1.0));
table->SetAlignmentToParent(Margin(0, 0, 0, 0));
table->SetCellPadding(2);
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 0, 1, 1);
closeButton=g::NewButton();
closeButton->SetText(L"Close Me!");
closeButton->Clicked.AttachMethod(this, &TextBoxPage::closeButton_Clicked);
cell->AddChild(closeButton->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(1, 0, 1, 1);
textBox=g::NewMultilineTextBox();
textBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
textBox->SetText(L"You can input several lines of text here.\r\nThis is a multiple line text box.");
cell->AddChild(textBox->GetBoundsComposition());
}
this->GetContainer()->GetContainerComposition()->AddChild(table);
}
public:
TextBoxPage()
:closeButton(0)
,textBox(0)
{
PageContainerReady.AttachMethod(this, &TextBoxPage::OnPageContainerReady);
this->SetText(L"Page "+itow(++pageCounter));
}
~TextBoxPage()
{
}
};
int TextBoxPage::pageCounter=0;
class TextBoxPageWindow : public GuiWindow
{
private:
GuiTab* tabControl;
GuiTabPage* controlPanelPage;
GuiButton* buttonAddPage;
void buttonAddPage_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
// when the button is clicked, it creates a new TextBoxPage and adds it to the tab control
TextBoxPage* page=new TextBoxPage;
tabControl->CreatePage(page);
tabControl->SetSelectedPage(page);
}
public:
TextBoxPageWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.Tab.TextBoxPage");
this->GetBoundsComposition()->SetPreferredMinSize(Size(640, 480));
// create a tab control
tabControl=g::NewTab();
tabControl->GetBoundsComposition()->SetAlignmentToParent(Margin(2, 2, 2, 2));
this->AddChild(tabControl);
// the first page is a control panel
controlPanelPage=tabControl->CreatePage();
controlPanelPage->SetText(L"Control Panel");
// add a button to the control panel
buttonAddPage=g::NewButton();
buttonAddPage->SetText(L"Add a tab page");
buttonAddPage->Clicked.AttachMethod(this, &TextBoxPageWindow::buttonAddPage_Clicked);
controlPanelPage->GetContainer()->GetContainerComposition()->SetInternalMargin(Margin(2, 2, 2, 2));
controlPanelPage->GetContainer()->AddChild(buttonAddPage);
this->ForceCalculateSizeImmediately();
this->MoveToScreenCenter();
}
~TextBoxPageWindow()
{
}
};
void GuiMain()
{
GuiWindow* window=new TextBoxPageWindow();
GetApplication()->Run(window);
delete window;
}
那一大段的注释,就是在讲延迟执行的事情。看过C++11的人都知道,lambda expression实际上就是一个functor。在旧C++里面,调用InvokeInMainThread的时候,要么可以传一个void(*)(void*)和void*,要么可以传一个带operator()()的struct。在新C++里面,直接把lambda expression写在里面就好了。
如果不使用延迟执行,在事件发生的时候把自己删掉,会导致Access Violation的发生,因为接下来要访问的对象被你删掉了。如果使用延迟执行,就可以在input message处理完之后,执行删除的代码。这样一切都是好的。
下一个Demo就是关于文本框的操作,再下一个Demo是关于如何做用来显示代码的高亮文本框的事情。敬请期待,啊哈哈哈。
posted @
2012-04-30 23:28 陈梓瀚(vczh) 阅读(2014) |
评论 (2) |
编辑 收藏
GacUI发布了一个新的Demo。这个Demo是关于多选框和单选框的。跟Windows一样,直接创建出来的单选框其实是不会互斥的,除非你把他们放进同一个group里面。界面是左右各一个group box,使用table来保证两边的尺寸都一样大。每一个group box里面放三个按钮,而且每一个group box的最小尺寸都取决于两边所有6按钮中最长的那个按钮。每一边的三个按钮使用stack来排列成像一个列表一样。左边是多选框,右边是单选框。现在先上图:
第一张是刚打开的时候,窗口的尺寸自动变化到能显示所有内容的最小的尺寸。尽管因为文字的关系,左边的按钮比右边的短,但是table可以控制两个group box一样大,并且共享最小尺寸。
然后改变窗口的尺寸,按钮始终靠左上角,两个group box则保持一样大。
大家已经看了前面的三个demo,所以有些东西其实已经不需要重复解释了。先上代码:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
class CheckAndRadioWindow : public GuiWindow
{
private:
GuiCellComposition* CreateButtons(const WString& groupName, const WString& buttonName, bool checkBox, GuiSelectableButton::GroupController* groupController)
{
GuiCellComposition* cell=new GuiCellComposition;
GuiControl* groupBox=g::NewGroupBox();
groupBox->GetBoundsComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
groupBox->GetContainerComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
// all child controls should at least 10 pixels away from the group box
groupBox->GetContainerComposition()->SetInternalMargin(Margin(10, 10, 10, 10));
// dock the group box to fill the cell
groupBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
groupBox->SetText(groupName);
// add the button to the cell
cell->AddChild(groupBox->GetBoundsComposition());
// create a stack to layout the 3 buttons from top to bottom shown like a list
GuiStackComposition* stack=new GuiStackComposition;
stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
stack->SetDirection(GuiStackComposition::Vertical);
stack->SetAlignmentToParent(Margin(0, 0, 0, 0));
stack->SetPadding(6);
groupBox->GetContainerComposition()->AddChild(stack);
// create buttons
for(int i=0;i<3;i++)
{
GuiSelectableButton* button=checkBox?g::NewCheckBox():g::NewRadioButton();
button->SetText(buttonName+itow(i+1));
button->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
if(groupController)
{
button->SetGroupController(groupController);
}
GuiStackItemComposition* stackItem=new GuiStackItemComposition;
stack->AddChild(stackItem);
stackItem->AddChild(button->GetBoundsComposition());
}
return cell;
}
public:
CheckAndRadioWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.Button.CheckAndRadio");
// limit the size that the window should always show the whole content without cliping it
this->GetContainerComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
// create a table to layout the 2 group boxes
GuiTableComposition* table=new GuiTableComposition;
// make the table to have 2 rows
table->SetRowsAndColumns(1, 2);
table->SetRowOption(0, GuiCellOption::MinSizeOption());
table->SetColumnOption(0, GuiCellOption::PercentageOption(0.5));
table->SetColumnOption(1, GuiCellOption::PercentageOption(0.5));
// dock the table to fill the window
table->SetAlignmentToParent(Margin(4, 4, 4, 4));
table->SetCellPadding(6);
// add the table to the window;
this->GetContainerComposition()->AddChild(table);
// add group box for check boxes
{
GuiCellComposition* cell=CreateButtons(L"Check Boxes", L"This is a check box ", true, 0);
table->AddChild(cell);
// this cell is the left cell
cell->SetSite(0, 0, 1, 1);
}
// add group box for radio buttons
{
// create a group controller to group those radio buttons together
// so that select a radio button will unselect the previous one automatically
GuiSelectableButton::GroupController* groupController=new GuiSelectableButton::MutexGroupController;
this->AddComponent(groupController);
GuiCellComposition* cell=CreateButtons(L"Radio buttons", L"This is a radio button ", false, groupController);
table->AddChild(cell);
// this cell is the right cell
cell->SetSite(0, 1, 1, 1);
}
// 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();
}
~CheckAndRadioWindow()
{
}
};
void GuiMain()
{
GuiWindow* window=new CheckAndRadioWindow();
GetApplication()->Run(window);
delete window;
}
需要关心的就是第二次调用CreateButtons函数,用来构造单选按钮的时候,穿进去的最后一个参数。GuiSelectableButton::GroupController类是一个虚类,用来控制选中状况。而预定义的MutexGroupController则可以控制连接到的所有GuiSelectionButton并保证他们互斥。如果需要更加复杂的情况,譬如说“最多只能选中N个按钮”这样的,则自己集成一个group controller就可以了。在创建了一个group controller,要调用GuiWindow::AddComponent保持他的生命周期,然后使用GuiSelectableButton::SetGroupController来帮顶一个按钮和一个group controller。
这个demo就介绍到这里了,下一个将是关于tab控件和文本框的demo。
posted @
2012-04-27 06:02 陈梓瀚(vczh) 阅读(2244) |
评论 (25) |
编辑 收藏
今天为
GacUI写了一个新的Demo,展示了一些可以自动排版的按钮。主要的设想就是在窗口上放一个表格,分成两行两列。上面的按钮占满一整行,下面两个单元格放两个按钮。然后就可以设置每个行和列占整个表格的比例,在这个Demo里面都设置成50%。这样每当窗口缩放的时候,按钮的位置也会随之重新排版。然后设置表格充满整个窗口,这样窗口的最小值就会被表格的内容所限定,这样试图把窗口缩小的时候,就会有一个最小的尺寸限制着,至始至终保证所有的东西都可以显示出来,不会因为窗口太小而只显示一半。按钮也是同样,可以设置它必须显示所有的文字。所有的过程一旦配置好之后,计算尺寸的时候所有的操作都会自动做,程序员不需要为窗口的Resize事件写任何代码。
下面先放图。
第一个图是窗口刚刚打开的时候的样子。因为Demo里面没有设置窗口的尺寸,所以一上来就自动变成了最小的尺寸——并且刚好可以显示所有的内容。
第二个图是窗口放大之后的样子。Disable按钮被按下了,所以上面的按钮就变灰。
这个Demo使用了Direct2D渲染器,所有的绘制过程都十分高速。而且表格的尺寸计算也是经过优化的,在拖放窗口的时候十分流畅。事实上按钮的渐变啊、边框啊、文字等等也是借助表格排版的。由于尺寸计算过于复杂,除了表格之外整个框架都不保存控件的尺寸,所有的东西都在需要的时候——譬如说渲染的时候,譬如说计算鼠标点中的位置——的那一刻才开始算。因此无论是鼠标滑过,或者是窗口拖放,都拼命地执行很多虚函数。可见C++的虚函数的性能之高,几乎永远都不会成为程序的瓶颈。下面来看代码:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
class EnableDisableWindow : public GuiWindow
{
private:
GuiButton* buttonTarget;
GuiButton* buttonEnable;
GuiButton* buttonDisable;
void buttonEnable_OnClick(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
buttonTarget->SetEnabled(true);
}
void buttonDisable_OnClick(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
buttonTarget->SetEnabled(false);
}
public:
EnableDisableWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.Button.EnableDisable");
// limit the size that the window should always show the whole content without cliping it
this->GetContainerComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
// create a table to layout the 3 buttons
GuiTableComposition* table=new GuiTableComposition;
// make the table to have 2 rows
table->SetRowsAndColumns(2, 2);
table->SetRowOption(0, GuiCellOption::PercentageOption(0.5));
table->SetRowOption(1, GuiCellOption::PercentageOption(0.5));
table->SetColumnOption(0, GuiCellOption::PercentageOption(0.5));
table->SetColumnOption(1, GuiCellOption::PercentageOption(0.5));
// dock the table to fill the window
table->SetAlignmentToParent(Margin(10, 10, 10, 10));
// add the table to the window;
this->GetContainerComposition()->AddChild(table);
// add the target button
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
// this cell is the top cell
cell->SetSite(0, 0, 1, 2);
buttonTarget=g::NewButton();
buttonTarget->SetText(L"Enable or disable me using the buttons below!");
// ensure that the buttonTarget display the whole text
buttonTarget->GetBoundsComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
// dock the button to fill the cell
buttonTarget->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 3));
// add the button to the cell
cell->AddChild(buttonTarget->GetBoundsComposition());
}
// add the enable button
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
// this cell is the bottom left cell
cell->SetSite(1, 0, 1, 1);
buttonEnable=g::NewButton();
buttonEnable->SetText(L"Enable");
buttonEnable->GetBoundsComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
buttonEnable->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 3, 3, 0));
buttonEnable->Clicked.AttachMethod(this, &EnableDisableWindow::buttonEnable_OnClick);
cell->AddChild(buttonEnable->GetBoundsComposition());
}
// add the disable button
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
// this cell is the bottom right cell
cell->SetSite(1, 1, 1, 1);
buttonDisable=g::NewButton();
buttonDisable->SetText(L"Disable");
buttonDisable->GetBoundsComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
buttonDisable->GetBoundsComposition()->SetAlignmentToParent(Margin(3, 3, 0, 0));
buttonDisable->Clicked.AttachMethod(this, &EnableDisableWindow::buttonDisable_OnClick);
cell->AddChild(buttonDisable->GetBoundsComposition());
}
// change the button font
{
FontProperties font;
font=buttonTarget->GetFont();
font.size=20;
buttonTarget->SetFont(font);
buttonEnable->SetFont(font);
buttonDisable->SetFont(font);
}
// 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()
table->UpdateCellBounds();
// update the size
this->SetBounds(Rect());
// move to the screen center
this->MoveToScreenCenter();
}
~EnableDisableWindow()
{
}
};
void GuiMain()
{
GuiWindow* window=new EnableDisableWindow();
GetApplication()->Run(window);
delete window;
}
代码里面充满了注释,而且主要的内容也在上面介绍了,在这里我就不罗嗦了。所有的代码都可以在
http://gac.codeplex.com中,下载最新的代码,然后在Libraries\GacUI\GacUIDemo\GacUIDemo.sln下面找到。
posted @
2012-04-25 02:46 陈梓瀚(vczh) 阅读(2332) |
评论 (5) |
编辑 收藏
今天我给
GacUI添加了一个新Demo。我发现写Demo也是一个测试的过程,可以用来检验类库提供的API是否够完整。前面这两个Demo都促使我往类库里面加入了新的函数。这次的Demo是用Label控件来模仿超链接。下载最新代码之后,可以在“Libraries\GacUI\GacUIDemo\GacUIDemo.sln”下面找到最新的Demo代码。
为了模仿超链接,我们要做两件事情。第一件事情就是鼠标悬浮在Label上的时候需要显示出手的光标图,第二件事情就是在鼠标进入Label的时候显示下划线,离开的时候去掉下划线。因此我们需要监听三个事件,分别是MouseEnter,MouseLeave和LeftButtonDown。下面是Demo的图:
上图:鼠标在Label外。下图:鼠标在Label内。单击Label的时候会打开浏览器。
代码如下:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
class HyperlinkWindow : public GuiWindow
{
private:
GuiLabel* labelHyperlink;
void labelHyperlink_OnMouseEnter(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
FontProperties font=labelHyperlink->GetFont();
font.underline=true;
labelHyperlink->SetFont(font);
}
void labelHyperlink_OnMouseLeave(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
FontProperties font=labelHyperlink->GetFont();
font.underline=false;
labelHyperlink->SetFont(font);
}
void labelHyperlink_OnLeftButtonDown(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments)
{
ShellExecute(NULL, L"OPEN", L"http://www.cppblog.com/vczh", NULL, NULL, SW_SHOWNORMAL);
}
public:
HyperlinkWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
SetText(L"Controls.Label.Hyperlink");
SetClientSize(Size(300, 200));
MoveToScreenCenter();
labelHyperlink=g::NewLabel();
labelHyperlink->SetText(L"http://www.cppblog.com/vczh");
labelHyperlink->SetTextColor(Color(0, 0, 255));
{
FontProperties font;
font.fontFamily=L"Segoe UI";
font.size=18;
font.antialias=true;
labelHyperlink->SetFont(font);
}
{
INativeCursor* hand=GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Hand);
labelHyperlink->GetBoundsComposition()->SetAssociatedCursor(hand);
}
labelHyperlink->GetEventReceiver()->mouseEnter.AttachMethod(this, &HyperlinkWindow::labelHyperlink_OnMouseEnter);
labelHyperlink->GetEventReceiver()->mouseLeave.AttachMethod(this, &HyperlinkWindow::labelHyperlink_OnMouseLeave);
labelHyperlink->GetEventReceiver()->leftButtonDown.AttachMethod(this, &HyperlinkWindow::labelHyperlink_OnLeftButtonDown);
AddChild(labelHyperlink);
}
~HyperlinkWindow()
{
}
};
void GuiMain()
{
GuiWindow* window=new HyperlinkWindow();
GetApplication()->Run(window);
delete window;
}
这里展示的主要是监听事件的方法。在使用control->GetEventReceiver()->event的时候,可以使用Attach、AttachMethod、AttachFunction和AttachLambda。AttachLambda传入一个functor,在C++11里面可以直接使用lambda表达式。在这里使用AttachMethod给一个事件绑定类成员函数。C++运行类成员函数的时候,不仅需要参数,还需要一个this对象,所以AttachMethod有两个参数,使用方法在Demo里面已经展现出来了。
在这里还引入了GetCurrentController函数。GetCurrentController返回的INativeController对象抽象了所有需要的操作系统的功能,其中获得一个光标的对象就封装在了ResourceService里面。INativeController还包含了很多其他的Service,这个留在以后的Demo展示。
posted @
2012-04-24 02:37 陈梓瀚(vczh) 阅读(2107) |
评论 (8) |
编辑 收藏
今晚终于把
GacUI所有该加上的XML注释都加上了,开始做起了Demo。需要做的Demo大概有几十个,除了每个控件要那么几个以外,还有一些其他功能,譬如换皮肤换风格啊,使用不同的渲染器啊,移植到Direct3D上什么的,好多啊,即将写到手软。今天写了第一个Demo,是一个helloworld程序。创建一个GacUI程序是一件很容易的事情,首先下载
GacUI的最新代码(在有了Release之后会有更好的文件组织和html文档),然后将“Libraries\GacUI\Public\Source”下面的5个文件添加进建好的Visual C++的Windows工程里面,然后开始写main函数:
#include "..\..\Public\Source\GacUIIncludes.h"
#include <Windows.h>
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
void GuiMain()
{
GuiWindow* window=g::NewWindow();
window->SetText(L"Hello, world!");
window->SetClientSize(Size(640, 480));
window->MoveToScreenCenter();
GuiControl* label=g::NewLabel();
label->SetText(L"Welcome to GacUI Library!");
{
FontProperties font;
font.fontFamily=L"Segoe UI";
font.size=40;
font.antialias=true;
label->SetFont(font);
}
window->AddChild(label);
GetApplication()->Run(window);
delete window;
}
Main函数里面需要选择渲染器,这里选的是Direct2D。如果要选择GDI,则调用SetupWindowsGDIRenderer。至于void GuiMain(void)函数则是类库自己的要求,一定要有那么一个,因为SetupWindowsDirect2DRenderer(或GDI)最终会进行一大段初始化工作之后调用一个声明了却没有实现的void GuiMain(void)函数,所以一个合法的GacUI工程需要包含void GuiMain(void)的实现,也就是上面这样。
然后接下来的就是普通的工作了,创建一个GuiWindow,加点东西,Run一下当主窗口。当主窗口被关掉的时候Run函数就会结束,这个时候进行一下必要的清理,程序就可以结束了。运行之后就是下面这个样子:
至此HelloWorld的Demo就结束了。
posted @
2012-04-23 07:37 陈梓瀚(vczh) 阅读(2461) |
评论 (12) |
编辑 收藏