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

此函數也結合前面的章節來看。

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

至此本系列全部寫完了!

源碼下載

喜歡的請點推薦,支持我的文章。謝謝各位啦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM