包氏波动思想

      有了前面介绍的AttachedBehavior,所有控件的任意方法都可以转换为Command了。

      但是,对于列表控件而言,还有一条解决方案,这包括DataGrid、ListBox、ComboBox等等,而对于CheckBox、RadioButton而言,也可以将其视为由多个选项组成的控件,所以,也在本文涉及的范围之内。

      先来看一个例子:

      (一)DataGrid

      一个DataGrid,选择其中的任意一行,都会在GroupBox中显示这一行的详细信息。对,这是Master-Detail技术:

      在传统窗体编程模型中,使用DataGrid的SelectionChanged事件,可以轻松实现这一功能:       

<data:DataGrid SelectionChanged="dataGrid1_SelectionChanged" … >

 

        private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selectStudent 
= e.AddedItems[0as Student;
            tbUserName.Text 
= selectStudent.UserName;
            tbScore.Text 
= selectStudent.Score.ToString();
        }

      clip_image002

      代码下载:WpfApplication7_1.zip

      但如今,我们要使用MVP来实现这个小例子,所以像SelectionChanged这样的事件是不能使用的,怎么办呢?

      仔细观察GridView控件,会发现它有一个SelectedItem属性,当SelectionChanged事件触发后,这个属性会被设置为当前选中的那一行。于是,我们在数据(Model)中,可以建立同名的一个属性,将它们进行绑定。那么,选中不同的行,GridView的SelectedItem就会改变,这一“波动”会导致数据(Model)中的相应属性(这里是SelectedStudent)也跟着变动,我们可以在数据(Model)中捕获到这一“波动”,从而执行一些自己的逻辑。 

    <data:DataGrid ItemsSource="{Binding StudentList}" SelectedItem="{Binding SelectedStudent}" … />


    public class ScoreListViewModel : INotifyPropertyChanged
    
{
        
public ScoreListViewModel()
        
{
            
this.StudentList = StudentService.RetrieveStudentList();
            
this.SelectedStudent = this.StudentList[0];
        }


        
public ScoreListView View getset; }

        
private Student selectedStudent;

        
public Student SelectedStudent
        
{
            
get
            
{
                
return this.selectedStudent;
            }

            
set
            
{
                
if (this.selectedStudent != value)
                
{
                    
this.selectedStudent = value;
                    OnPropertyChanged(
"SelectedStudent");
                }

            }

        }

    }

      代码下载:WpfApplication7_2.zip

      这种设计思想是我在实际编程中自己琢磨出来的,事后在Prism的codeplex论坛上发现之前就已经有人这么实现了。真是英雄所见略同啊!因为这种设计的适用性是很广的,所以我将其总结为——

      包氏波动思想定义:对于列表性控件(DataGrid、ListBox、ComboBox等等),以及像CheckBox、RadioButton这样的带有选项集合的控件,它们都具有类似SelectedItem这样的属性,我们可以在MVP模式中绑定这些属性,以监视选择项的变化。

      接下来的章节我将分别展示这些控件的另类绑定方式,以证实包氏波动思想的伟大价值。


      (二)ListBox

      将上面例子中的DataGrid换为ListBox就可以了,其它地方无需任何改变。 

    <ListBox Margin="12,12,66,144" Name="listBox1" ItemsSource="{Binding StudentList}" SelectedItem="{Binding SelectedStudent}">
        
<ListBox.ItemTemplate>
            
<DataTemplate>
                
<StackPanel Orientation="Horizontal">
                    
<TextBlock Text="{Binding Path=UserName}" Width="40" />
                    
<TextBlock Text="{Binding Path=Score}"/>
                
</StackPanel>
            
</DataTemplate>
        
</ListBox.ItemTemplate>
    
</ListBox>

      代码下载: WpfApplication7_3.zip

      (三)CheckBox

      CheckBox中有一个属性IsChecked,我们只要监视着该属性的波动,就可以了。

      代码下载: WpfApplication7_4.zip


      (四)RadioButton

      这个实现起来比较费事,细节请参见Clingingboy的文章:《WPF and SL RadioButtonList Tip》

      这个土人居然没给出个Demo,我只好自己动手丰衣足食,代码下载: WpfTestRadioButton.zip

      但是我们发现,这个例子太简单了,只是2个性别“男”和“女”的选择,如果用CheckBox也是可以模拟出来的。

      于是,在此基础上,我试着添加了第3性“女博士”,来扩展RadionButton的应用。

      代码下载: WpfTestRadioButton_extension.zip


      (五)ComboBox

      这个控件的波动思想,在WPF中实现起来比较简单。

      代码下载: WpfTestComboBox.zip

      注:这个控件玩起来有时候比较烦,就是当下拉列表中有一行默认空白选项时,我们会在《最复杂的MVVM模式》一文中遇到这一场景。


      (六)总结

      最常用的就是这几种了,也是最典型的几种。其它列表控件的绑定方式均可依样画葫芦,我这里就不多讲了。

      对于列表控件,究竟是使用AttachedBehavior,还是使用“包氏波动思想”(继续陶醉中),我的取舍是后者,只有在不能用波动思想的时候,才会考虑AttachedBehavior。波动思想俨然是基于数据的,这和MVP模式将数据从UI中剥离不谋而合;而AttachedBehavior则是传统Event编程模型的变形,我一直认为它是Event和Command妥协的产物,尤其在描述数据上,是远远不如包氏波动思想的。

      哈哈,老包卖瓜,自卖自夸,不管这种思想是不是姓包,我们都要养成“基于数据编程”的习惯,而不再是“基于控件编程”。


      这是一种境界的提升。

1
1
(请您对文章做出评价)
« 上一篇:玩转INotifyPropertyChanged和ObservableCollection
» 下一篇:Prism研究(for WPF & Silverlight)9.Command批判
posted @ 2009-10-14 00:37 包建强 阅读(2235) 评论(15)  编辑 收藏 网摘 所属分类: Prism

  回复  引用  查看    
#1楼2009-10-14 01:47 | jv9      
包氏波动思想,呵呵,这个名字不错。
  回复  引用    
#2楼2009-10-14 06:35 | 问一下[未注册用户]
我的wpf里怎么没有DataGrid呐?
  回复  引用  查看    
#3楼2009-10-14 07:13 | 青林一霸      
好啊,包子波动!连续两个早上起床我就吃早餐,营养很丰富,很爽口!
  回复  引用  查看    
#4楼2009-10-14 08:46 | Todd Wei      
"波动"何解?
  回复  引用  查看    
#5楼2009-10-14 09:07 | Clingingboy      
那个比较老...

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF.Controls">

<Style TargetType="{x:Type local:ListControl}" BasedOn="{StaticResource {x:Type ListBox}}">

<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid HorizontalAlignment="Left" VerticalAlignment="Top"
Rows="{Binding Path=Rows,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:ListControl}}}"
Columns="{Binding Path=Columns,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:ListControl}}}"></UniformGrid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>

<Style TargetType="{x:Type local:RadioButtonList}" BasedOn="{StaticResource {x:Type ListControl}}">
<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<RadioButton IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}">
<RadioButton.Content>
<ContentPresenter Grid.Column="0" />
</RadioButton.Content>
</RadioButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>


<Style TargetType="{x:Type local:CheckBoxList}" BasedOn="{StaticResource {x:Type local:ListControl}}">

<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}">
<CheckBox.Content>
<ContentPresenter Grid.Column="0" />
</CheckBox.Content>
</CheckBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>

</ResourceDictionary>

  回复  引用  查看    
#6楼2009-10-14 09:08 | 未登陆的包包      
波动,譬如敌动我动,我动敌也动。
又如扔石头到湖面,泛起涟漪无数。所谓一石激起千层浪。

  回复  引用  查看    
#7楼2009-10-14 09:22 | airy      
似乎明白了点什么。
  回复  引用  查看    
#8楼2009-10-14 10:28 | 王德水      
波涛汹涌的波,呵呵,估计老包练健美了
  回复  引用  查看    
#9楼[楼主]2009-10-14 11:24 | 包建强      
@王德水
相亲的时候遇到了75C的波霸,呵呵。

  回复  引用  查看    
#10楼2009-10-14 12:01 | 青林一霸      
上大学的时候,一女生,波极大,外面穿的是衬衣,没有扣扣子,里面是一件小衫,迎面向我们50多人的队伍走来,由于此女走路有点猫步,微风轻拂,此情此境估计用“波动”形容比较贴切。我们当时所有的人都异口同声“哇”,如雷贯耳!已经是十年前的事了,如今记忆犹新。
上初中时,一女生,经常喜欢趴在课桌上,由于其波大,我们男生在想,是不是感觉太重了,人撑不住!这可能可叫“波不动”。呵呵,这事已经快二十年了

  回复  引用  查看    
#11楼2009-10-15 08:32 | Hell's Hand      
这个对于我来说还是很有用的,学习了
谢谢楼主。

  回复  引用  查看    
#12楼2009-10-15 10:31 | boozhidao      
境界有了,谈谈优势
  回复  引用    
#13楼2009-10-24 17:36 | 发放上阿德[未注册用户]
顶顶顶!

打扰了 推荐个ASP.NET群 兼炒股群!

不炒股的朋友勿进啊!

谈股、炒股、玩股、恨股、爱股。对股的爱恨情仇、来发泄吧。

本群 谈股论妞:67221760

  回复  引用  查看    
#14楼2009-11-17 11:14 | Franz      
继续讨伐你!

如果你的业务逻辑真的很关心你的选择了那个,这个是个好方法,我也经常用。

但是啊,如果你只是为了UI的同步展示,何必这样去做呢?Binding里面有个叫ElementName的东西你不会不知道吧。

还“波动”,我晕,你这样的才分还真不少,给你顶一个。

  回复  引用  查看    
#15楼2009-11-17 11:21 | Franz      
“有了前面介绍的AttachedBehavior,所有控件的任意方法都可以转换为Command了。”


注意表达哈,是控件的事件,而不是任意方法。赶快改一改。