实现一个简单的侧边导航Winform程序框架

  • A+
所属分类:.NET技术
摘要

每次新项目都要想着界面怎么设计好,但想来想去上位机界面就那几种,按照导航方式可分为:菜单工具栏导航、汉堡包导航、侧边导航等。我用的最多的是侧边导航,导航菜单一般只有一级(最多二级),三级导航菜单基本很少用到。
本文实现一个简单的侧边导航Winform程序框架,以后开发项目可以直接用,话不多说上图:

简介

每次新项目都要想着界面怎么设计好,但想来想去上位机界面就那几种,按照导航方式可分为:菜单工具栏导航、汉堡包导航、侧边导航等。我用的最多的是侧边导航,导航菜单一般只有一级(最多二级),三级导航菜单基本很少用到。
本文实现一个简单的侧边导航Winform程序框架,以后开发项目可以直接用,话不多说上图:
实现一个简单的侧边导航Winform程序框架

整个程序界面分为上、中、下三个区域,分别是:

  • 标题区:显示软件的名称、LOG、版本等信息,实现窗体标题栏的基本功能。
  • 导航区:显示整个软件的主要内容,即导航菜单和显示面板。
  • 状态区:显示软件的状态信息,如用户、报警、日志等,此状态栏不是必须的,重要的状态信息也可以在标题栏显示。

整个界面的实现可以分为三个部分:实现窗体标题、实现导航面板、调整界面布局,主要的技术难点(或者说工作任务)集中在前面两个部分,最终核心还是在于导航面板的实现。
下面将按重要程度依次介绍各个部分的实现过程,而且所有内容都尽量不使用VS的UI设计器,通过代码来控制所有控件的属性。

实现导航面板

为了代码复用,导航面板封装为一个用户控件,主要功能就是点击导航菜单后显示指定的界面,使用时直接传入菜单项名称和对应的窗体实例即可。

实现方法

新建一个用户控件命名为LeftNavigation,在用户控件上添加SplitContainer控件,不需要设置任何属性,后台代码如下:

