2013年12月19日星期四

Silverlight MVVM 贴近实战(七)

今天我们通过用户管理模块来系统的讲述关于Action,Trigger的使用。首先还是先上一张图,调一下胃口。 这个图中的Grid怎会变成如此摸样,是的,在这里我们用到了一点点3D的效果。首先我们先从前台代码入手。 controls:ChildWindowx:Class= MISInfoManage.UserManageView /*此处省略部分代码*/ xmlns:resource= clr-namespa
  
今天我们通过用户管理模块来系统的讲述关于Action,Trigger的使用。首先还是先上一张图,调一下胃口。
  

这个图中的Grid怎会变成如此摸样,是的,在这里我们用到了一点点3D的效果。首先我们先从前台代码入手。
<controls:ChildWindow x:Class="MISInfoManage.UserManageView" 
           /*此处省略部分代码*/              xmlns:resource=
"clr-namespace:MISInfoManage.Resources" 
 
           xmlns:trigger=
"clr-namespace:MISInfoManage.Trigger" 
           xmlns:i=
"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         /*此处省略部分代码*/   
     <Grid x:Name="LayoutRoot" Margin="2"
        <Grid.RowDefinitions
            <RowDefinition Height="65"/
            <RowDefinition Height="297"/
            <RowDefinition Height="Auto"/
        </Grid.RowDefinitions
        <sdk:DataGrid Grid.Row="0" Grid.Column="0" Height="300" Width="540"   
                         /*此处省略部分代码*/   

            
            <sdk:DataGrid.Projection
                <PlaneProjection RotationX="0" RotationY="10" RotationZ="0"></PlaneProjection
            </sdk:DataGrid.Projection
            <sdk:DataGrid.Effect
                <DropShadowEffect BlurRadius="5" Color="Black" ShadowDepth="8" Opacity="0.8"/
            </sdk:DataGrid.Effect
            <i:Interaction.Triggers
                <i:EventTrigger EventName="SelectionChanged"
                    <trigger:SelectChangeTargetTrigger TargetName="txtUserNo"
                    </trigger:SelectChangeTargetTrigger
                    <trigger:SelectChangeTargetTrigger TargetName="cmbType"
                    </trigger:SelectChangeTargetTrigger
                    <trigger:SelectChangeTargetTrigger TargetName="cmbState"
                    </trigger:SelectChangeTargetTrigger
                </i:EventTrigger
            </i:Interaction.Triggers
            <sdk:DataGrid.Columns
                 /*此处省略部分代码*/               </sdk:DataGrid.Columns
 
        </sdk:DataGrid
        <sdk:DataPager x:Name="dataPager" 
                       Grid.Row=
"1" BorderThickness="1" 
                       BorderBrush=
"Black" 
                       DisplayMode=
"FirstLastPreviousNextNumeric" 
                       PageSize=
"10"
        </sdk:DataPager
        <Border BorderThickness="1" Margin="0,5,0,5" Background="Silver" BorderBrush="Black" Grid.Row="2" Grid.Column="0"
            /*此处省略部分代码*/                           <Image Source="Images/Windows.png" Opacity="0.7" Grid.Row="0" Grid.Column="0"/
    
 
                        <TextBox x:Name="txtUserNo" Foreground="Silver" BorderThickness="0" Grid.Row="0" Grid.Column="1" Text="{Binding UserNo,Mode=OneWay}"
                            <i:Interaction.Triggers
                               <i:EventTrigger EventName="GotFocus"
                                   <trigger:FocusTrigger></trigger:FocusTrigger
                               </i:EventTrigger
                                <i:EventTrigger EventName="LostFocus"
                                    <trigger:LostFocusTrigger
                                    </trigger:LostFocusTrigger
                                </i:EventTrigger
                            </i:Interaction.Triggers
                        </TextBox
                    </Grid
                </Border
               /*此处省略部分代码*/                   <ComboBox x:Name="cmbType" Grid.Row="1" Grid.Column="1" 
    
 
                          Background=
"AliceBlue"   
                          HorizontalAlignment=
"Left" Margin="2,5,0,0" Width="200" 
                          ItemsSource=
"{Binding UserTypeList,Mode=OneWay}"   
                          SelectedItem=
