GacUI Demo:列表控件内容的排序和移动,以及Linq for C++

GacUI Demo:列表控件内容的排序和移动,以及Linq for C++

    趁此机会做个广告,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进去了,而是直接告诉列表控件一共有多少个项目,然后列表要显示的时候回调一下获得真正的内容。这个功能是很有用的,特别是当内容特别多,我们没办法一下子准备好的时候,可以让界面很快的出来,让用户感觉起来,程序运行的很流畅。

你可能感兴趣的:(GacUI Demo:列表控件内容的排序和移动,以及Linq for C++)