简介

每次新项目都要想着界面怎么设计好,但想来想去上位机界面就那几种,按照导航方式可分为:菜单工具栏导航、汉堡包导航、侧边导航等。我用的最多的是侧边导航,导航菜单一般只有一级(最多二级),三级导航菜单基本很少用到。

本文实现一个简单的侧边导航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);

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

按钮设置

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

  • 最小化
  • 正常化
  • 最大化
  • 退出

在PictureBox控件的Image属性中点击“...”导入,如下图所示:



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

#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

参考文章

实现一个简单的侧边导航Winform程序框架的更多相关文章

  1. 一个简单的c# 贪吃蛇程序

    一个简单的c#贪吃蛇程序 程序分为界面设计和程序设计:界面设计和程序设计均参考了一些游戏实例,但是所有代码内容是本人编写. 由于看到别人写的程序并没有署名,这里的署名全部都是csdn官网. 游戏界面设 ...

  2. Django 学习笔记之六 建立一个简单的博客应用程序

    最近在学习django时建立了一个简单的博客应用程序,现在把简单的步骤说一下.本人的用的版本是python 2.7.3和django 1.10.3,Windows10系统 1.首先通过命令建立项目和a ...

  3. 用nodejs搭建一个简单的服务监听程序

    作为一个从业三年左右的,并且从事过半年左右PHP开发工作的前端,对于后台,尤其是对以js语言进行开发的nodejs,那是比较有兴趣的,虽然本身并没有接触过相关的工作,只是自己私下做的一下小实验,但是还 ...

  4. (1)风色从零单排《C++ Primer》 一个简单的c++程序

    从零单排<C++ Primer> --(1)一个简单的c++程序 本次学习收获 0.写在前面 风色以前上过C++的课程,然而当时并没有认真去学,基本不能使用c++来作项目开发. 这次又一次 ...

  5. 在 Visual Studio 中创建一个简单的 C# 控制台应用程序

    转载:https://blog.csdn.net/qq_43994242/article/details/87260824 快速入门:使用 Visual Studio 创建第一个 C# 控制台应用 h ...

  6. 创建一个简单的maven的web程序

    最近学习Hadoop,发现学习要想用hadoop作为后台运行web程序,必须应用maven,所以学习了今天学习了一下maven,然后搭建了一个简单的web程序 首先我使用的是eclipse中自带的ma ...

  7. 使用eclipse创建一个简单的Java Web应用程序

    关于Java JDK/JRE.Tomcat的配置等等都没什么好说的,主要记录一下使用Eclipse创建web工程时的一些点以及说一说自己用IDEA的创建失败的过程(IDEA没运行成功...暂时不想弄了 ...

  8. 在cmd下编译一个简单的servlet时出现程序包javax.servlet不存在

    由于servlet和JSP不是Java平台JavaSE(标准版)的一部分,而是Java EE(企业版)的一部分,因此,必须告知编译器servlet的位置. 解决“软件包 javax.servlet不存 ...

  9. wpf编写一个简单的PDF转换的程序

    wpf 调用Spire.Pdf将PDF文件转换为其他文件模式 首先在Nuget里下载该第三方包Spire.Pdf. 然后可以编写程序 //这里我调用的是解析成流模式,这是因为我要使用ProgressB ...

随机推荐

  1. NIO中的File

    package nio; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files ...

  2. 深入Pulsar Consumer的使用方式&源码分析

    原文链接 1.使用前准备 引入依赖: <dependency> <groupId>org.apache.pulsar</groupId> <artifactI ...

  3. vue 引用省市区三级联动(element-ui select)

    npm 下载 axios npm install --save axios static 静态文件夹里 创建 json 文件夹 json 文件夹里创建 map.json map.json 文件里写 ( ...

  4. CGLib浅析

    CGLib浅析 什么是CGLib CGLIB实现动态代理,并不要求被代理类必须实现接口,底层采用asm字节码生成框架生成代理类字节码(该代理类继承了被代理类). 所以被代理类一定不能定义为final ...

  5. 异步处理方式之信号(三):kill、raise、alarm、pause函数简介

    文章目录 6. 函数kill和raise 7. 函数alarm和pause 7.1 alarm() 7.2 pause() 6. 函数kill和raise kill函数用来将信号发送给进程或者进程组. ...

  6. 将rgb表示方式转换为hex表示方式-------------将hex表示方式转换为rgb表示方式(这里返回rgb数组组合)

      /**  * kevin 2021.1.4  * 将rgb表示方式转换为hex表示方式  * @param {string} rgbColor 传过来的hex格式的颜色  * @returns { ...

  7. Android使用百度语音识别api代码实现。

    第一步 ① 创建平台应用 点击百度智能云进入,没有账号的可以先注册账号,这里默认都有账号了,然后登录. 然后左侧导航栏点击找到语音技术 然后会进入一个应用总览页面, 然后点击创建应用 立即创建 点击查 ...

  8. lombok时运行编译无法找到get/set方法 看这篇就够了

    今天项目突然运行的时候报错,提示找不到get和set方法,这个时候我就检查了项目,在编译器(idea)是没有报错的.说明编译没问题,只是运行过不去. 后面就开始用我的方法解决这个问题,一步一步排查. ...

  9. 在Jupyter Notebook添加代码自动补全功能

    在使用Jupyter notebook时发现没有代码补全功能,于是在网上查找了一些资料,最后总结了以下内容. 1 安装显示目录功能: pip install jupyter_contrib_nbext ...

  10. 【Python】python 2.7.16 x64 百度网盘

    倒霉官网下载太慢,下好了分享出来,也给自己留一个备份. 链接:点这里提取码:znaf PS: py2.7版本 for win 64位