• 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;
            }
     

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

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

    至此本系列全部写完了!

    源码下载

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

  • 相关阅读:
    Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)
    终于有人把“TCC分布式事务”实现原理讲明白了
    Java synchronized 关键字的实现原理
    Synchronized的实现原理(汇总)
    Spring的Bean的生命周期(大众版)
    Synchronized与Lock的区别与应用场景
    Lock与synchronized 的区别
    线程的同步控制synchronized和lock的对比和区别
    lock和synchronized的同步区别与选择
    Mybatis3.x与Spring4.x整合
  • 原文地址:https://www.cnblogs.com/liulun/p/2337163.html
Copyright © 2020-2023  润新知