精华内容
下载资源
问答
  • 本人借鉴了wpf treeview控件绑定数据的例子,在winform中实现了根据节点信息生成的功能,希望能给别人的开发带来启示,欢迎批评指正。
  • 由于项目需要一个层级列表的展现,在网上找了一款控件ObjectListView,里面的TreeDataView组件实现了层级列表的展现 ![在这里插入图片描述]...

    由于项目需要一个树层级列表的展现,在网上找了一款控件ObjectListView,里面的TreeDataView组件实现了层级列表的展现
    在这里插入图片描述
    http://objectlistview.sourceforge.net/cs/index.html 官网地址,可下载源代码,源代码里面有demo。

    • 使用方法: 将ObjectListView.dll文件或者ObjectListVIew项目 导入到解决方案中。代码如下
    • 创建树(ProtoFrameInfo是根据需求定义的实体类)
     public void SetupTree(){
     			//目录是否存在子集
                this.proTreeListView.CanExpandGetter = delegate(object x)
                {
                    return ((ProtoFrameInfo)x).IsRef;
                };
                //可以展开的获取此条数据的子集。
                this.proTreeListView.ChildrenGetter = delegate(object x)
                {
                    try
                    {
                        return ((ProtoFrameInfo)x).GetFrameInfos();
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        this.BeginInvoke((MethodInvoker)delegate()
                        {
                            this.proTreeListView.Collapse(x);
                            MessageBox.Show(this, ex.Message, "ObjectListViewDemo", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        });
                        return new ArrayList();
                    }
                };
                //绑定根目录数据
                ArrayList roots = new ArrayList();
                for (int i = 0; i < frameList.Count; i++)
                {
                    ProtoFrameInfo pfie = new ProtoFrameInfo(frameList[i]);
                    pfie.isChild = false;
                    roots.Add(pfie);
                 }
                this.proTreeListView.Roots = roots;
            }
    
    • 这样已经完成了树的数据绑定,接下来可以具体设置每列的格式化
       this.olvColumnName.ImageGetter = delegate(object x) {
           return helper.GetImageIndex(((MyFileSystemInfo) x).FullName);
       };
       this.olvColumnSize.AspectToStringConverter = delegate(object x) {
       ...
       };
       this.olvColumnFileType.AspectGetter = delegate(object x) {
       ...
       };
    
    • 设置行
     private void SetupRows()
            {
                this.proTreeListView.RowFormatter = delegate(OLVListItem olvItem)
                {
                    ProtoFrameInfo e = (ProtoFrameInfo)olvItem.RowObject;
                    if (!e.isChild)
                    {
                        string result = protocolBusiness.visualData(e.Info.XmlText, e.Info, frameList);
                        if (!"".Equals(result))
                        {
                            e.Info.isRight = 0;
                            string rr = protocolBusiness.visualData(e.Info.XmlText, e.Info, frameList);
                            //针对不正确的数据单独设置行背景色
                            olvItem.BackColor = Color.FromArgb(255, 204, 205);
                        }
                    }
                };
            }
    
    //添加节点
    public void addNode(){
               ProtoFrameInfo pfi = new ProtoFrameInfo(e);
               pfi.isChild = false;
               this.proTreeListView.AddObject(pfi);
               this.proTreeListView.SelectObject(pfi, true);
     }
     //选中事件
      private void proTreeListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
           {
               OLVListItem ele = this.proTreeListView.SelectedItem;
               ProtoFrameInfo pf = (ProtoFrameInfo)ele.RowObject;
               if (!pf.isChild)
               {
                   ProtoDataControl pdc = new ProtoDataControl(pf.Info, frameList, _readOnly);
                   pdc.Dock = DockStyle.Fill;
                   _panelProperty.Controls.Clear();
                   _panelProperty.Controls.Add(pdc);
               }
           }
    
    • 设置背景颜色
      this.proTreeListView.SelectedBackColor = Color.FromArgb(187, 225, 204);
      this.proTreeListView.UnfocusedSelectedBackColor = Color.FromArgb(187, 225, 204);

    • 设置线颜色
      TreeListView.TreeRenderer renderer = this.proTreeListView.TreeColumnRenderer;
      renderer.LinePen = new Pen(Color.FromArgb(204, 204, 204), 0.5f);
      renderer.LinePen.DashStyle = DashStyle.Dot;

    • 注意:需要将HideSelection 属性设置为false,才能设置选中某行数据。
      可将UseHotControls设置为false,则在滑动时不会触发动态刷新重绘列表

    在使用时发现

         /// <summary>
        /// 解决动态添加控件时的闪烁问题
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x02000000;
                return cp;
            }
        }
    

    此方法会影响该控件的重绘方法,造成重绘树级列表时一直闪烁,研究控件源代码后发现是这段方法的问题:
    ObjectListView.cs
    在这里插入图片描述
    将此句话注释掉则可解决此问题。

    展开全文
  • 我在做Winform界面的时候,一般都是统一化处理,界面顶部放置一些字段条件供查询,下面就是分页查询列表,展示相关的数据。但有时候碰到一些表字段内容分类比较多,有一些...1、标准WInform列表界面 标准的查询条...

    我在做Winform界面的时候,一般都是统一化处理,界面顶部放置一些字段条件供查询,下面就是分页查询列表,展示相关的数据。但有时候碰到一些表字段内容分类比较多,有一些特别重要,如果放在一个树形列表来进行快速分类查询,用户体验应该更好。本篇随笔主要介绍如何快速实现树形列表和分页查询整合的WInform程序界面。

    1、标准WInform列表界面

    标准的查询条件+列表数据展示的WInform界面如下所示。

    这个界面主要就是通过代码生成工具(Database2Sharp)进行初期的Winform界面生成即可。要了解具体如何生成标准的列表界面和数据编辑界面,查看下面两篇操作介绍随笔即可。

    利用代码生成工具Database2Sharp设计数据编辑界面》、《代码生成工具之Winform查询列表界面生成》。

    2、树形列表和分页查询整合的Winform程序界面

    以上的界面有时候感觉不够友好,正如文章开头说到,我需要在左边放置一些重要的数据分类进行查询,这样能够提高用户体验效果,最终希望的界面效果如下所示。

    为了实现这种效果,我们需要进行几部操作。

    1)在标准列表界面上增加窗口分割控件(如DevExpress的是SplitContainerControl控件)

     传统的Winform界面可以使用SplitContainer控件

    在现有已生成界面的基础上,把查询部分和列表部分的控件拖动小一点,然后把上述分隔控件拖动到界面后,在右边面板放入已有的查询和分页控件部分的内容,中间状态的列表界面效果如下所示。

    然后在左边放入一个GroupControl控件,并加入树形控件TreeView,这样我们调整后的设计界面效果如下所示。

    首先我们需要在代码里面绑定树的初始化代码,生成需要快速查询的内容,示意代码如下所示。主要逻辑思路就是,从数据字典中检索相关的分类,然后绑定一些查询条件,方便后面的处理。

            private void InitTree()
            {
                base.LoginUserInfo = Cache.Instance["LoginUserInfo"] as LoginUserInfo;
    
                this.treeView1.BeginUpdate();
                this.treeView1.Nodes.Clear();
                //添加一个未分类和全部客户的组别
                TreeNode topNode = new TreeNode("所有记录", 0, 0);
                this.treeView1.Nodes.Add(topNode);
    
                TreeNode CategoryNode = new TreeNode("客户活动类别", 2, 2);
                this.treeView1.Nodes.Add(CategoryNode);
                AddDictData(CategoryNode, 0, "Category");
    
                TreeNode OrderYearNode = new TreeNode("记录年度", 8, 8);
                this.treeView1.Nodes.Add(OrderYearNode);
                List<string> yearList = BLLFactory<Maintenace>.Instance.GetYearList();
                foreach (string year in yearList)
                {
                    TreeNode subNode = new TreeNode(year, 0, 0);
                    subNode.Tag = year;
                    OrderYearNode.Nodes.Add(subNode);
                }
    
    
                this.treeView1.ExpandAll();
                this.treeView1.EndUpdate();
            }

    为了处理树形列表的节点的单击事件,我们可以在其AfterSelect事件进行处理,示意代码如下所示。主要逻辑就是根据及节点和条件的不同,进行不同的处理。

            string treeConditionSql = "";
            private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
            {
                if (e.Node != null && e.Node.Tag != null)
                {
                    if (e.Node.FullPath.Contains("记录年度"))
                    {
                        int year = Convert.ToInt32(e.Node.Tag.ToString());
                        SearchCondition condition = new SearchCondition();
                        condition.AddCondition("StartTime", Convert.ToDateTime(string.Format("{0}-01-01", year)), SqlOperator.MoreThanOrEqual);
                        condition.AddCondition("StartTime", Convert.ToDateTime(string.Format("{0}-01-01", year + 1)), SqlOperator.LessThan);
                        treeConditionSql = condition.BuildConditionSql().Replace("Where", "");
                        BindData();
                    }
                    else
                    {
                        treeConditionSql = e.Node.Tag.ToString();
                        BindData();
                    }
                }
                else
                {
                    treeConditionSql = "";
                    BindData();
                }
            }

    上面的代码,我们定义了一个局部变量treeConditionSql 用来存储树列表单击后的条件,触发单击事件后,我们最终还是传回给标准列表界面已有的查询操作--BindData函数进行处理。

    BindData里面最主要的操作就是构造查询条件,构造条件的语句如下所示,通过SearchCondition对象处理进行使用多数据库的兼容处理。

            /// <summary>
            /// 根据查询条件构造查询语句
            /// </summary> 
            private string GetConditionSql()
            {
                //如果存在高级查询对象信息,则使用高级查询条件,否则使用主表条件查询
                SearchCondition condition = advanceCondition;
                if (condition == null)
                {
                    condition = new SearchCondition();
                    condition.AddCondition("Category", this.txtCategory.Text.Trim(), SqlOperator.Like);
                    condition.AddCondition("Title", this.txtTitle.Text.Trim(), SqlOperator.Like);
                    condition.AddDateCondition("StartTime", this.txtStartTime1, this.txtStartTime2); //日期类型
                    condition.AddCondition("Contact", this.txtContact.Text.Trim(), SqlOperator.Like);
                    condition.AddCondition("Place", this.txtPlace.Text.Trim(), SqlOperator.Like);
                }
                string where = condition.BuildConditionSql().Replace("Where", "");
                //如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的
                if (!string.IsNullOrEmpty(treeConditionSql))
                {
                    where = treeConditionSql;
                } 
    
                return where;
            }

    最终绑定数据的函数BindData的逻辑代码如下所示。

            /// <summary>
            /// 绑定列表数据
            /// </summary>
            private void BindData()
            {
                //entity
                this.winGridViewPager1.DisplayColumns = "Customer_ID,HandNo,Category,Title,Content,StartTime,EndTime,Contact,ContactPhone,ContactMobile,Place,PlaceAddress,PlacePhone,Note,Editor,EditTime";
                this.winGridViewPager1.ColumnNameAlias = BLLFactory<Activity>.Instance.GetColumnNameAlias();//字段列显示名称转义
    
                string where = GetConditionSql();
                List<ActivityInfo> list = BLLFactory<Activity>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo);
                this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<ActivityInfo>(list);
                this.winGridViewPager1.PrintTitle = "客户活动管理报表";
            }

    这样我们就完成了树形列表和分页查询整合一起的数据查询处理逻辑,从而实现我们说需要的结果,这样的界面在某种程度上,给我们提供更多的方便,更好的体验。

    本文转自博客园伍华聪的博客,原文链接:如何快速开发树形列表和分页查询整合的WInform程序界面,如需转载请自行联系原博主。



    展开全文
  • Winform开发主界面菜单的动态列表展示 我在之前很多文章里面,介绍过Winform主界面的开发,基本上都是标准的界面,在顶部放置工具栏,中间区域则放置多文档的内容,但是在顶部菜单比较多的时候,就需要把菜单...

    Winform开发主界面菜单的动态树形列表展示

    我在之前很多文章里面,介绍过Winform主界面的开发,基本上都是标准的界面,在顶部放置工具栏,中间区域则放置多文档的内容,但是在顶部菜单比较多的时候,就需要把菜单分为几级处理,如可以在顶部菜单放置一二级菜单,这种方式在一般功能点不算太多的情况下,呈现的界面效果较为直观、也较为美观。不过随着一些系统功能的增多,这种方式可能就会显得工具栏比较拥挤,那么我们是否可以在左侧放置一个树形列表,这样通过树形列表的收缩折叠,就可以放置非常多的菜单功能了。

    1、菜单的树形列表展示

    一般情况下,树形列表的显示可以分为多个节点,节点可以收缩也可以展开,当然节点是有不同的图标的了。这样就可以把很多功能点整合在一个树列表里面了,树的节点也可以分为很多级别,很多层次

      

    如果我们想按照业务的范畴来区分,也可以分为多个模块展示,类似选项卡的方式,一个模块的功能菜单列表集合在一起展示,如下所示。

    上面这样的折叠展示,有利于业务范畴的区分,并且可以让树菜单菜单不会很大,是一种比较好的界面组织方式。

     

    2、菜单的动态配置管理

    上面介绍了树形菜单的展示,以及如何组织菜单的内容,做好这些,就为我们奠定了界面菜单组织的雏形了。 

    那么问题来了,我们一般是需要根据系统创建很多菜单的,如果是能通过配置的方式,这样才能较好的管理这些菜单,而且可以动态给菜单指定权限,实现不同角色用户的权限控制。

    那么我们就需要在系统里面引入一个菜单管理模块,实现菜单的配置管理功能,方便我们后面的动态创建菜单操作。

    通过菜单的配置,我们可以指定菜单的图标,是否可见,是否展开,权限控制点,以及菜单触发点击后,处理的窗体对象等信息,有了这些基础信息,我们就很方便把菜单在树形列表里面进行合适、美观的展示了。

     

    3、菜单动态构建的实现

    前面介绍了,如何在数据库里面对菜单数据进行了存储,这样我们就可以在系统主界面里面,动态的构建属性列表进行菜单的展示操作了。

    首先,我们需要在设计时刻对主界面的布局进行一定的设计,放置一些初始化的树形列表,方便查看效果。至于里面的内容,我们可以根据数据库的菜单配置,动态从数据库里面获取菜单信息,在左侧树形列表里面进行构建。

    我们可以通过一个辅助类进行菜单的动态创建,如下所示。

            private void InitToolbar()
            {
                TreeMenuHelper helper = new TreeMenuHelper(this, this.nvBarMenu, this.imageList1);
                helper.Init();
            }

    也就是辅助类,传入当前窗体,以及左侧的导航控件等参数后,我们在辅助类里面封装对应的动态构建菜单的逻辑处理。

    首先我们动态创建的开始,先要清空原来控件展示的菜单内容,并重新从数据库里面获取,如下代码所示。

    复制代码

                //清空所有导航控件的内容
                barControl.Controls.Clear();
                barControl.Groups.Clear();
                barControl.Items.Clear();
                this.imageList.Images.Clear();
    
                //限定显示几个导航选项卡
                barControl.NavigationPaneMaxVisibleGroups = 3;
    
                //约定菜单共有3级,第一级为大的类别,第二级为小模块分组,第三级为具体的菜单
                List<MenuNodeInfo> menuList = BLLFactory<SysMenu>.Instance.GetTree(Portal.gc.SystemType);
                if (menuList.Count == 0) return;

    复制代码

    然后我们会对菜单进行遍历,并判断是否具有对应的权限点,如果没有对应的权限,那么对应菜单的子菜单也不会进一步展示。

                //递归遍历所有的菜单,进行分级展示
                foreach (MenuNodeInfo firstInfo in menuList)
                {
                    //如果没有菜单的权限,则跳过
                    if (!Portal.gc.HasFunction(firstInfo.FunctionId)) continue;

    创建菜单的时候,我们注意到整个菜单项是动态构建的,因此我们需要根据NavBarControl的控件属性,动态构建对应的选项卡NavBarGroup、展示容器NavBarGroup、树形对象TreeView、树形节点TreeNode等内容,如下代码所示。

    复制代码

                    TreeView treeView = new TreeView();
                    treeView.Dock = DockStyle.Fill;
                    treeView.ImageList = this.imageList;
                    treeView.ItemHeight = 30;//设置高度,显示更美观
    
                    NavBarGroupControlContainer container = new NavBarGroupControlContainer();
                    container.Size = new System.Drawing.Size(213, 412);
                    container.Controls.Add(treeView);
                    barControl.Controls.Add(container);
    
                    //加载图标
                    this.imageList.Images.Add(LoadIcon(firstInfo.Icon));
                    int index = this.imageList.Images.Count - 1;//最后一个序号
    
                    NavBarGroup group = new NavBarGroup();
                    group.Caption = firstInfo.Name;
                    group.ControlContainer = container;
                    group.Expanded = true;
                    group.GroupClientHeight = 410;
                    group.GroupStyle = NavBarGroupStyle.ControlContainer;
                    group.LargeImageIndex = index;
                    group.SmallImageIndex = index;
                    barControl.Groups.Add(group);
    
                    //创建一级列表
                    TreeNode pNode = new TreeNode();
                    pNode.Text = firstInfo.Name;
                    pNode.Tag = firstInfo.WinformType;
                    pNode.ImageIndex = index;
                    pNode.SelectedImageIndex = index;
                    treeView.Nodes.Add(pNode);
    
                    //递归创建子列表
                    AddTreeItems(pNode, firstInfo.Children);

    复制代码

    通过递归的方式,我们就很容易递归构建了所有层次的树形菜单,并进行合适的展示了。

    菜单的单击事件,我们通过一个函数代码实现对它进行处理就可以了。

    复制代码

                    //处理树形菜单的点击操作,如果TAG存在,则解析并加载对应的页面到多文档里面
                    treeView.AfterSelect += (sender, e) =>
                    {
                        string tag = e.Node.Tag as string;
                        if (!string.IsNullOrEmpty(tag))
                        {
                            LoadPlugInForm(tag);
                        }
                    };

    复制代码

    这里面就是对它的AfterSelect 事件进行处理,实现我们动态加载窗体对象到多文档界面的处理了。

    其中加载窗体是根据菜单配置的选项,动态构建界面出来的,具体分析代码如下所示。

     

    复制代码

            /// <summary>
            /// 加载插件窗体
            /// </summary>
            private void LoadPlugInForm(string typeName)
            {
                try
                {
                    string[] itemArray = typeName.Split(new char[] { ',', ';' });
    
                    string type = itemArray[0].Trim();
                    string filePath = itemArray[1].Trim();//必须是相对路径
    
                    //判断是否配置了显示模式,默认窗体为Show非模式显示
                    string showDialog = (itemArray.Length > 2) ? itemArray[2].ToLower() : "";
                    bool isShowDialog = (showDialog == "1") || (showDialog == "dialog");
    
                    string dllFullPath = Path.Combine(Application.StartupPath, filePath);
                    Assembly tempAssembly = System.Reflection.Assembly.LoadFrom(dllFullPath);
                    if (tempAssembly != null)
                    {
                        Type objType = tempAssembly.GetType(type);
                        if (objType != null)
                        {
                            LoadMdiForm(this.mainForm, objType, isShowDialog);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogTextHelper.Error(string.Format("加载模块【{0}】失败,请检查书写是否正确。", typeName), ex);
                }
            }

    复制代码

     

    加载多文档的操作,就是在集合里面判断是否存在,如果没有存在就创建,否则就激活显示即可,具体处理如下所示。

    复制代码

            /// <summary>
            /// 唯一加载某个类型的窗体,如果存在则显示,否则创建。
            /// </summary>
            /// <param name="mainDialog">主窗体对象</param>
            /// <param name="formType">待显示的窗体类型</param>
            /// <returns></returns>
            public Form LoadMdiForm(Form mainDialog, Type formType, bool isShowDialog)
            {
                Form tableForm = null;
                bool bFound = false;
                if (!isShowDialog) //如果是模态窗口,跳过
                {
                    foreach (Form form in mainDialog.MdiChildren)
                    {
                        if (form.GetType() == formType)
                        {
                            bFound = true;
                            tableForm = form;
                            break;
                        }
                    }
                }
    
                //没有在多文档中找到或者是模态窗口,需要初始化属性
                if (!bFound || isShowDialog)
                {
                    tableForm = (Form)Activator.CreateInstance(formType);
    
                    //如果窗体集成了IFunction接口(第一次创建需要设置)
                    IFunction function = tableForm as IFunction;
                    if (function != null)
                    {
                        //初始化权限控制信息
                        function.InitFunction(Portal.gc.LoginUserInfo, Portal.gc.FunctionDict);
    
                        //记录程序的相关信息
                        function.AppInfo = new AppInfo(Portal.gc.AppUnit, Portal.gc.AppName, Portal.gc.AppWholeName, Portal.gc.SystemType);
                    }
    
                }
    
                if (isShowDialog)
                {
                    tableForm.ShowDialog();
                }
                else
                {
                    tableForm.MdiParent = mainDialog;
                    tableForm.Show();
                }
                tableForm.BringToFront();
                tableForm.Activate();
    
                return tableForm;
            }

    复制代码

     

    4、系统界面的总体效果

    最后,为了更好理解整个动态菜单的界面效果,贴出几个做好的界面展示图,供参考学习。

    1)标准界面的处理方式

     

    2)树形列表界面的处理方式

    打开多文档页面后如下所示。

     

    主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
    专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
      转载请注明出处:
    撰写人:伍华聪  http://www.iqidi.com 
        

    展开全文
  • 在我们一般的应用系统里面,由于系统是面向不同类型的用户,我们所看到的菜单会越来越多,多一点的甚至上...在我们有些软件里面,我们可能在界面上顶部放置菜单,也可能在界面的左侧放置列表菜单,这种情况都有可能
  • 一、 菜单列表的动态个性化配置的过程 在我们有些软件里面,我们可能在界面上顶部放置菜单,也可能在界面的左侧放置列表菜单,这种情况都有可能,本篇摘取其中之一,左侧菜单进行一个介绍菜单的配置处理。...

    点击“了解更多”获取DevExpress v20.2完整版下载

    在我们一般的应用系统里面,由于系统是面向不同类型的用户,我们所看到的菜单会越来越多,多一点的甚至上百个,但是我们实际工作接触的菜单可能就是那么几个,那么对于这种庞大的菜单体系,寻找起来非常不便。因此对菜单的个性化配置就显得尤为重要,本文就是基于这样的理念,提供用户对可见菜单进行一个动态配置,只选自己喜欢、常用的菜单显示出来即可,菜单的配置存储在数据库里面,在不同的客户端体验都是一样。本文主要介绍实现这样的功能的一个完整思路,部分代码逻辑可供参考。

    一、 菜单列表的动态个性化配置的过程

    在我们有些软件里面,我们可能在界面上顶部放置菜单,也可能在界面的左侧放置树形列表菜单,这种情况都有可能,本篇摘取其中之一,左侧菜单进行一个介绍菜单的配置处理。

    例如我们在左侧根据用户权限展示相关的菜单信息,动态生成整个列表展示,大致的界面效果如下所示。

    68fb012a68289a41a53de74808a05d04.png

    然后在功能列表上提供一个右键的菜单进行菜单的刷新、配置管理,如下界面所示。

    fc3ef9062e34588686d6a24f453469db.png

    通过配置功能,我们让用户进入一个配置管理界面,在其中配置显示自己感兴趣的菜单,然后进行保存即可,保存后同时刷新界面的功能菜单显示。

    1ac414f21b1b2ebeb5e7f20d32f354f8.png

    以上几个界面效果就是为了介绍整个菜单配置管理的一般过程,之所以把界面效果放在前面介绍,就是能够让我们有一个类似原型设计方式的感性认识,了解了相关的处理过程,我们就可以着手通过编码的方式来实现这个处理逻辑了。

    二、菜单动态个性化配置的功能实现

    上面介绍了大概的界面效果,有了参考,我们可以把它的实现思路通过代码实现出来。

    1. 参数的数据存储

    首先我们需要了解,用户配置可以通过XML保存在本地,也可以通过数据库存储保存在服务器,后者在分布式的客户端的时候,可以处处一样,这样就不会造成体验上的差异,因此我们这里采用存储在数据库的方案。

    这个配置管理组件SettingsProvider.net使用起来也是比较方便的,可以选择存储在本地的对象,也可以选择存储在数据库的存储对象。

    首先我们先定义一个存储的参数类,这个是使用这个组件所必须的存储对象信息,如下代码所示。

    /// /// 用来控制人员管理显示菜单的参数配置/// public class UserMenuParameter{[DefaultValue("")][Description("用户ID")]public string UserID { get; set; }[Description("用户设置可见的菜单")]public Dictionary VisibleDict { get; set; }}

    需要获取或存储这个对象信息的时候,我们初始化几个管理类,如下代码所示。

    //参数存储所需的相关对象private SettingsProvider settings;private ISettingsStorage store;private UserMenuParameter parameter;

    然后在配置管理界面窗体里面,初始化这几个对象,如下代码所示。

    // PortableStorage: 在运行程序目录创建一个setting的文件记录参数数据// DatabaseStorage:在数据库TB_UserParameter表存储用户配置参数store = new DatabaseStorage(LoginUserInfo.ID);settings = new SettingsProvider(store);parameter = settings.GetSettings();

    这样我们就可以根据用户的ID,获取对应记录的信息并转换为相关的对象了,如果我们需要把修改的信息写会到存储介质里面,代码如下所示。

    try{parameter = settings.GetSettings();parameter.VisibleDict = dict;parameter.UserID = LoginUserInfo.ID;settings.SaveSettings(parameter);ProcessDataSaved(sender, e);//触发外部事件this.DialogResult = System.Windows.Forms.DialogResult.OK;}catch (Exception ex){LogHelper.Error(ex);MessageDxUtil.ShowError(ex.Message);return;}

    2. 配置管理界面的实现

    解决了参数的获取及存储功能后,我们需要编写一个界面来管理用户的菜单配置,也就是我们前面介绍的菜单配置管理界面。

    a06f9c593bf4a593d7517a22cc7d3e62.png

    我们这个界面的定义代码如下所示。

    eef6a163bc332526fd5173a467f870cc.png

    其中参数的数据存储就是应用了前面介绍的代码,这里需要根据用户的配置项初始化树形菜单的显示处理,通过InitTree的函数实现菜单的显示。

    在显示菜单前,我们先介绍一下功能菜单显示的规则,仅当参数存在对应记录,并且该记录显式设置不可见,菜单才不可见,否则默认菜单是可以看到的。

    这样确保了,在参数没有配置前,所有的菜单对当前用户是可见的,只有用户设置为不不可见,该菜单才不显示为不可见。

    /// /// 获取菜单是否可见。/// 仅当参数存在对应记录,并且该记录显式设置不可见,菜单才不可见,否则默认菜单是可以看到的。/// /// 菜单ID/// private bool GetVisibleMenu(string id){bool result = true;if (parameter != null){var dict = parameter.VisibleDict;if(dict != null && dict.ContainsKey(id)){result = dict[id];}}return result;

    显示菜单的相关处理逻辑,就是根据上面的判断,然后确定是否勾选记录,如下代码所示。

    cc8a06ddc64550b3e97ff26aadcb0df3.png

    存储用户勾选的记录的时候,我们需要遍历整个树节点,判断勾选了那些选项,然后把它保存数据库即可。

    /// /// 递归获取选中的树节点集合/// /// 树节点/// 字典集合/// private Dictionary GetTreeSelection(TreeNode node, Dictionary dict){if (node.Tag != null){var check = node.Checked;var menuId = string.Concat(node.Tag);if(!dict.ContainsKey(menuId)){dict.Add(menuId, check);}}foreach (TreeNode child in node.Nodes){GetTreeSelection(child, dict);}return dict;}

    参数的保存操作如下所示。

    /// /// 保存用户配置信息/// private void btnOK_Click(object sender, EventArgs e){//获取用户勾选的树列表,存放在字典集合里面var dict = new Dictionary();foreach(TreeNode node in this.treeView1.Nodes){GetTreeSelection(node, dict);}try{//重新获取参数信息,并设置新值后保存parameter = settings.GetSettings();parameter.VisibleDict = dict;parameter.UserID = LoginUserInfo.ID;settings.SaveSettings(parameter);ProcessDataSaved(sender, e);//触发外部事件this.DialogResult = System.Windows.Forms.DialogResult.OK;}catch (Exception ex){LogHelper.Error(ex);MessageDxUtil.ShowError(ex.Message);return;}}

    3. 主界面的相关处理

    以上处理完成后,我们在主界面的工具栏右键菜单添加一个菜单项,用来进入配置界面的,如下逻辑代码所示。

    private void tool_MenuSetting_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e){MenuSetting();}/// /// 配置菜单项/// private void MenuSetting(){FrmMenuSetting dlg = new FrmMenuSetting();dlg.OnDataSaved += (s, arg) =>{//用户保存参数后,提示用户更新树形列表InitToolbar();};dlg.ShowDialog();}

    这样界面配置参数并保存后,界面的树形菜单会及时得到更新处理。

    另外,我们主界面的树形列表,也要根据配置参数的信息作相关的调整,如果用户配置了不显示某个菜单,那么主界面也要根据配置参数控制显示。

    da451edfcec1add4dc7146b291159dc8.png

    3. 总结

    以上就是整个菜单列表的动态个性化配置管理的整体思路和实现步骤代码,主要的界面考量还是以用户的视觉来考虑界面的布局和功能,如果在几百个菜单项中寻找几个常用的菜单,每次是一个比较耗时无聊的操作,因此提供一个个性化的界面,根据工作情况的不同,显示一些和自己相关的功能即可。

    例如有些情况下,我们的菜单显示,希望通过工具栏的方式进行控制显示,如下界面效果所示。

    7cea54c2352471b8a2cef3fc86aeff2a.png

    那么配置维护界面还是差不多,只是我们控制工具栏的显示逻辑有所不同而已,对于RibbonPage及其功能菜单的动态生成处理如下所示。

    cc19ebb2d4870c1d112894d3c01e9690.png

    本文主要还是希望读者借鉴配置存储和菜单个性化管理的思路,具体的逻辑会因用户界面的不同,使用的控件不同而有所差异,不过总体思路是一致的即可。

    例如有些参数的配置管理,可以统一使用一个配置管理界面进行维护,如我之前的随笔介绍的界面功能一样。

    4e698b617b3f6e6e1846cf93fe37d3aa.png

    本文转载自博客园-伍华聪

    展开全文
  • WinForm界面开发

    千次阅读 2014-04-11 21:29:39
    如何快速开发列表和分页查询整合的WInform程序界面 伍华聪 2014-02-17 21:36 阅读:1905 评论:4  邮件代收代发功能模块的操作界面设计和阶段性总结 伍华聪 2014-0
  • WinForm窗体 Button 按钮 Textbox 文本框 ComboBox 下拉列表框 Checkbox 复选框 RadioButton 单选框 GroupBox 分组控件 Label 标签 ListBox 列表框 PictureBox 图片框 DateTimePicker 日期时间控件 ...
  • 网络采集软件核心技术剖析系列(7)---如何使用C#语言搭建程序框架(经典Winform界面,菜单栏,列表,多Tab界面) 网络采集软件核心技术剖析系列(综合实例) 二 第七节主要内容简介(如何使用C#语言搭建...
  • 扩展WinForm的ComboBox

    2009-04-26 00:33:00
    个人认为winform的combobox不是那么的好用,所以自己扩展了一下。 重新定义Items属性,并且支持结构。 为每项加入了CheckBox状态。 丰富的列表项类ListItem。 效果如图: 代码清单: usingSystem; usingSystem....
  • Demo包含一些自定义控件(下拉列表列表 , 图片列表等)均可实现透明效果。主窗体是一个海康相机的监控视频。使用时可替换成其他视频。
  • DevExpress提供的列表控件TreeList和形下拉列表控件TreeListLookupEdit都是非常强大的一个控件,它和我们传统Winform的TreeView控件使用上有所不同,我一般在Winform开发中根据情况混合使用这些控件,不过整体...
  • 对于前面21章的学习内容来说,稍微有点陌生,因为不管是Xaml的构建UI的方式还是依赖项属性,路由事件,包括命令都和我们一开始的winform不一样,但是有了之前的基础,再回来看ListboxView,Treeview,Datagridview,...
  • Winform和Web网站的动态添加方式有所不同。 我前面的帖子中有介绍,有兴趣的童鞋... /// 递归加载角色树列表 ///  /// 节点的fatherid  /// 该节点  private void addTree(string fatherid, TreeNode pnode)
  • DevExpress提供的列表控件TreeList和形下拉列表控件TreeListLookupEdit都是非常强大的一个控件,它和我们传统Winform的TreeView控件使用上有所不同,我一般在Winform开发中根据情况混合使用这些控件,不过整体...
  • 丰富的图标处理,在菜单、工具栏、树列表等地方,以及按钮等地方,都可以使用,而这些我们可以利用DevExpress的内置图标选择来减轻我们寻找合适图标的烦恼。 一些按钮、工具栏等的图标设置,一般是固定的,我们往往...
  • 原文:Winform 实现像菜单一样弹出层在实际工作中,如果能像菜单一样弹出自定义内容,会方便很多,比如查询时,比如下拉列表显示多列信息时,比如在填写某个信息需要查看一些信息时。这个时候自定义弹出界面就显...
  • 问题描述:我左边的形结构选择改变的时候,再选择回来后发现子列表为空白了,跟踪测试发现,当我当前选择项改变时,子列表里面的数据被清空 这个问题让我头疼了一个下午,最终我发现问题出在datasource.clear...
  • DevExpress Winforms Controls内置140多个UI...介绍了在列表TreeList控件上面,利用SearchControl实现节点的模糊查询过滤操作,效果还是非常不错的,TreeList功能比较强大,界面也相对比微软内置的Winform的Tree.
  • 在实际工作中,如果能像菜单一样弹出自定义内容,会方便很多,比如查询时,比如下拉列表显示多列信息时,比如在填写某个信息需要查看一些信息时。这个时候自定义弹出界面就显的非常重要了
  • 在很多情况下,我们需要通过树列表进行数据的展示,如一些有层次关系的数据,通过有层级的展示,能够使用户更加直观查看和管理相关的数据。在一般Winform开发的情况下,可以使用微软的TreeView控件,也可以使用...

空空如也

空空如也

1 2 3 4 5
收藏数 81
精华内容 32
关键字:

winform列表树