public partial class LeftNavigation : UserControl {     /// <summary>     /// 生成菜单按钮的方法,可以自己设置按钮的风格、属性,主要按钮的父类是Control即可     /// </summary>     public Func<Control> CreateBtnFunc=()=> { return null; };      /// <summary>     /// 菜单按钮的点击事件,在处理完导航内容的显示任务后触发     /// </summary>     public event EventHandler BtnClick;      /// <summary>     /// 获取或设置菜单按钮的背景颜色     /// </summary>     public Color BtnBackColor{ get; set; } = Color.FromArgb(30, 56, 83);      /// <summary>     /// 获取或设置菜单按钮的选中颜色     /// </summary>     public Color BtnSelectkColor { get; set; } = Color.FromArgb(67, 92, 200);      /// <summary>     /// 获取菜单按钮的大小,这个属性基本上很少变动,所以只支持在构造函数的参数中设置该值     /// </summary>     public Size BtnSize { get;}      /// <summary>     /// 获取菜单按钮之间、菜单按钮与面板边缘之间的间距,该值也作为拆分器的间隔设定值。     /// 这个属性基本上很少变动,所以只支持在构造函数的参数中设置该值。     /// </summary>     public int BtnInterval { get;}      /// <summary>     /// 构造函数     /// </summary>     /// <param name="btnWidth">菜单按钮的宽度</param>     /// <param name="btnHeight">菜单按钮的高度</param>     /// <param name="btnInterval">间距值</param>     public LeftNavigation(int btnWidth=198, int btnHeight=66, int btnInterval=5)     {         InitializeComponent();         this.Dock = DockStyle.Fill;         //用户控件太小时,splitContainer尺寸调整会失效         this.Size = new Size(1000, 1000);          BtnSize = new Size(btnWidth, btnHeight);         BtnInterval = btnInterval;          splitContainer1.Dock = DockStyle.Fill;         splitContainer1.SplitterWidth = BtnInterval;         splitContainer1.SplitterDistance = BtnSize.Width + BtnInterval * 2;                     splitContainer1.IsSplitterFixed = true;                   splitContainer1.FixedPanel = FixedPanel.Panel1;         splitContainer1.Panel1.AutoScroll = true;         splitContainer1.Panel2.AutoScroll = true;          splitContainer1.Panel1.BackColor = Color.White;     }       /// <summary>     /// 菜单按钮点击事件处理     /// </summary>     /// <param name="sender"></param>     /// <param name="e"></param>     private void button_Click(object sender, EventArgs e)     {         Control btnCtr = sender as Control;         foreach (var item in splitContainer1.Panel1.Controls)         {             Control btnItem = item as Control;             Form form = btnItem.Tag as Form;             if (btnItem == btnCtr)             {                 btnItem.BackColor = BtnSelectkColor;                 splitContainer1.Panel2.Controls.Clear();                 if (form != null)                  {                                             splitContainer1.Panel2.Controls.Add(form);                     form.Visible = true;                 }             }             else              {                 btnItem.BackColor = BtnBackColor;                 if (form != null) form.Visible = false;             }         }          if (BtnClick != null) BtnClick(sender, e);       }      /// <summary>     /// 生成菜单按钮     /// </summary>     /// <param name="text">显示的文本</param>     /// <param name="tag">对应的窗体实例</param>     /// <param name="point">按钮位置</param>     /// <returns></returns>     private Control CreateBtnCtr(string text,Form tag,Point point)     {         Control btnCtr= CreateBtnFunc();         if (btnCtr == null)          {             Button btn = new Button();             btn.BackColor = BtnBackColor;             btn.Font = new Font("Tahoma", 20, FontStyle.Bold);             btn.ForeColor = Color.White;             btn.FlatStyle = FlatStyle.Flat;                             btnCtr = btn;                      }          btnCtr.Click += button_Click;         btnCtr.Text = text;         btnCtr.Tag = tag;         btnCtr.Size = BtnSize;         btnCtr.Location = new Point(point.X, point.Y);          return btnCtr;     }         /// <summary>     /// 设置导航信息     /// </summary>     /// <param name="btnDic"></param>     public void SetNavigation(Dictionary<string,Form> btnDic)     {                     splitContainer1.Panel1.Controls.Clear();         splitContainer1.Panel2.Controls.Clear();          Point point = new Point(BtnInterval, BtnInterval);         foreach (var item in btnDic)         {             if (item.Value != null)              {                 item.Value.TopLevel = false;                 item.Value.FormBorderStyle = FormBorderStyle.None;                 item.Value.Dock = DockStyle.Fill;                 item.Value.AutoScroll = true;                 item.Value.Show();             }             if (!string.IsNullOrEmpty(item.Key))              {                 Control btnCtr = CreateBtnCtr(item.Key, item.Value, point);                 point.Y = point.Y + BtnSize.Height + BtnInterval;                 splitContainer1.Panel1.Controls.Add(btnCtr);             }                   }                     if (splitContainer1.Panel1.Controls.Count > 0)         {             button_Click(splitContainer1.Panel1.Controls[0], new EventArgs());         }     } } 

有以下几点需要注意:

  • 为了使用方便,导航面板不支持随意调整导航菜单的宽度,只能在初始化时一次性指定。
  • 为了简化逻辑,窗体实例绑定在按钮的Tag属性上,每次点击需要循环点击遍历控件,此处可以根据自己的需求进行优化。
  • 如果想在导航的子窗体显示时做一些操作,建议在窗体的VisibleChanged事件中进行。

使用方法

使用起来也很简单,在主界面中添加一个Panel控件命名为panel_Navigation,直接在主窗体的构造函数里面添加如下代码:

LeftNavigation leftNavigation = new LeftNavigation(); Dictionary<string, Form> btnFormDic = new Dictionary<string, Form>() {     {"form1",new Form1()},     {"form2",new Form2()},     {"form3",new Form3()},     {"退出",null} };  leftNavigation.SetNavigation(btnFormDic); leftNavigation.BtnClick += (sender, e) => {     Control ctr = sender as Control;     if (ctr.Text == "退出")     {         this.Close();     } };  panel_Navigation.Controls.Add(leftNavigation); 

实现标题栏

标题栏的功能和系统窗体的标题栏功能类似,主要有窗体拖拽及最大化、自定义窗体按钮两个功能。自定义的标题栏拥有极大的可操作性,设计窗体时还是很有必要的。
先将主窗体重命名为MainForm,然后在主窗体中添加一个Panel控件命名为panel_Title

窗体拖拽及最大化

在主界面的后台代码中实现窗体拖拽及最大化功能,代码如下:

#region 无边框窗体移动及最大化  // 鼠标按下 private bool isMouse = false; // 鼠标是否按下 // 原点位置 private int originX = 0; private int originY = 0; // 鼠标按下位置 private int mouseX = 0; private int mouseY = 0; private void windowMove_MouseDown(object sender, MouseEventArgs e) {     if (e.Button == MouseButtons.Left)     { // 判断鼠标按键         isMouse = true;         // 屏幕坐标位置         originX = this.Location.X;         originY = this.Location.Y;         // 鼠标按下位置         mouseX = originX + e.X;         mouseY = originY + e.Y;     } }  // 鼠标移动 private void windowMove_MouseMove(object sender, MouseEventArgs e) {     if (isMouse)     {         // 移动距离         int moveX = (e.X + this.Location.X) - mouseX;         int moveY = (e.Y + this.Location.Y) - mouseY;         int targetX = originX + moveX;         int targetY = originY + moveY;         this.Location = new Point(targetX, targetY);     } }  // 鼠标释放 private void windowMove_MouseUp(object sender, MouseEventArgs e) {     if (isMouse)     {         isMouse = false;     } }  // 鼠标双击 private void windowMove_DoubleClick(object sender, MouseEventArgs e) {     if (isMouse)     {         this.WindowState = this.WindowState == FormWindowState.Normal ? FormWindowState.Maximized : FormWindowState.Normal;     } }  #endregion 

上面只是鼠标事件的处理程序,需要绑定到控件上才会生效。这里使用Panel控件作为标题栏,绑定到标题栏Panel控件的事件上即可。如果没有使用控件作为标题栏,则需要绑定到主窗体的事件上。
在主界面的构造函数里面添加绑定事件,代码如下:

this.FormBorderStyle = FormBorderStyle.None; this.StartPosition = FormStartPosition.CenterScreen; this.BackColor = SystemColors.ActiveCaption; panel_Title.MouseDown += windowMove_MouseDown; panel_Title.MouseUp += windowMove_MouseUp; panel_Title.MouseMove += windowMove_MouseMove; panel_Title.MouseDoubleClick += windowMove_DoubleClick; 

自定义窗体按钮

自定义窗体按钮正常的操作是派生一个Button控件的子类,然后实现一些自定义的功能。这里不想搞得那么复杂,直接使用PictureBox控件作为按钮即可。
在标题栏控件panel_Title中添加pictureBoxBtn_Min、pictureBoxBtn_Max、pictureBoxBtn_Close三个PictureBox控件,另外添加一个Label控件命名为label_Title显示标题。

标题显示

在主窗体的构造函数中设置标题Label控件的属性,代码如下:

label_Title.AutoSize = true; label_Title.Anchor = AnchorStyles.None; label_Title.Location = new Point((panel_Title.Size.Width - label_Title.Size.Width) / 2, (panel_Title.Size.Height - label_Title.Size.Height) / 2); 

标题字体的样式按自己的喜好设置,这里就不一一介绍了。

按钮设置

首先需要导入按钮的图标,分别是以下几个(文末源码里面有完整图标):

