这个行业里,山外有山,人外有人,忽然觉得自己是个菜鸟。
今天我们通过用户管理模块来系统的讲述关于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>
在这里布局我们就不说了,相信看过前几篇的都明白。首先我们看有这么一段代码
<sdk:DataGrid.Projection> <PlaneProjection RotationX="0" RotationY="10" RotationZ="0"></PlaneProjection> </sdk:DataGrid.Projection>
这段代码是设置DataGrid的3D效果的,在这里我们设置其x轴的旋转角为0,Y轴旋转角为10,Z轴是0。怎么理解呢?这就好比空间一个三维坐标,X轴转动你想象成手指头抓住X轴的正方向,用手指去撵的让它转动。同理,Y轴和Z轴也是同样的道理。我们在这里设置正角,是让它逆时针转动,如果是负角则是顺时针。OK,我们接着看下面这样一段代码
<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>
这段涉及到我开始提到的Action,在这里我们使用到了TargetedTriggerAction,也就是在DataGrid SelectionChanged事件触发的时候,会对这三个TargetName指定的UIElement进行UI的变化。我们看看这个SelectChangeTargetTrigger 。
namespace MISInfoManage.Trigger { public class SelectChangeTargetTrigger : TargetedTriggerAction<DependencyObject> { private DependencyObject element; public SelectChangeTargetTrigger() { } protected override void OnAttached() { base.OnAttached(); if (Target != null) { element = Target; } } protected override void OnDetaching() { base.OnDetaching(); element = null; } protected override void OnTargetChanged(DependencyObject oldTarget, DependencyObject newTarget) { base.OnTargetChanged(oldTarget, newTarget); if (element == null) { element = newTarget; } } protected override void Invoke(object parameter) { if ((this.AssociatedObject as DataGrid).SelectedItem != null) { if (this.Target.GetType() == typeof(TextBox)) { TextBox textBox = (Target as TextBox); textBox.IsReadOnly = true; if (textBox.Name == "txtUserNo") { textBox.Foreground = new SolidColorBrush(Colors.Black); } } if (this.GetType() == typeof(ComboBox)) { (Target as ComboBox).IsEnabled = false; } } } } }
在这里需要注意的是要引用System.Windows.Interactivity命名空间。结合界面代码我们可以看出,当DataGrid的SelectionChanged事件触发以后,会调用Invoke方法,也就是遍历该TargetedTrigger下的目标元素,在这里是对文本框设置Readonly,对ComboBox设置IsEnabled。这样就有效的实现了UI与逻辑的分离,这个将放在ViewModel中做处理。再往下走我们发现这样一段代码
<Border BorderThickness="1" BorderBrush="Black" Grid.Row="0" Grid.Column="1" Width="200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <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>
这段代码是模拟了一个水印文本框效果,在这里使用到了Trigger,一个是FocusTrigger,一个是LostFocusTrigger,结合起来实现水印效果。我们看看这两个Trigger的代码
public class FocusTrigger:TriggerAction<TextBox> { protected override void OnAttached() { base.OnAttached(); } protected override void OnDetaching() { base.OnDetaching(); } protected override void Invoke(object parameter) { if (this.AssociatedObject.Text.Trim() == "请输入用户名") { this.AssociatedObject.Text = string.Empty; this.AssociatedObject.Foreground = new SolidColorBrush(Colors.Black); } } }
这个是FocusTrigger,文本框获取焦点时,调用Invoke方法。这个时候当文本框的值是“请输入用户名”的时候,就清掉文本框,并且字体前景色设置为黑色。我们再看看LostFocusTrigger的代码
public class LostFocusTrigger : TriggerAction<TextBox> { protected override void OnAttached() { base.OnAttached(); } protected override void OnDetaching() { base.OnDetaching(); } protected override void Invoke(object parameter) { if (string.IsNullOrWhiteSpace(this.AssociatedObject.Text)) { this.AssociatedObject.Foreground = new SolidColorBrush(Colors.Gray); this.AssociatedObject.Text = "请输入用户名"; } } }
当文本框失去焦点的时候,如果文本框值为空或者空白,那么文本框的值为“请输入用户名”,前景色改为Gray。OK,我们继续往下看
<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>
在这里我们使用了ComboBox的项模板,在这里你可以自定义你想要的模版。在这里我放了一个图片和一个文本。如下图所示
怎么样,很不错吧。最后在这列我们重点看看分页。我这可是服务端分页。
<sdk:DataPager x:Name="dataPager" Grid.Row="1" BorderThickness="1" BorderBrush="Black" DisplayMode="FirstLastPreviousNextNumeric" PageSize="10"> </sdk:DataPager>
这个时候我就要把ViewModel的代码贴出来了,否则没法讲述清楚。
namespace MISInfoManage.ViewModels { public class UserManageViewModel : INotifyPropertyChanged { private bool isFirstLoad; public UserManageView userManageView; DataPagerTrigger dataPagertrigger; List<int> list = new List<int>(); public UserManageViewModel() { this.InitData(1, 10); } public UserManageViewModel(UserManageView userManageView) : this() { isFirstLoad = true; this.userManageView = userManageView; this.dataPagertrigger = new DataPagerTrigger(this); this.AttachTrigger(); this.SetTitle(); this.UserNo = "请输入用户名"; } #region property /*此处省略部分代码*/ #endregion #region method private void AttachTrigger() { this.dataPagertrigger.Attach(this.userManageView.dataPager); } private void SetTitle() { Image image = new Image(); image.Source = new BitmapImage(new Uri("../Images/drag.png", UriKind.Relative)); image.Height = 30; image.Width = 20; TextBlock textBlock = new TextBlock(); textBlock.Margin = new Thickness(5, 0, 0, 0); textBlock.Text = "用户管理"; textBlock.FontSize = 14; textBlock.FontWeight = FontWeights.Bold; StackPanel stackPanel = new StackPanel(); stackPanel.Orientation = Orientation.Horizontal; stackPanel.Background = new RadialGradientBrush(Colors.Gray, Colors.Brown); stackPanel.Width = this.userManageView.Width; stackPanel.Children.Add(image); stackPanel.Children.Add(textBlock); this.userManageView.Title = stackPanel; } private void GetUserList(UserRequest request, EventHandler<GetUserListCompletedEventArgs> callback) { UserManageService.UserManageServiceClient client = new UserManageServiceClient(); client.GetUserListCompleted += callback; client.GetUserListAsync(request); } /*此处省略部分代码*/ private void GetUserPagedCollection(int totalCount) { list.Clear(); for (int i = 0; i < totalCount; i++) { list.Add(i); } PagedCollectionView pagedCollectionView = new PagedCollectionView(list); this.userManageView.dataPager.Source = pagedCollectionView; isFirstLoad = false; } public void GetDataByPage(int pageIndex, int pageSize) { UserRequest userRequest = new UserRequest() { PageIndex = pageIndex, PageSize = pageSize }; this.GetUserList(userRequest, (obj, args) => { if (args.Error == null) { this.UserResponse = args.Result; this.UserList = userResponse.UserList; if (isFirstLoad) { this.GetUserPagedCollection(this.UserResponse.RecordCount); } } }); } private void InitData(int pageIndex, int pageSize) { this.GetDataByPage(pageIndex,pageSize); this.GetUserStateList(); this.GetUserTypeList(); } #endregion /*此处省略部分代码*/ } }
在ViewModel的代码中,我们发现了这样一段代码
this.dataPagertrigger = new DataPagerTrigger(this); this.AttachTrigger(); private void AttachTrigger() { this.dataPagertrigger.Attach(this.userManageView.dataPager); }
这段代码就是在初始化ViewModel的时候,将dataPager控件附加给dataPagertrigger。我们看看dataPagertrigger代码
public class DataPagerTrigger : TriggerBase<UIElement> { public UserManageViewModel userManageViewModel; public DataPagerTrigger(UserManageViewModel userManageViewModel) { this.userManageViewModel = userManageViewModel; } protected override void OnAttached() { base.OnAttached(); if (this.AssociatedObject is DataPager) { (this.AssociatedObject as DataPager).PageIndexChanged += this.PageIndexChanged; } } protected override void OnDetaching() { base.OnDetaching(); if (this.AssociatedObject is DataPager) { (this.AssociatedObject as DataPager).PageIndexChanged -= this.PageIndexChanged; } } private void PageIndexChanged(object sender, EventArgs e) { DataPager dataPager = sender as DataPager; this.userManageViewModel.GetDataByPage(dataPager.PageIndex, dataPager.PageSize); } }
这个类继承自TriggerBase,我们给其注册了一个PageIndexChanged 事件,当DataPager控件页码变化时,调用PageIndexChanged方法,在这个方法里调用userManageViewModel的GetDataByPage方法。在GetDataByPage方法中先获取用户列表,判断如果是第一次加载,就给 DataPager控件附一个PagedCollectionView类型的一个source,在这里通过模拟,可以有效的进行分页,因为目前我发现这个分页控件只能客户端分页。所以模拟一个List<int>,它的总条数==表的总记录。这样就实现了服务端分页,如果谁想要源码,可以加入群205217091。我们看看分页的效果
最后大家可能注意到弹出界面的Title和以前的不一样,这里你可以自定义,如ViewModel中的SetTitle方法。好了,时间不早了,洗洗睡!