随笔-341  评论-2670  文章-0  trackbacks-0

    趁此机会做个广告,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(53);
        table
->SetCellPadding(3);
        table
->SetAlignmentToParent(Margin(0000));

        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(0051);

            listSource
=g::NewTextList();
            listSource
->GetBoundsComposition()->SetAlignmentToParent(Margin(0000));
            
// 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(0251);

            listDestination
=g::NewTextList();
            listDestination
->GetBoundsComposition()->SetAlignmentToParent(Margin(0000));
            
// 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(1111);

            buttonAdd
=g::NewButton();
            buttonAdd
->SetText(L">>");
            buttonAdd
->GetBoundsComposition()->SetAlignmentToParent(Margin(0000));
            buttonAdd
->GetBoundsComposition()->SetPreferredMinSize(Size(3232));
            buttonAdd
->Clicked.AttachMethod(this&NameSelectorWindow::buttonAdd_Clicked);
            buttonAdd
->SetEnabled(false);
            cell
->AddChild(buttonAdd->GetBoundsComposition());
        }
        {
            GuiCellComposition
* cell=new GuiCellComposition;
            table
->AddChild(cell);
            cell
->SetSite(3111);

            buttonRemove
=g::NewButton();
            buttonRemove
->SetText(L"<<");
            buttonRemove
->GetBoundsComposition()->SetAlignmentToParent(Margin(0000));
            buttonRemove
->GetBoundsComposition()->SetPreferredMinSize(Size(3232));
            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(640480));
        
// 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) 阅读(3582) 评论(12)  编辑 收藏 引用 所属分类: GacUI

评论:
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 01:42 | phoenixbing
我是来支持老大的  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 06:35 | 溪流
linq 能合起来写不?哈~  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 07:29 | 朱峰everettjf
学习。
请问,能否说,gaclib是一个.NET WinForm风格的c++界面库?
GPU accelerated 是针对显示做了优化(相对于使用sdk或mfc、wtl)?  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 09:19 | 陈梓瀚(vczh)
@朱峰everettjf
我觉得,应该是WPF风格的吧。GPU是针对WinForm、MFC、WTL这种传统的界面而言的。  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 09:20 | 陈梓瀚(vczh)
@溪流
能啊,里面不是合了吗,还嵌套了  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-26 18:23 | 朱峰everettjf
@陈梓瀚(vczh)
嗯。向博主学习。  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-27 16:42 | tb
向博主学习  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-27 20:49 | 溪流
@陈梓瀚(vczh)
我是说类sql的写法:)  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-27 23:09 | TianJun
单击列表控件里面的名字有时候会选择到其它名字, 不是我单击的名字。  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-28 01:23 | 陈梓瀚(vczh)
@TianJun
有没有重现方法?然后我去查一查  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-28 01:23 | 陈梓瀚(vczh)
@溪流
没有那种写法,C++搞不定这种……  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-28 03:44 | Scan
在C++中倒是可以弄出a.OrderBy().Select([](){}).ToList()这种语法的linq,借助一个代理类,用.来连接操作,至少在代码补全方面更好用。
我以前倒是也试过在C++11中借助闭包实现linq to object,闭包用于延迟迭代。  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++ 2012-05-29 19:13 | TianJun
@陈梓瀚(vczh)
刚打开程序时不会, 把一些名字移动到右边后, 就会出现单击左边列表控件会选择不对的名字。  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++[未登录] 2012-05-29 19:35 | 陈梓瀚(vczh)
@TianJun
我去看一看  回复  更多评论
  
# re: GacUI Demo:列表控件内容的排序和移动,以及Linq for C++[未登录] 2012-05-29 20:13 | 陈梓瀚(vczh)
@TianJun
refactor的时候犯了点小错误,这个问题已经解决了。感谢提供信息。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理