"{Binding UserType}"
                    <ComboBox.ItemTemplate
                        <DataTemplate
                            <StackPanel Orientation="Horizontal"
                                <Image Source="{Binding UserTypeImage}" Height="22" Stretch="UniformToFill"></Image
                                <TextBlock Text="{Binding UserTypeName,Mode=TwoWay}" FontWeight="Bold" Margin="2,0,0,0"/
                            </StackPanel
                        </DataTemplate
                    </ComboBox.ItemTemplate
                </ComboBox
               /*此处省略部分代码*/               </Grid
 
        </Border
    </Grid
</controls:ChildWindow
在这里布局我们就不说了,相信看过前几篇的都明白。首先我们看有这么一段代码
  1. <sdk:DataGrid.Projection> 
  2.                 <PlaneProjection RotationX="0" RotationY="10" RotationZ="0"></PlaneProjection> 
  3.             </sdk:DataGrid.Projection> 
这段代码是设置DataGrid的3D效果的,在这里我们设置其x轴的旋转角为0,Y轴旋转角为10,Z轴是0。怎么理解呢?这就好比空间一个三维坐标,X轴转动你想象成手指头抓住X轴的正方向,用手指去撵的让它转动。同理,Y轴和Z轴也是同样的道理。我们在这里设置正角,是让它逆时针转动,如果是负角则是顺时针。OK,我们接着看下面这样一段代码
  1. <i:Interaction.Triggers> 
  2.                 <i:EventTrigger EventName="SelectionChanged"> 
  3.                     <trigger:SelectChangeTargetTrigger TargetName="txtUserNo"> 
  4.                     </trigger:SelectChangeTargetTrigger> 
  5.                     <trigger:SelectChangeTargetTrigger TargetName="cmbType"> 
  6.                     </trigger:SelectChangeTargetTrigger> 
  7.                     <trigger:SelectChangeTargetTrigger TargetName="cmbState"> 
  8.                     </trigger:SelectChangeTargetTrigger> 
  9.                 </i:EventTrigger> 
  10.             </i:Interaction.Triggers> 