  • 最小化 实现一个简单的侧边导航Winform程序框架
  • 正常化 实现一个简单的侧边导航Winform程序框架
  • 最大化 实现一个简单的侧边导航Winform程序框架
  • 退出 实现一个简单的侧边导航Winform程序框架

在PictureBox控件的Image属性中点击“...”导入,如下图所示:
实现一个简单的侧边导航Winform程序框架
实现一个简单的侧边导航Winform程序框架

在主界面的后台代码中实现按钮的单击事件,代码如下:

#region 自定义窗体按钮 //鼠标进入按钮 private void pictureBoxBtn_MouseEnter(object sender, EventArgs e) {     PictureBox pictureBox = sender as PictureBox;     pictureBox.BackColor = Color.FromArgb(67, 92, 200); }  //最小化 private void pictureBoxBtn_Min_Click(object sender, EventArgs e) {     this.WindowState = FormWindowState.Minimized; } //最大化 private void pictureBoxBtn_Max_Click(object sender, EventArgs e) {     PictureBox pictureBox = sender as PictureBox;     if (this.WindowState == FormWindowState.Normal)     {         pictureBox.Image = Properties.Resources.MaxNormal;         this.WindowState = FormWindowState.Maximized;     }     else if (this.WindowState == FormWindowState.Maximized)     {         pictureBox.Image = Properties.Resources.Max;         this.WindowState = FormWindowState.Normal;     } } //退出 private void pictureBoxBtn_Close_Click(object sender, EventArgs e) {     this.Close(); }        //鼠标离开按钮 private void pictureBoxBtn_MouseLeave(object sender, EventArgs e) {     PictureBox pictureBox = sender as PictureBox;     pictureBox.BackColor = SystemColors.ActiveCaption; } #endregion 

在主界面的构造函数中设置按钮属性并绑定事件,代码如下:

pictureBoxBtn_Max.Image = Properties.Resources.Max;  pictureBoxBtn_Min.Anchor = AnchorStyles.Top | AnchorStyles.Right; pictureBoxBtn_Max.Anchor = AnchorStyles.Top | AnchorStyles.Right; pictureBoxBtn_Close.Anchor = AnchorStyles.Top | AnchorStyles.Right;  pictureBoxBtn_Min.SizeMode = PictureBoxSizeMode.CenterImage; pictureBoxBtn_Max.SizeMode = PictureBoxSizeMode.CenterImage; pictureBoxBtn_Close.SizeMode = PictureBoxSizeMode.CenterImage;  pictureBoxBtn_Min.Click += pictureBoxBtn_Min_Click; pictureBoxBtn_Max.Click += pictureBoxBtn_Max_Click; pictureBoxBtn_Close.Click += pictureBoxBtn_Close_Click;  pictureBoxBtn_Min.MouseEnter +=pictureBoxBtn_MouseEnter; pictureBoxBtn_Max.MouseEnter += pictureBoxBtn_MouseEnter; pictureBoxBtn_Close.MouseEnter += pictureBoxBtn_MouseEnter;  pictureBoxBtn_Min.MouseLeave += pictureBoxBtn_MouseLeave; pictureBoxBtn_Max.MouseLeave += pictureBoxBtn_MouseLeave; pictureBoxBtn_Close.MouseLeave += pictureBoxBtn_MouseLeave; 

实现状态栏

状态栏就没什么好说的,根据项目的情况自行添加,此处只实现一个实时时间显示标签意思一下。
在主界面中添加一个Panel控件命名为panel_State,在Panel控件中添加一个Label控件命名为label_Time,调整一下大小和位置。
在主界面的构造函数中设置定时器,代码如下:

label_Time.Text = DateTime.Now.ToString(); label_Time.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; Timer timer = new Timer(); timer.Interval = 1000; timer.Tick += (sender, obj) => {     label_Time.Text = DateTime.Now.ToString(); }; timer.Start(); 

定时器的启动有一点的延迟,需要先设置一下label_Time的显示内容。

整体使用

在窗体设置界面调整好Panel控件的尺寸,主要是标题栏、状态栏的高度。
在主界面的构造函数中设置Panel控件的布局,代码如下:

panel_Title.Dock = DockStyle.Top; panel_Navigation.Dock = DockStyle.Fill; panel_State.Dock = DockStyle.Bottom; //必须置于顶层 panel_Navigation.BringToFront(); 

然后新建Form1、Form2、Form3等窗体,实现自己的业务逻辑。

本文项目的下载链接(提取码:sfbk):https://pan.baidu.com/s/18w85F7ebwV1PFY-4CkmBWA

参考文章