Silverlight企业应用框架设计【六】自定义系统菜单(使用自己的DataForm)

索引

SilverLight企业应用框架设计【五】客户端调用服务端(使用JSON传递数据,自己实现RESTful Web服务)

SilverLight企业应用框架设计【四】实体层设计+为客户端动态生成服务代理(自己实现RiaService)

SilverLight企业应用框架设计【三】服务端设计

SilverLight企业应用框架设计【二】框架画面

SilverLight企业应用框架设计【一】整体说明

 

首先我们设计的窗体如下

image

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类

image

这也是为什么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;

        }
 

此函数也结合前面的章节来看。

--------------------------------------

至此本系列全部写完了!

源码下载

喜欢的请点推荐,支持我的文章。谢谢各位啦

你可能感兴趣的:(silverlight)