这段涉及到我开始提到的Action,在这里我们使用到了TargetedTriggerAction,也就是在DataGrid SelectionChanged事件触发的时候,会对这三个TargetName指定的UIElement进行UI的变化。我们看看这个SelectChangeTargetTrigger 。
  1. namespace MISInfoManage.Trigger  
  2. {  
  3.     public class SelectChangeTargetTrigger : TargetedTriggerAction<DependencyObject>  
  4.     {  
  5.         private DependencyObject element;  
  6.         public SelectChangeTargetTrigger()  
  7.         { }  
  8.  
  9.         protected override void OnAttached()  
  10.         {  
  11.             base.OnAttached();  
  12.             if (Target != null)  
  13.             {  
  14.                 element = Target;  
  15.             }  
  16.         }  
  17.  
  18.         protected override void OnDetaching()  
  19.         {  
  20.             base.OnDetaching();  
  21.             element = null;  
  22.         }  
  23.  
  24.         protected override void OnTargetChanged(DependencyObject oldTarget, DependencyObject newTarget)  
  25.         {  
  26.             base.OnTargetChanged(oldTarget, newTarget);  
  27.             if (element == null)  
  28.             {  
  29.                 element = newTarget;  
  30.             }  
  31.         }  
  32.  
  33.         protected override void Invoke(object parameter)  
  34.         {  
  35.             if ((this.AssociatedObject as DataGrid).SelectedItem != null)  
  36.             {  
  37.                 if (this.Target.GetType() == typeof(TextBox))  
  38.                 {  
  39.                     TextBox textBox = (Target as TextBox);  
  40.                     textBox.IsReadOnly = true;  
  41.                     if (textBox.Name == "txtUserNo")  
  42.                     {  
  43.                         textBox.Foreground = new SolidColorBrush(Colors.Black);  
  44.                     }  
  45.                 }  
  46.                 if (this.GetType() == typeof(ComboBox))  
  47.                 {  
  48.                     (Target as ComboBox).IsEnabled = false;  
  49.                 }   
  50.             }  
  51.         }  
  52.     }  
在这里需要注意的是要引用System.Windows.Interactivity命名空间。结合界面代码我们可以看出,当DataGrid的SelectionChanged事件触发以后,会调用Invoke方法,也就是遍历该TargetedTrigger下的目标元素,在这里是对文本框设置Readonly,对ComboBox设置IsEnabled。这样就有效的实现了UI与逻辑的分离,这个将放在ViewModel中做处理。再往下走我们发现这样一段代码
  1. <Border BorderThickness="1" BorderBrush="Black" Grid.Row="0" Grid.Column="1"  Width="200"> 
  2.                     <Grid> 
  3.                         <Grid.RowDefinitions> 
  4.                             <RowDefinition Height="Auto"></RowDefinition> 
  5.                         </Grid.RowDefinitions> 
  6.                         <Grid.ColumnDefinitions> 
  7.                             <ColumnDefinition Width="Auto"></ColumnDefinition> 
  8.                             <ColumnDefinition Width="*"></ColumnDefinition> 
  9.                         </Grid.ColumnDefinitions> 
  10.                         <Image Source="Images/Windows.png" Opacity="0.7" Grid.Row="0" Grid.Column="0"/> 
  11.                         <TextBox x:Name="txtUserNo" Foreground="Silver" BorderThickness="0" Grid.Row="0" Grid.Column="1" Text="{Binding UserNo,Mode=OneWay}"> 
  12.                             <i:Interaction.Triggers> 
  13.                                <i:EventTrigger EventName="GotFocus"> 
  14.                                    <trigger:FocusTrigger></trigger:FocusTrigger> 
  15.                                </i:EventTrigger> 
  16.                                 <i:EventTrigger EventName="LostFocus"> 
  17.                                     <trigger:LostFocusTrigger> 
  18.                                     </trigger:LostFocusTrigger> 
  19.                                 </i:EventTrigger> 
  20.                             </i:Interaction.Triggers> 
  21.                         </TextBox> 
  22.                     </Grid> 
  23.                 </Border> 
这段代码是模拟了一个水印文本框效果,在这里使用到了Trigger,一个是FocusTrigger,一个是LostFocusTrigger,结合起来实现水印效果。我们看看这两个Trigger的代码
public class FocusTrigger:TriggerActionTextBox { protected override void OnAttached() { base .OnAttached(); } protected override void OnDetaching() { base .OnDetaching(); } protected override void In
  
  1. public class FocusTrigger:TriggerAction<TextBox>  
  2.     {  
  3.         protected override void OnAttached()  
  4.         {  
  5.             base.OnAttached();  
  6.         }  
  7.  
  8.         protected override void OnDetaching()  
  9.         {  
  10.             base.OnDetaching();  
  11.         }  
  12.  
  13.         protected override void Invoke(object parameter)  
  14.         {  
  15.             if (this.AssociatedObject.Text.Trim() == "请输入用户名")  
  16.             {  
  17.                 this.AssociatedObject.Text = string.Empty;  
  18.                 this.AssociatedObject.Foreground = new SolidColorBrush(Colors.Black);  
  19.             }  
  20.         }  
  21.     } 
这个是FocusTrigger,文本框获取焦点时,调用Invoke方法。这个时候当文本框的值是“请输入用户名”的时候,就清掉文本框,并且字体前景色设置为黑色。我们再看看LostFocusTrigger的代码
  1. public class LostFocusTrigger : TriggerAction<TextBox>  
  2.     {  
  3.         protected override void OnAttached()  
  4.         {  
  5.             base.OnAttached();  
  6.         }  
  7.  
  8.         protected override void OnDetaching()  
  9.         {  
  10.             base.OnDetaching();  
  11.         }  
  12.  
  13.         protected override void Invoke(object parameter)  
  14.         {  
  15.             if (string.IsNullOrWhiteSpace(this.AssociatedObject.Text))  
  16.             {  
  17.                 this.AssociatedObject.Foreground = new SolidColorBrush(Colors.Gray);  
  18.                 this.AssociatedObject.Text = "请输入用户名";  
  19.             }  
  20.         }  
  21.     } 
当文本框失去焦点的时候,如果文本框值为空或者空白,那么文本框的值为“请输入用户名”,前景色改为Gray。OK,我们继续往下看
  1. <ComboBox x:Name="cmbType" Grid.Row="1" Grid.Column="1" 
  2.                           Background="AliceBlue"   
  3.                           HorizontalAlignment="Left" Margin="2,5,0,0" Width="200" 
  4.                           ItemsSource="{Binding UserTypeList,Mode=OneWay}"   
  5.                           SelectedItem="{Binding UserType}"> 
  6.                     <ComboBox.ItemTemplate> 
  7.                         <DataTemplate> 
  8.                             <StackPanel Orientation="Horizontal"> 
  9.                                 <Image Source="{Binding UserTypeImage}" Height="22" Stretch="UniformToFill"></Image> 
  10.                                 <TextBlock Text="{Binding UserTypeName,Mode=TwoWay}" FontWeight="Bold" Margin="2,0,0,0"/> 
  11.                             </StackPanel> 
  12.                         </DataTemplate> 
  13.                     </ComboBox.ItemTemplate> 
  14.                 </ComboBox> 
在这里我们使用了ComboBox的项模板,在这里你可以自定义你想要的模版。在这里我放了一个图片和一个文本。如下图所示
怎么样,很不错吧。最后在这列我们重点看看分页。我这可是服务端分页。
  1. <sdk:DataPager x:Name="dataPager" 
  2.                        Grid.Row="1" BorderThickness="1" 
  3.                        BorderBrush="Black" 
  4.                        DisplayMode="FirstLastPreviousNextNumeric" 
  5.                        PageSize="10"> 
  6.         </sdk:DataPager> 
这个时候我就要把ViewModel的代码贴出来了,否则没法讲述清楚。
  1. namespace MISInfoManage.ViewModels  
  2. {  
  3.     public class UserManageViewModel : INotifyPropertyChanged  
  4.     {  
  5.         private bool isFirstLoad;  
  6.         public UserManageView userManageView;  
  7.         DataPagerTrigger dataPagertrigger;  
  8.         List<int> list = new List<int>();  
  9.         public UserManageViewModel()  
  10.         {  
  11.             this.InitData(1, 10);  
  12.         }  
  13.  
  14.         public UserManageViewModel(UserManageView userManageView)  
  15.             : this()  
  16.         {  
  17.             isFirstLoad = true;  
  18.             this.userManageView = userManageView;  
  19.             this.dataPagertrigger = new DataPagerTrigger(this);  
  20.             this.AttachTrigger();  
  21.             this.SetTitle();  
  22.             this.UserNo = "请输入用户名";  
  23.         }  
  24.  
  25.         #region property  
  26.  
  27.        /*此处省略部分代码*/           #endregion  
  28.     
  29.  
  30.  
  31.         #region method  
  32.  
  33.         private void AttachTrigger()  
  34.         {  
  35.             this.dataPagertrigger.Attach(this.userManageView.dataPager);  
  36.         }  
  37.  
  38.         private void SetTitle()  
  39.         {  
  40.             Image image = new Image();  
  41.             image.Source = new BitmapImage(new Uri("../Images/drag.png", UriKind.Relative));  
  42.             image.Height = 30;  
  43.             image.Width = 20;  
  44.             TextBlock textBlock = new TextBlock();  
  45.             textBlock.Margin = new Thickness(5, 0, 0, 0);  
  46.             textBlock.Text = "用户管理";  
  47.             textBlock.FontSize = 14;  
  48.             textBlock.FontWeight = FontWeights.Bold;  
  49.             StackPanel stackPanel = new StackPanel();  
  50.             stackPanel.Orientation = Orientation.Horizontal;  
  51.             stackPanel.Background = new RadialGradientBrush(Colors.Gray, Colors.Brown);  
  52.             stackPanel.Width = this.userManageView.Width;  
  53.             stackPanel.Children.Add(image);  
  54.             stackPanel.Children.Add(textBlock);  
  55.             this.userManageView.Title = stackPanel;  
  56.         }  
  57.  
  58.         private void GetUserList(UserRequest request, EventHandler<GetUserListCompletedEventArgs> callback)  
  59.         {  
  60.             UserManageService.UserManageServiceClient client = new UserManageServiceClient();  
  61.             client.GetUserListCompleted += callback;  
  62.             client.GetUserListAsync(request);  
  63.         }  
  64.  
  65.        /*此处省略部分代码*/    
  66.     
  67.  
  68.         private void GetUserPagedCollection(int totalCount)  
  69.         {  
  70.             list.Clear();  
  71.             for (int i = 0; i < totalCount; i++)  
  72.             {  
  73.                 list.Add(i);  
  74.             }  
  75.             PagedCollectionView pagedCollectionView = new PagedCollectionView(list);  
  76.             this.userManageView.dataPager.Source = pagedCollectionView;  
  77.             isFirstLoad = false;  
  78.         }  
  79.  
  80.         public void GetDataByPage(int pageIndex, int pageSize)  
  81.         {  
  82.             UserRequest userRequest = new UserRequest()  
  83.             {  
  84.                 PageIndex = pageIndex,  
  85.                 PageSize = pageSize  
  86.             };  
  87.             this.GetUserList(userRequest, (obj, args) =>  
  88.             {  
  89.                 if (args.Error == null)  
  90.                 {  
  91.                     this.UserResponse = args.Result;  
  92.                     this.UserList = userResponse.UserList;  
  93.                     if (isFirstLoad)  
  94.                     {  
  95.                         this.GetUserPagedCollection(this.UserResponse.RecordCount);  
  96.                     }  
  97.                 }  
  98.             });  
  99.         }  
  100.  
  101.         private void InitData(int pageIndex, int pageSize)  
  102.         {  
  103.             this.GetDataByPage(pageIndex,pageSize);  
  104.             this.GetUserStateList();  
  105.             this.GetUserTypeList();  
  106.         }  
  107.  
  108.         #endregion  
  109.  
  110.         /*此处省略部分代码*/       }  
  111.     
  112.  
在ViewModel的代码中,我们发现了这样一段代码
  1. this.dataPagertrigger = new DataPagerTrigger(this);  
  2.            this.AttachTrigger(); 
  1. private void AttachTrigger()  
  2.        {  
  3.            this.dataPagertrigger.Attach(this.userManageView.dataPager);  
  4.        } 
这段代码就是在初始化ViewModel的时候,将dataPager控件附加给dataPagertrigger。我们看看dataPagertrigger代码
  1. public class DataPagerTrigger : TriggerBase<UIElement>  
  2.     {  
  3.         public UserManageViewModel userManageViewModel;  
  4.         public DataPagerTrigger(UserManageViewModel userManageViewModel)  
  5.         {  
  6.             this.userManageViewModel = userManageViewModel;  
  7.         }  
  8.  
  9.         protected override void OnAttached()  
  10.         {  
  11.             base.OnAttached();  
  12.             if (this.AssociatedObject is DataPager)  
  13.             {  
  14.                 (this.AssociatedObject as DataPager).PageIndexChanged += this.PageIndexChanged;  
  15.             }  
  16.         }  
  17.  
  18.         protected override void OnDetaching()  
  19.         {  
  20.             base.OnDetaching();  
  21.             if (this.AssociatedObject is DataPager)  
  22.             {  
  23.                 (this.AssociatedObject as DataPager).PageIndexChanged -= this.PageIndexChanged;  
  24.             }  
  25.         }  
  26.  
  27.         private void PageIndexChanged(object sender, EventArgs e)  
  28.         {  
  29.             DataPager dataPager = sender as DataPager;  
  30.             this.userManageViewModel.GetDataByPage(dataPager.PageIndex, dataPager.PageSize);  
  31.         }  
  32.     } 
这个类继承自TriggerBase,我们给其注册了一个PageIndexChanged 事件,当DataPager控件页码变化时,调用PageIndexChanged方法,在这个方法里调用userManageViewModel的GetDataByPage方法。在GetDataByPage方法中先获取用户列表,判断如果是第一次加载,就给 DataPager控件附一个PagedCollectionView类型的一个source,在这里通过模拟,可以有效的进行分页,因为目前我发现这个分页控件只能客户端分页。所以模拟一个List<int>,它的总条数==表的总记录。这样就实现了服务端分页,如果谁想要源码,可以加入群205217091。我们看看分页的效果
最后大家可能注意到弹出界面的Title和以前的不一样,这里你可以自定义,如ViewModel中的SetTitle方法。好了,时间不早了,洗洗睡!
本文来自BruceAndLee的博客,原文地址:http://leelei.blog.51cto.com/856755/950748

http://silverlightchina.net/html/tips/2012/0806/17928_2.html