扯面工程师,配菜工程师,门迎工程师,当今世界工程师可真多啊。
今天我们把系统参数管理模块翻译成Silverlight项目。首先请看两张图
再看看翻译好的图
其实我们看到了第二张图用到了treeView,Popup,这节主要讲的还是MVVM,不过和以往有所不同。先看看前台代码
<controls:ChildWindow x:Class="MISInfoManage.CodeManageView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:resource="clr-namespace:MISInfoManage.Resources" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows" mc:Ignorable="d" Width="700" Height="400" FontSize="13" Title="系统参数管理"> <controls:ChildWindow.Resources> <resource:CodeManageResource x:Key="CodeManageResource"/> <Style x:Key="ColumnHeaderStyle" TargetType="sdk:DataGridColumnHeader"> <Setter Property="Height" Value="25"></Setter> </Style> </controls:ChildWindow.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="0"></ColumnDefinition> </Grid.ColumnDefinitions> <Border BorderBrush="AliceBlue" Width="150" Grid.Row="0" Grid.Column="0" BorderThickness="1" CornerRadius="2" Margin="0,0,10,0"> <toolkit:TreeView x:Name="treeViewCode"></toolkit:TreeView> </Border> <sdk:DataGrid Grid.Row="0" Grid.Column="1" BorderBrush="Black" BorderThickness="1" IsReadOnly="True" x:Name="dgCodeList" AutoGenerateColumns="False" AlternatingRowBackground="Gray" CanUserReorderColumns="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" SelectionMode="Single" SelectedItem="{Binding CodeEntity,Mode=TwoWay}" ItemsSource="{Binding CodeList,Mode=OneWay}" CanUserSortColumns="True"> <sdk:DataGrid.Columns> <sdk:DataGridTemplateColumn Header="选择"> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox HorizontalAlignment="Center"></CheckBox> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn Binding="{Binding data,Mode=OneWay}" HeaderStyle="{StaticResource ColumnHeaderStyle}" Header="数据值"/> <sdk:DataGridTextColumn Binding="{Binding ename,Mode=OneWay}" HeaderStyle="{StaticResource ColumnHeaderStyle}" Header="英文代码"/> <sdk:DataGridTextColumn Binding="{Binding cname,Mode=OneWay}" HeaderStyle="{StaticResource ColumnHeaderStyle}" Header="中文代码"/> <sdk:DataGridTextColumn Binding="{Binding display_content,Mode=OneWay}" HeaderStyle="{StaticResource ColumnHeaderStyle}" Header="显示值"/> <sdk:DataGridTemplateColumn Header="操作"> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel> <StackPanel.Resources> <Style x:Key="buttonStyle" TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border CornerRadius="2" BorderThickness="1" BorderBrush="Red"> <StackPanel Orientation="Horizontal" Background="Turquoise"> <StackPanel.Effect> <DropShadowEffect Color="Black" Direction="270" ShadowDepth="5" BlurRadius="5" Opacity="0.5"> </DropShadowEffect> </StackPanel.Effect> <Image Source="/MISInfoManage;component/Images/drag.png" Height="25" Width="60"/> <TextBlock Text="修改" Foreground="Brown" FontWeight="Bold"></TextBlock> </StackPanel> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </StackPanel.Resources> <Button Content="Button1" Style="{StaticResource buttonStyle}"/> </StackPanel> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> </sdk:DataGrid.Columns> </sdk:DataGrid> <Primitives:Popup x:Name="CodeInfoPop" Grid.Row="0" Grid.Column="2"> <StackPanel Width="200"> <StackPanel.Style> <Style TargetType="StackPanel"> <Setter Property="Background" Value="Brown"></Setter> </Style> </StackPanel.Style> <StackPanel.Effect> <DropShadowEffect Color="Black" Direction="300" ShadowDepth="10" BlurRadius="5" Opacity="0.6"> </DropShadowEffect> </StackPanel.Effect> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Tb_Ename,Source={StaticResource CodeManageResource}}"/> <TextBlock Text="{Binding CodeEntity.ename}" Margin="5,0,0,0"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Tb_CNm,Source={StaticResource CodeManageResource}}"/> <TextBlock Text="{Binding CodeEntity.cname}" Margin="5,0,0,0"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Tb_Display,Source={StaticResource CodeManageResource}}"/> <TextBlock Text="{Binding CodeEntity.display_content}" Margin="5,0,0,0"/> </StackPanel> </StackPanel> </Primitives:Popup> </Grid> </controls:ChildWindow>
最普遍的布局方式Grid+StackPanel。记得上次好像我在写博客的时候,给每个控件都设置了FontSize,其实这个是没有必要的,只需要在controls:ChildWindow节点中设置,那么整个页面的控件的FontSize都起作用。再往下看,有这样的一些节点
<Style x:Key="buttonStyle" TargetType="Button">...</Style>里面有这么一段代码
<ControlTemplate TargetType="Button"> <Border CornerRadius="2" BorderThickness="1" BorderBrush="Red"> <StackPanel Orientation="Horizontal" Background="Turquoise"> <StackPanel.Effect> <DropShadowEffect Color="Black" Direction="270" ShadowDepth="5" BlurRadius="5" Opacity="0.5"> </DropShadowEffect> </StackPanel.Effect> <Image Source="/MISInfoManage;component/Images/drag.png" Height="25" Width="60"/> <TextBlock Text="修改" Foreground="Brown" FontWeight="Bold"></TextBlock> </StackPanel> </Border> </ControlTemplate>
这个是定义一个Button的控件模版。在这里我们给StackPanel定义了一个特效DropShadowEffect ,一个阴影效果。在这个模板中我放置了一个图片和一个文本,大家看看上面的图就知道了。整个一按钮变成如此摸样,所以在我们做开发的时候,可以通过设置ControlTemplate来定制我们按钮或者其他控件的模版。在按钮中,我们只需要设置Style="{StaticResource buttonStyle}"即可。再往下走,有这么一段代码
<Primitives:Popup x:Name="CodeInfoPop" Grid.Row="0" Grid.Column="2">这个节点正是定义我们的Popup,当DataGrid的SelectionChanged触发时,弹出Popup。注意Popup这里需要引用System.Windows.Controls.Primitives。好了前台没什么,就这么些。我们看看页面后台,代码如下
namespace MISInfoManage { public partial class CodeManageView : ChildWindow { CodeManageModel codeManage; public CodeManageView() { InitializeComponent(); codeManage = new CodeManageModel(this); this.LayoutRoot.DataContext = codeManage; } } }
我勒了个去,怎么就这么点代码,我告诉你,就这么点代码。我们这次使用了Behavior,那么就不会出现页面后台事件。看看ViewModel到底都干了些什么。
namespace MISInfoManage.ViewModels { public class CodeManageModel : INotifyPropertyChanged { public ChildWindow userControl; CodeManageServiceClient client; SelectionChangedBehavior mouseRightButtonBrhavior; public CodeManageModel() { client = new CodeManageServiceClient(); this.GetCodesList((obj, args) => { this.CodesList = args.Result; this.BuildTree(); }); } public CodeManageModel(ChildWindow userControl) : this() { this.userControl = userControl; this.mouseRightButtonBrhavior = new SelectionChangedBehavior(this); mouseRightButtonBrhavior.Attach((userControl as CodeManageView).dgCodeList); this.SetContextMenu(); } private List<Codes> codesList; public List<Codes> CodesList { get { return codesList; } set { codesList = value; NotifyPropertyChange("CodesList"); } } private List<Codes> codeList; public List<Codes> CodeList { get { return codeList; } set { codeList = value; NotifyPropertyChange("CodeList"); } } private Codes codeEntity; public Codes CodeEntity { set { codeEntity = value; NotifyPropertyChange("CodeEntity"); } get { return codeEntity; } } public void GetCodesList(EventHandler<GetCodeListCompletedEventArgs> handler) { client.GetCodeListCompleted += handler; client.GetCodeListAsync(); } public void GetCodeListByCondition(string ename, EventHandler<GetCodeListByConditionCompletedEventArgs> handler) { client.GetCodeListByConditionCompleted += handler; client.GetCodeListByConditionAsync(ename); } public void BuildTree() { TreeViewItem mainitem = new TreeViewItem(); mainitem.Header = "系统参数"; mainitem.IsExpanded = true; List<Codes> codeList = null; if (this.CodesList != null && this.CodesList.Count > 0) { codeList = this.CodesList.Distinct<Codes>(new EqualityCompare()).ToList(); codeList.ForEach(c => { TreeViewItem treeViewItem = new TreeViewItem(); StackPanel stackPanel = new StackPanel(); stackPanel.Orientation = Orientation.Horizontal; TextBlock textBlock = new TextBlock(); textBlock.Text = c.cname; textBlock.Tag = c.ename; textBlock.MouseLeftButtonDown += this.LoadCodeByID; Image image = new Image(); image.Width = 12; image.Height = 12; image.Margin = new Thickness(0, 0, 5, 0); image.Source = new BitmapImage(new Uri("../Images/Windows.jpg", UriKind.Relative)); stackPanel.Children.Add(image); stackPanel.Children.Add(textBlock); treeViewItem.Header = stackPanel; mainitem.Items.Add(treeViewItem); }); (this.userControl as CodeManageView).treeViewCode.Items.Add(mainitem); } } private void LoadCodeByID(object sender, MouseButtonEventArgs e) { TextBlock textBlock = sender as TextBlock; string ename = textBlock.Tag.ToString(); this.GetCodeListByCondition(ename, (obj, args) => { this.CodeList = args.Result; }); } private void SetContextMenu() { ContextMenu contextMenu = new ContextMenu(); MenuItem menuItem = new MenuItem(); menuItem.Tag = "Modify"; TextBlock textBlock = new TextBlock(); textBlock.Text = "修改"; menuItem.Header = textBlock; contextMenu.Items.Add(menuItem); menuItem = new MenuItem(); textBlock = new TextBlock(); textBlock.Text = "添加"; menuItem.Tag = "Add"; menuItem.Header = textBlock; contextMenu.Items.Add(menuItem); menuItem = new MenuItem(); textBlock = new TextBlock(); textBlock.Text = "关闭"; menuItem.Tag = "Close"; menuItem.Header = textBlock; menuItem.Click += delegate(object sender, RoutedEventArgs e) { (userControl as CodeManageView).Close(); }; contextMenu.Items.Add(menuItem); ContextMenuService.SetContextMenu((this.userControl as CodeManageView).dgCodeList, contextMenu); } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChange(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } } public class EqualityCompare : IEqualityComparer<Codes> { public bool Equals(Codes code1, Codes code2) { return code1.ename.Equals(code2.ename); } public int GetHashCode(Codes code) { return code.ename.GetHashCode(); } } }
首先进入构造函数,初始化我们的TreeView,初始化TreeView的时候有这么个代码codeList = this.CodesList.Distinct<Codes>(new EqualityCompare()).ToList();去除List<T>重复。我们知道对象的值都一样,不代表对象一样。所以我们需要实现IEqualityComparer<T>接口,Equals方法确定你要Distinct的规则,在这里就是Ename不相等。GetHashCode必须返回Ename的HashCode。再往下走,这么一段代码
this.mouseRightButtonBrhavior = new SelectionChangedBehavior(this); mouseRightButtonBrhavior.Attach((userControl as CodeManageView).dgCodeList);
在这里使用到了Behavior,我们为DataGrid定义了一个行为,我们来看看这个行为
namespace MISInfoManage.Behavior { public class SelectionChangedBehavior : Behavior<DataGrid> { CodeManageModel _codeManageModel; public SelectionChangedBehavior():base() {} public SelectionChangedBehavior(CodeManageModel codeManageModel) : this() { _codeManageModel = codeManageModel; } protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged; this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; } protected override void OnDetaching() { base.OnDetaching(); this.AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged; this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove; } private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (_codeManageModel.CodeEntity != null) { Popup popup = (this._codeManageModel.userControl as CodeManageView).CodeInfoPop; popup.HorizontalOffset = 20; popup.VerticalOffset = 70; popup.IsOpen = true; } } private void AssociatedObject_MouseMove(object sender,MouseEventArgs e) { Popup popup = (this._codeManageModel.userControl as CodeManageView).CodeInfoPop; popup.IsOpen = false; } } }
在这里我只说一个基类,其他的就不往出贴了,大家自己看
namespace System.Windows.Interactivity { // Summary: // Encapsulates state information and zero or more ICommands into an attachable // object. // // Type parameters: // T: // The type the System.Windows.Interactivity.Behavior<T> can be attached to. // // Remarks: // Behavior is the base class for providing attachable state and commands to // an object. The types the Behavior can be attached to can be controlled by // the generic parameter. Override OnAttached() and OnDetaching() methods to // hook and unhook any necessary handlers from the AssociatedObject. public abstract class Behavior<T> : Behavior where T : System.Windows.DependencyObject { // Summary: // Initializes a new instance of the System.Windows.Interactivity.Behavior<T> // class. protected Behavior(); // Summary: // Gets the object to which this System.Windows.Interactivity.Behavior<T> is // attached. protected T AssociatedObject { get; } } }
这里的的AssociatedObject就是泛型T,并且它是System.Windows.DependencyObject类型的。Behavior类是个抽象类,具有Attach,Detach,OnAttached,OnDetaching方法。OnAttached在Attach方法调用以后生效。所以这段代码就实现了DataGrid的SelectionChanged和MouseMove事件,在SelectionChanged以后弹出Popup,MouseMove以后隐藏Popup。这种方式是不是很有效的实现了页面UI和逻辑的分离。再往下走,有个SetContextMenu方法,正是给DataGrid附加一个弹出菜单。在菜单构造好以后,需要调用ContextMenuService.SetContextMenu((this.userControl as CodeManageView).dgCodeList, contextMenu);,第一个参数是一个DependencyObject类型的对象,第二个是ContextMenu对象。类似于这样的内置对象还有FocusManager,它的方法FocusManager.GetFocusedElement()可以直接找到页面获得焦点的元素。我们来看看这个菜单的效果,
当我们点击关闭的时候,将会关闭该页面。OK,今天就讲到这里,时间也不早了,洗洗睡。