wpf CollectionViewSource与ListBox的折叠、分组显示,及输入关键字 Filter的筛选
在wpf中虽然ObservableCollection<T>作为ListBox的Itemsource,很好,很强大!但是CollectionViewSource与ListBox才是天作之合!
wpf中ListBox支持分组显示,CollectionViewSource.GroupDescriptions为其实现了分组。废话不多说,下面上ListBox分组显示的Demo代码:
XAML:
<Window x:Class="WpfListGroup.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="MainWindow" Height="450" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="employeeCollectionViewSource" Filter="employeeCollectionViewSource_Filter">
<CollectionViewSource.SortDescriptions>
<!--排序描述-->
<scm:SortDescription PropertyName="Num"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<!--分组描述-->
<PropertyGroupDescription PropertyName="Title"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource> <Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#F3F3F3" Offset="0"/>
<GradientStop Color="#EBEBEB" Offset="0.5"/>
<GradientStop Color="#DDDDDD" Offset="0.5"/>
<GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Style x:Key="nocheckedButtonStyle" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Width="29.72">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="contentPresenter">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/>
</DoubleAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse">
<EasingColorKeyFrame KeyTime="0" Value="#FF2CA50B"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="Focused"/>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidFocused"/>
<VisualState x:Name="InvalidUnfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="ellipse" Fill="#FF75AB80" Margin="0" Stroke="{x:Null}" VerticalAlignment="Stretch" Width="16" Height="16"/>
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" SnapsToDevicePixels="true" >
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RenderTransformOrigin="0.5,0.5">
<ContentPresenter.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</ContentPresenter.RenderTransform>
</ContentPresenter>
</Microsoft_Windows_Themes:ButtonChrome>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false"/>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Row="0" LastChildFill="True" >
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Left" Text="搜索:"/>
<Button Content=" × " VerticalAlignment="Center" DockPanel.Dock="Right"
Background="White" BorderBrush="{x:Null}" Margin="0"
Style="{DynamicResource nocheckedButtonStyle}"
HorizontalAlignment="Right"
FontFamily="Forte" Foreground="White" ToolTip="清空"
Click="btnClearKeyword_Click"/>
<TextBox x:Name="txtEmployeeKeyword" VerticalAlignment="Center" TextChanged="txtEmployeeKeyword_TextChanged" />
</DockPanel>
<ScrollViewer x:Name="scv1" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ListBox x:Name="lbx1" SelectionMode="Extended" ItemsSource="{Binding Source={StaticResource ResourceKey=employeeCollectionViewSource}}">
<!--分组样式-->
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,10,0">
<!--分组的组名-->
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<!--该分组元素(员工)的总和数-->
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount, StringFormat=(共{0}条)}"/>
</StackPanel>
<Line Grid.Column="1" SnapsToDevicePixels="true" X1="0" X2="1" Stretch="Fill" StrokeThickness="1"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<!--右键菜单-->
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Show" Click="MenuItem_Click"/>
</ContextMenu>
</ListBox.ContextMenu>
<!--“没有”绑定ListBox.ItemTemplate,是因为在Employee类重写了ToString()方法-->
</ListBox>
</ScrollViewer>
<ScrollViewer x:Name="scv2" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Visibility="Collapsed">
<ListBox Name="lbx2" ItemsSource="{Binding Source={StaticResource employeeCollectionViewSource}}" SelectionMode="Extended"> <!--按Ctrl键可多选-->
<ListBox.ContextMenu>
<ContextMenu>
<!--右键菜单-->
<MenuItem Header="Show" Click="MenuItem_Click"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</ScrollViewer>
</Grid>
</Window>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel; namespace WpfListGroup
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#region 基础数据(员工集合)
ObservableCollection<Employee> employeeList = new ObservableCollection<Employee>
{
new Employee{EmployeeNum="",EmployeeName="张三",Sex="男",Title="副经理"},
new Employee{EmployeeNum="",EmployeeName="春丽",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="王五",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="赵阳",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="孙迪",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="李玥玥",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="钱哆哆",Sex="男",Title="副经理"},
new Employee{EmployeeNum="",EmployeeName="周畅",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="郑超",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="王思聪",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="李文",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="周琪妹",Sex="女",Title="秘书"}
};
#endregion CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource");
employeeCvs.Source = employeeList;
}
/// <summary>
/// 右键菜单、按住Ctrl键可多选
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
//获取关键字
string keyword = txtEmployeeKeyword.Text.Trim();
if (string.IsNullOrEmpty(keyword))//如果没有关键字
{
if (lbx1.SelectedItem != null)//判断lbx1有没有选中项
{
foreach (var item in lbx1.SelectedItems)
{
Employee employee = item as Employee;
string msg = string.Format("姓名:{0},工号:{1},性别:{2},职位:{3}", employee.EmployeeName, employee.EmployeeNum, employee.Sex, employee.Title);
MessageBox.Show(msg);
}
}
}
else
{
if (lbx2.SelectedItem != null)//有关键字的话,显示lbx2
{
foreach (var item in lbx2.SelectedItems)//判断lbx2有没有选中项
{
Employee employee = item as Employee;
string msg = string.Format("姓名:{0},工号:{1},性别:{2},职位:{3}", employee.EmployeeName, employee.EmployeeNum, employee.Sex, employee.Title);
MessageBox.Show(msg);
}
}
}
} /// <summary>
/// 关键字改变时触发
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtEmployeeKeyword_TextChanged(object sender, TextChangedEventArgs e)
{
string keyword = txtEmployeeKeyword.Text.Trim();
if (string.IsNullOrEmpty(keyword))//无关键字,显示scv1下的listbox(有分组)
{ scv1.Visibility = Visibility.Visible;
scv2.Visibility = Visibility.Collapsed;
}
else//有关键字,显示scv2下的listbox(无分组)
{
scv1.Visibility = Visibility.Collapsed;
scv2.Visibility = Visibility.Visible;
}
CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource");
employeeCvs.View.Refresh();//刷新View
}
/// <summary>
/// 根据关键字(工号或姓名)筛选员工
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void employeeCollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string keyword = txtEmployeeKeyword.Text.Trim();
Employee employee = e.Item as Employee;
if (employee != null)
{
if (string.IsNullOrEmpty(keyword))//无关键字,直接Accept
{
e.Accepted = true;
}
else
{
//有关键字、筛选员工号或姓名中包含关键字的员工
e.Accepted = employee.EmployeeNum.Contains(keyword) || employee.EmployeeName.Contains(keyword);
}
}
}
/// <summary>
/// 清空关键字
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnClearKeyword_Click(object sender, RoutedEventArgs e)
{
this.txtEmployeeKeyword.Clear();
}
} public class Employee:INotifyPropertyChanged
{
#region 实现更改通知
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion /// <summary>
/// 重载ToString()方法
/// </summary>
/// <returns></returns>
public override string ToString()
{
return this.EmployeeNum + " " + this.EmployeeName;
} private string title;
/// <summary>
/// 职位
/// </summary>
public string Title
{
get { return title; }
set { title = value;
RaisePropertyChanged("Title");
}
} private string employeeName;
/// <summary>
/// 姓名
/// </summary>
public string EmployeeName
{
get { return employeeName; }
set { employeeName = value;
RaisePropertyChanged("EmployeeName");
}
}
private string employeeNum;
/// <summary>
/// 工号
/// </summary>
public string EmployeeNum
{
get { return employeeNum; }
set { employeeNum = value;
RaisePropertyChanged("EmployeeNum");
}
}
private string sex;
/// <summary>
/// 性别
/// </summary>
public string Sex
{
get { return sex; }
set { sex = value;
RaisePropertyChanged("Sex");
}
} }
}
运行效果:
右键菜单点击“Show” 弹出选中项的员工信息:
输入关键字"同步"筛选模糊查询员工:
点击清空按钮清空关键字,“恢复”分组数据:
总结核心xaml:
①资源CollectionViewSource, CollectionViewSource.GroupDescriptions:分组描述(依据),CollectionViewSource.SortDescriptions:分组排序(描述)
在资源中:
<CollectionViewSource x:Key="employeeCollectionViewSource" Filter="employeeCollectionViewSource_Filter">
<CollectionViewSource.SortDescriptions>
<!--排序描述-->
<scm:SortDescription PropertyName="Num"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<!--分组描述-->
<PropertyGroupDescription PropertyName="Title"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
②绑定到ListBox的Itemsource上,设置分组样式,使用Expander控件使分组可以折叠:
<ScrollViewer x:Name="scv1" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ListBox x:Name="lbx1" SelectionMode="Extended" ItemsSource="{Binding Source={StaticResource ResourceKey=employeeCollectionViewSource}}">
<!--分组样式-->
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,10,0">
<!--分组的组名-->
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<!--该分组元素(员工)的总和数-->
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount, StringFormat=(共{0}条)}"/>
</StackPanel>
<Line Grid.Column="1" SnapsToDevicePixels="true" X1="0" X2="1" Stretch="Fill" StrokeThickness="1"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<!--右键菜单-->
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Show" Click="MenuItem_Click"/>
</ContextMenu>
</ListBox.ContextMenu>
<!--“没有”绑定ListBox.ItemTemplate,是因为在Employee类重写了ToString()方法-->
</ListBox>
</ScrollViewer>
总结核心C#:
①CollectionViewSource的筛选器Filter的方法:
/// <summary>
/// 根据关键字(工号或姓名)筛选员工
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void employeeCollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string keyword = txtEmployeeKeyword.Text.Trim();
Employee employee = e.Item as Employee;
if (employee != null)
{
if (string.IsNullOrEmpty(keyword))//无关键字,直接Accept
{
e.Accepted = true;
}
else
{
//有关键字、筛选员工号或姓名中包含关键字的员工
e.Accepted = employee.EmployeeNum.Contains(keyword) || employee.EmployeeName.Contains(keyword);
}
}
}
②关键字文本框的文本发生改变时触发的事件:
/// <summary>
/// 关键字改变时触发
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtEmployeeKeyword_TextChanged(object sender, TextChangedEventArgs e)
{
string keyword = txtEmployeeKeyword.Text.Trim();
if (string.IsNullOrEmpty(keyword))//无关键字,显示scv1下的listbox(有分组)
{ scv1.Visibility = Visibility.Visible;
scv2.Visibility = Visibility.Collapsed;
}
else//有关键字,显示scv2下的listbox(无分组)
{
scv1.Visibility = Visibility.Collapsed;
scv2.Visibility = Visibility.Visible;
}
CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource");
employeeCvs.View.Refresh();//刷新View
}
总结:以上就是ListBox的分组、折叠、筛选显示的Demo。日积月累,水滴石穿!
wpf CollectionViewSource与ListBox的折叠、分组显示,及输入关键字 Filter的筛选的更多相关文章
- WPF中的ListBox实现按块显示元素的方法
本文实例讲述了WPF中的ListBox实现按块显示元素的方法.分享给大家供大家参考,具体如下: 注意:需要设置ListBox的属性 ScrollViewer.HorizontalScrollBarVi ...
- WPF的项目,ListBox 纵向滚动条不显示
最近在做WPF的项目,ListBox 纵向滚动条不显示,但是鼠标滚轮可以在ListBox中使用,但是必须要出现纵向滚动条. 索性就直接在listBox外面包裹一个ScrollViewer. Scro ...
- 重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示
原文:重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示 [源码下载] 重新想象 Windows 8 Store Ap ...
- Group GridView:用于.Net的分组显示的GridView
我的项目需要一个可以分组显示的GridView,我不会写,上网找了一圈,最终在国外的网站上找到的这个,比较符合我的要求,但它的分页得重写,它写了能分页,但我发现它的分页功能事实上并没有实现,也不知道是 ...
- Linq DataTable Group By 分组显示人员明细
实现功能: 多个字段分组源码样例: 原始数据: 分组后的输出结果: 源代码: public static void PrintPersons() { //准备数据 DataTable dt ...
- WPF绑定的ListBox获取ListBoxItem及GoToState应用
现公司项目中需要制作一个扇形菜单,菜单项是用ListBox重写Style实现的,其数据是绑定的.菜单的每一项都有Normal,MouseOver和Selected三种状态,这三种状态当然可以通过鼠标移 ...
- MySQL数据中分级分组显示数据
前面已经有了SqlServer数据分级分组显示数据了.今天又来做一个MySQL数据库中的分级分组显示,SqlServer中用到了递归,这里为了简单就直接把根的数据显示为0 ,而不用递归了. 在MySQ ...
- layout折叠后显示标题
Easyui的layout折叠后显示怎样可以显示标题 //在layout的panle全局配置中,增加一个onCollapse处理title$.extend($.fn.layout.paneldefau ...
- 在WPF中实现图片一边下载一边显示
原文 在WPF中实现图片一边下载一边显示 当我们上网查看一个较大的图片时,浏览器能一边下载一边显示,这样用户体验是比较好的,但在WPF程序中,当我们通过如下方式显示一幅图片时: img.Source ...
随机推荐
- javascript中关于继承的理解
首先,你要理解在javascript中,每当一个新函数创建时,都会生成一个prototype属性,我们管它叫做原型对象.看一个例子: function foo(){ this.name='qiangq ...
- 2、创建File类对象
既然是内置类,那么我们创建对象时自然要看它封装好的构造函数咯,由下图的4中构造函数我们可知有4种办法来创建File对象 具体代码如下 public class Demo { public static ...
- 实时计算storm流程架构总结
hadoop一般用在离线的分析计算中,而storm区别于hadoop,用在实时的流式计算中,被广泛用来进行实时日志处理.实时统计.实时风控等场景,当然也可以用在对数据进行实时初步的加工,存储到分布式数 ...
- Spring Security(09)——Filter
目录 1.1 Filter顺序 1.2 添加Filter到FilterChain 1.3 DelegatingFilterProxy 1.4 FilterChainPr ...
- [SOJ] connect components in undirected graph
题目描述: 输入一个简单无向图,求出图中连通块的数目 输入: 输入的第一行包含两个整数n和m,n是图的顶点数,m是边数.1<=n<=1000,0<=m<=10000. 以下m行 ...
- 朱丽叶—Cuda+OSG
#include <cuda_runtime.h> #include <osg/Image> ; typedef struct cuComplex { float r; flo ...
- delphi怎么实现全选的功能
1. SelectAll 可以实现全选功能 Delphi/Pascal code edit1.SelectAll; // Delphi/Pascal code RichEdit1.SelStart:= ...
- 多线程---优先级&yield方法
优先级只有10级,1-10.最高10(java中用Thread.MAX_PRIORITY),最低1,中间级5. 设置优先级的方法是 线程对象.setPriority(5): yield : 暂停(不是 ...
- Erlang OTP gen_event
转自:http://www.myexception.cn/program/1569725.html Erlang OTP gen_event (0) 原英文文档:http://www.erlang.o ...
- erlang四大behaviour之二-gen_fsm
来源:http://www.cnblogs.com/puputu/articles/1701012.html 今天介绍erlang的一个非常重要的behaviour,就是gen_fsm-有限状态机,有 ...