索引
SilverLight企业应用框架设计【五】客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务)
SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)
首先我们设计的窗体如下
xaml代码如下:
<location:BasePage x:Class="RTMDemo.Frame.Pages.Sys.MenuLE"
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"
mc:Ignorable="d"
xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:location="clr-namespace:RTMDemo.Frame.Pages"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="580" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="CDL" Width="200"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<ScrollViewer Grid.Row="0" Grid.Column="0"
Width="{Binding ElementName=CDL,Path=Width}"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<sdk:TreeView BorderThickness="0" Name="MenuTV" SelectedItemChanged="MenuTV_SelectedItemChanged">
</sdk:TreeView>
</ScrollViewer>
<Grid x:Name="MenuFormG" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"></ColumnDefinition>
<ColumnDefinition Width="8"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32"></RowDefinition>
<RowDefinition Height="32"></RowDefinition>
<RowDefinition Height="32"></RowDefinition>
<RowDefinition Height="32"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="32"></RowDefinition>
</Grid.RowDefinitions>
<sdk:Label Target="{Binding ElementName=MenuNameTB}"
VerticalAlignment="Center" HorizontalAlignment="Right"
></sdk:Label>
<TextBox Name="MenuNameTB" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" Width="300"
VerticalAlignment="Center" Height="22" Text="{Binding MenuName, Mode=TwoWay
,NotifyOnValidationError=True,ValidatesOnExceptions=True}">
</TextBox>
<sdk:Label Grid.Row="1" Target="{Binding ElementName=MenuOrderTB}"
VerticalAlignment="Center" HorizontalAlignment="Right"
></sdk:Label>
<TextBox Name="MenuOrderTB" Grid.Column="2" Grid.Row="1"
HorizontalAlignment="Left" Width="100"
VerticalAlignment="Center" Height="22"
Text="{Binding OrderNum,Mode=TwoWay
,NotifyOnValidationError=True,ValidatesOnExceptions=True}"></TextBox>
<sdk:Label Grid.Row="2" Content="菜单路径"
VerticalAlignment="Center" HorizontalAlignment="Right"
></sdk:Label>
<ComboBox x:Name="MenuUrlCB" Height="22" Width="300"
SelectedValue="{Binding Url,Mode=TwoWay}"
Grid.Column="2" Grid.Row="2" HorizontalAlignment="Left"></ComboBox>
<TextBlock Grid.Row="3" Text="父级菜单" VerticalAlignment="Center" HorizontalAlignment="Right"></TextBlock>
<ComboBox Grid.Column="2" Grid.Row="3" DisplayMemberPath="MenuName" x:Name="TMenuCB"
Height="22" Width="100"
HorizontalAlignment="Left">
</ComboBox>
<sdk:Label Grid.Row="4" Target="{Binding ElementName=HelpTB}"
VerticalAlignment="Center" HorizontalAlignment="Right"
></sdk:Label>
<TextBox Grid.Column="2" Grid.Row="4" x:Name="HelpTB"
AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="{Binding MenuDes,Mode=TwoWay
,NotifyOnValidationError=True,ValidatesOnExceptions=True}"></TextBox>
<StackPanel Grid.Column="2" Grid.Row="111" Orientation="Horizontal">
<Button x:Name="AddBTN" Width="100" Height="22" Margin="0 0 10 0" Content="增加" Click="AddBTN_Click"></Button>
<Button x:Name="EditBTN" Width="100" Height="22" Margin="0 0 10 0" Content="修改" Click="EditBTN_Click"></Button>
<Button x:Name="DelBTN" Width="100" Height="22" Margin="0 0 10 0" Content="删除" Click="DelBTN_Click"></Button>
</StackPanel>
</Grid>
</Grid>
</location:BasePage>
需要说明的:
1.
所有的业务窗体都继承自BasePage类
这也是为什么xaml代码的开始处是<location:BasePage….
2.
由于左侧的树控件和右侧的Grid控件中间
有个GridSplitter控件
所以可以自由的拖动GridSplitter控件以变化左右两侧控件的大小
树控件我们暂且不提(没有什么特殊的地方)
-------------------------
在加载页面的Loaded事件中执行了如下代码
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
if (IsLoaded)
{
return;
}
InitMenuTree();
InitTypeCB();
}
其中IsLoaded属性是基类BasePage的属性
代码如下
protected bool IsLoaded = false;
public BasePage()
{
this.Loaded += new RoutedEventHandler(BasePage_Loaded);
}
void BasePage_Loaded(object sender, RoutedEventArgs e)
{
IsLoaded = true;
}
这样做就是为了避免重复执行InitMenuTree和InitTypeCB两个方法的代码
(tab页面切换会触发Loaded事件)
------------------------------------
先来看InitMenuTree的代码
void InitMenuTree()
{
var tMenu = Common.ViewUtility.AllMenu
.Where(m => m.ParentId == Guid.Empty)
.OrderBy(m=>m.OrderNum);
InitParentMenu(tMenu);
foreach (var tm in tMenu)
{
var ttvi = new TreeViewItem();
ttvi.Header = tm.MenuName;
ttvi.DataContext = tm;
if (MenuTV.Items.Count < 1)
{
MenuFormG.DataContext = tm;
ttvi.IsSelected = true;
}
ttvi.IsExpanded = true;
MenuTV.Items.Add(ttvi);
var sMenu = Common.ViewUtility.AllMenu
.Where(m => m.ParentId == tm.Id)
.OrderBy(m => m.OrderNum);
foreach (var sm in sMenu)
{
var stvi = new TreeViewItem();
stvi.Header = sm.MenuName;
stvi.DataContext = sm;
ttvi.Items.Add(stvi);
}
}
}
笔者并没有使用数据绑定的形式给控件赋值
而是直接创建了树控件的子控件来赋值的(这与我们的数据结构有关,这样做更简便一些)
MenuM类型并不是一个自引用的类型(没有记录ParentMenu只记录了ParentId)
其中InitParentMenu是初始化下拉框的函数(修改子菜单的父级菜单时用到,这里就不多说了)
/// <summary>
/// 构造父级菜单的combo box
/// </summary>
/// <param name="tMenu"></param>
void InitParentMenu(IEnumerable<MenuM> tMenu)
{
var rs = tMenu.ToList();
var TM = new MenuM();
TM.MenuName = "请选择";
TM.Id = Guid.Empty;
rs.Insert(0, TM);
TMenuCB.ItemsSource = rs;
TMenuCB.SelectedIndex = 0;
}
-----------------------------------------------
InitTypeCB是构造可以使用的菜单路径(下拉框)的函数
void InitTypeCB()
{
var tys = Application.Current.GetType().Assembly.GetTypes().ToList();
var results = tys.Where(m => m.IsPublic
&& m.FullName.StartsWith("RTMDemo.Frame.Pages")
&& !m.FullName.EndsWith(".BasePage"))
.Select(m=>m.FullName.TrimStart("RTMDemo.Frame.".ToArray()))
.ToList();
results.Insert(0,"请选择");
MenuUrlCB.ItemsSource = results;
MenuUrlCB.UpdateLayout();
MenuUrlCB.SelectedIndex = 0;
}
此函数反射出了所有业务窗体的类名,并赋值给了一个ComboBox,以供选择
---------------------------------------------------------------
当选中菜单树中的某一项时执行如下事件
private void MenuTV_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var item = MenuTV.SelectedItem as TreeViewItem;
var menuObj = item.DataContext as MenuM;
var fobj = Common.Utility.DeepCopy(menuObj);
MenuFormG.DataContext = fobj;
var parent = Common.ViewUtility.AllMenu
.Where(m => m.Id == menuObj.ParentId)
.FirstOrDefault();
TMenuCB.SelectedItem = (parent == null ? TMenuCB.Items.FirstOrDefault() : parent);
MenuUrlCB.SelectedItem = (string.IsNullOrEmpty(menuObj.Url) ? "请选择" : menuObj.Url);
}
因为MenuFormG内的数据绑定元素基本上都是使用的双向绑定(更改会直接反应在实体上)
所以我们深拷贝了一个实体提供给表单(这样就不会影响现有实体的数据)
技巧:深拷贝其实就是执行了一次序列化和反序列化的过程
代码如下:
public static object DeepCopy(object tar)
{
MemoryStream ms = new MemoryStream();
var jsonSerializer = new DataContractJsonSerializer(tar.GetType());
jsonSerializer.WriteObject(ms, tar);
var result = jsonSerializer.ReadObject(ms);
return result;
}
------------------------------------------------------
下面我们来看一下增加一个菜单的方法
private void AddBTN_Click(object sender, RoutedEventArgs e)
{
var obj = MenuFormG.DataContext as MenuM;
if (FormHasError(MenuFormG))
{
Common.ViewUtility.Alert("数据有误不能提交");
return;
}
var ms = new MenuService();
obj.Id = Guid.NewGuid();
obj.ParentId = (TMenuCB.SelectedItem as MenuM).Id;
ms.Completed += new ServiceEventHandler((o, se) =>
{
Common.ViewUtility.Alert("增加成功");
Common.ViewUtility.AllMenu.Add(obj);
Reload();
});
ms.AddMenu(obj);
}
验证客户端输入的数据是否正确的方法,是基类提供的
protected bool FormHasError(DependencyObject form)
{
var items = form.GetVisuals();
foreach (var formItem in items)
{
if (Validation.GetHasError(formItem))
{
((Control)formItem).Focus();
return true;
}
}
return false;
}
public static IEnumerable<DependencyObject> GetVisuals(this DependencyObject root)
{
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
foreach (var descendants in child.GetVisuals())
{
yield return descendants;
}
}
}
如果某一个菜单项含有错误信息,那么将验证不通过。
Reload方法也是基类提供的
protected void Reload()
{
var t = this.GetType();
var ti = this.Parent as TabItem;
var menuObj = ti.DataContext as MenuM;
var tc = ti.Parent as TabControl;
tc.Items.Remove(ti);
var obj = Activator.CreateInstance(t);
ti = new Controls.PageContainer();
ti.DataContext = menuObj;
ti.Header = menuObj.MenuName;
ti.Content = obj;
tc.Items.Add(ti);
tc.SelectedItem = ti;
}
此函数也结合前面的章节来看。
--------------------------------------
至此本系列全部写完了!