继上一篇,今天为大家分享如何利用 BqUMenuItem 生成MenuStrip菜单栏,即BqGetMenuStrip 类。根据数据库中的模块权限数据表动态生成菜单(MenuStrip)。
支持多级菜单、分隔符、图标、快捷键、快速键,并支持通过反射动态绑定 Click、MouseEnter、MouseMove 事件。还可以合并两个不同的MenuStrip。
完整的源代码如下:
vbnet
#Region "全新打造的 菜单生成基类 BqGetMenuStrip 作者:cdbqss 20260524"
''' <summary>
''' 根据数据库中的模块权限数据表动态生成菜单(MenuStrip)。
''' 支持多级菜单、分隔符、图标、快捷键、快速键,并支持通过反射动态绑定 Click、MouseEnter、MouseMove 事件。
''' </summary>
''' <remarks>需要先设置 BqpTableMode(菜单数据表)、BqpTargetForm(目标窗体)、BqpEvClick/BqpEvMouseEnter/BqpEvMouseMove(事件方法名),然后通过 BqGetStrip 属性获取生成的菜单。</remarks>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:动态菜单生成器,生成 MenuStrip")>
Public Class BqGetMenuStrip
#Region "私有变量"
Private oMenStip As System.Windows.Forms.MenuStrip
Private ltbMode As DataTable ' 数据表,这个是从数据库获得的数据表
Private lNameFir As String ' 菜单名的前缀
Private lFormTarget As Object ' 目标窗体实例,用于反射调用
Private eEvClick As EventHandler ' 三个事件,传入时必须在窗体定义 Public 事件名,注意大小写和事件参数保持一致
Private eEvMouseEnter As EventHandler
Private eEvMouseMove As MouseEventHandler
Dim ltbOldMenu As DataTable '这个是从菜单内部建立的数据表
#End Region
#Region "公用只写属性"
''' <summary>
''' 设置菜单的数据表(模块权限表)。内部会复制一份数据表,避免原表被修改。
''' </summary>
''' <param name="Value">包含模块信息的 DataTable,必须包含字段:BHmode, BHparent, MCmode, ModeKey, ModeAlt, MeM, FRMNAME, Icon, MarKsy, blnAdd, blnEdi, blnDel, blnPrn, blnBow, blnOut 等。</param>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:设置菜单的数据来源(模块权限表)")>
Public WriteOnly Property BqSetTableMode() As DataTable
Set(ByVal Value As DataTable)
ltbMode = New DataTable
ltbMode = Value.Copy ' 不能直接用 ltbMode,因为重新添加节点时,会提示"表已经在另一个数据集 DataSet 中"
End Set
End Property
''' <summary>
''' 设置动态菜单的名称前缀。生成的每个菜单项的 Name 将使用"前缀_模块编号"格式。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:菜单名称前缀,用于区分不同来源的菜单项")>
Public WriteOnly Property BqSetNameFist() As String
Set(value As String)
lNameFir = value
End Set
End Property
''' <summary>
''' 设置目标窗体实例(用于反射调用事件处理方法)。
''' 务必要先设置窗体实例,后续在设置事件名称时,才能正常进行反射调用
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:目标窗体实例,用于动态事件绑定")>
Public WriteOnly Property BqSetTargetForm As Object
Set(value As Object)
lFormTarget = value
End Set
End Property
''' <summary>
''' 设置动态菜单 Click 事件对应的方法名称(方法签名必须为 Sub(sender As Object, e As EventArgs))。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:Click 事件绑定的方法名(须符合 EventHandler 签名)")>
Public WriteOnly Property BqsEvClick() As String
Set(value As String)
eEvClick = mSetHandle(lFormTarget, value, GetType(EventHandler))
'Debug.WriteLine(eEvClick)
End Set
End Property
''' <summary>
''' 设置动态菜单 MouseMove 事件对应的方法名称(方法签名必须为 Sub(sender As Object, e As MouseEventArgs))。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:MouseMove 事件绑定的方法名(须符合 MouseEventHandler 签名)")>
Public WriteOnly Property BqsEvMouseMove() As String
Set(value As String)
eEvMouseMove = mSetHandle(lFormTarget, value, GetType(MouseEventHandler))
'Debug.WriteLine(eEvMouseMove)
End Set
End Property
''' <summary>
''' 设置动态菜单 MouseEnter 事件对应的方法名称(方法签名必须为 Sub(sender As Object, e As EventArgs))。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:MouseEnter 事件绑定的方法名(须符合 EventHandler 签名)")>
Public WriteOnly Property BqsEvMouseEnter() As String
Set(value As String)
eEvMouseEnter = mSetHandle(lFormTarget, value, GetType(EventHandler))
'Debug.WriteLine(eEvMouseEnter)
End Set
End Property
#End Region
#Region "公用只读属性"
''' <summary>
''' 获取返回生成的菜单栏(MenuStrip)。如果生成失败或没有菜单项,则返回 Nothing。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:获取生成的 MenuStrip 对象")>
Public ReadOnly Property BqGetStrip As System.Windows.Forms.MenuStrip
Get
Call mReadTable()
If IsNothing(oMenStip) = False AndAlso oMenStip.Items.Count > 0 Then
Return oMenStip
Else
Return Nothing
End If
End Get
End Property
''' <summary>
''' 创建并返回一个 菜单数据表,包含所有必要字段,布尔字段默认值为 True。
''' </summary>
Public ReadOnly Property BqCreateMenuTab() As DataTable
Get
Dim ltb As New DataTable
' 添加 Boolean 字段,默认值均为 True
Dim colMarKsy As New DataColumn("MarKsy", GetType(Boolean)) With {.DefaultValue = True}
Dim colAdd As New DataColumn("blnAdd", GetType(Boolean)) With {.DefaultValue = True}
Dim colEdi As New DataColumn("blnEdi", GetType(Boolean)) With {.DefaultValue = True}
Dim colDel As New DataColumn("blnDel", GetType(Boolean)) With {.DefaultValue = True}
Dim colPrn As New DataColumn("blnPrn", GetType(Boolean)) With {.DefaultValue = True}
Dim colBow As New DataColumn("blnBow", GetType(Boolean)) With {.DefaultValue = True}
Dim colOut As New DataColumn("blnOut", GetType(Boolean)) With {.DefaultValue = True}
' ID 自动递增
Dim colID As New DataColumn("ID", GetType(Integer)) With {
.AutoIncrement = True, .AutoIncrementSeed = 1, .AutoIncrementStep = 1}
' 其他字段
Dim colXHmode As New DataColumn("XHmode", GetType(Integer))
Dim colBHmode As New DataColumn("BHmode", GetType(String))
Dim colBHparent As New DataColumn("BHparent", GetType(String))
Dim colFrmName As New DataColumn("FrmName", GetType(String))
Dim colMCmode As New DataColumn("MCmode", GetType(String))
Dim colMeM As New DataColumn("MeM", GetType(String))
Dim colModeAlt As New DataColumn("ModeAlt", GetType(String))
Dim colModeKey As New DataColumn("ModeKey", GetType(String))
Dim colIcon As New DataColumn("Icon", GetType(String))
' 添加所有列到表
ltb.Columns.AddRange({colID, colXHmode, colBHmode, colBHparent, colFrmName, colMCmode,
colMeM, colModeAlt, colModeKey, colIcon,
colMarKsy, colAdd, colEdi, colDel, colPrn, colBow, colOut})
' 设定主键:ID 和 BHmode
ltb.PrimaryKey = {colBHmode, colID}
Return ltb
End Get
End Property
#End Region
#Region "公用方法"
''' <summary>
''' 输出当前类内部生成的菜单(oMenStip)的结构到调试窗口,用于调试。
''' </summary>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:输出当前生成的菜单结构到输出窗口")>
Public Overloads Sub BqmPrintStrip()
If IsNothing(oMenStip) = False AndAlso oMenStip.Items.Count > 0 Then
Call mPrinMenuItems(oMenStip)
End If
End Sub
''' <summary>
''' 输出指定 MenuStrip 菜单的结构到调试窗口,用于调试。
''' </summary>
''' <param name="oMiStrip">要输出结构的 MenuStrip 对象</param>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:输出指定的菜单结构到输出窗口")>
Public Overloads Sub BqmPrintStrip(oMiStrip As System.Windows.Forms.MenuStrip)
If IsNothing(oMiStrip) = False AndAlso oMiStrip.Items.Count > 0 Then
Call mPrinMenuItems(oMiStrip)
End If
End Sub
''' <summary>
''' 为指定 MenuStrip 中的所有末级菜单项强制绑定 Click 事件。
''' </summary>
''' <param name="oMiStrip">目标 MenuStrip 对象</param>
''' <param name="lsClick">Click 事件对应的方法名称(必须符合 EventHandler 签名 Sub(sender As Object, e As EventArgs))</param>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:为指定菜单栏的所有叶子菜单项统一绑定 Click 事件")>
Public Sub BqmBindMenuClickEvents(oMiStrip As System.Windows.Forms.MenuStrip, lsClick As String)
If IsNothing(oMiStrip) = False AndAlso oMiStrip.Items.Count > 0 Then
Dim lClick As EventHandler
lClick = mSetHandle(lFormTarget, lsClick, GetType(EventHandler))
Debug.WriteLine($"强制绑定 {lsClick } 事件 {lClick }")
For Each mi1 As ToolStripMenuItem In oMiStrip.Items
' 只处理顶层菜单的下拉项,顶层菜单本身不绑定
If mi1.HasDropDownItems Then
mBindEvTag(mi1, lClick)
End If
Next
End If
End Sub
''' <summary>
''' 根据菜单名称查找并返回菜单项对象(从当前生成的菜单中查找)。
''' </summary>
''' <param name="name">菜单项的名称(Name 属性)</param>
''' <returns>找到的 ToolStripMenuItem,未找到则返回 Nothing</returns>
<Browsable(True), Category("Bqss"), Description("cdbqss属性提示:根据菜单名称查找菜单项")>
Public Overloads Function BqmFindMenuItemByName(name As String) As ToolStripMenuItem
If oMenStip Is Nothing OrElse oMenStip.Items.Count = 0 Then
Return Nothing
End If
For Each mi1 As ToolStripMenuItem In oMenStip.Items
Dim result = mFindMenuItem(mi1, name)
If result IsNot Nothing Then Return result
Next
Return Nothing
End Function
''' <summary>
''' 将 insertMenu 的所有顶级项移动到 sourceMenu 的指定索引之后(直接移动,不克隆)。
''' 如果参数无效,则安全返回,不会抛出异常。
''' </summary>
''' <param name="sourceMenu">目标菜单(将被修改)</param>
''' <param name="insertMenu">要插入的菜单(移动后其 Items 将被清空)</param>
''' <param name="insertAfterIndex">在 sourceMenu 中的哪个索引之后插入。有效范围 -1 到 sourceMenu.Items.Count-1。<br/>
''' -1 表示插入到最前面;等于 sourceMenu.Items.Count-1 表示追加到最后。</param>
Public Function BqMergeMenuStrips(sourceMenu As MenuStrip, insertMenu As MenuStrip, insertAfterIndex As Integer)
' 容错:任一参数为空则直接返回
If sourceMenu Is Nothing OrElse insertMenu Is Nothing Then Return Nothing
If insertMenu.Items.Count = 0 Then Return sourceMenu
' 确定插入起始位置(索引范围 -1 到 Count-1)
Dim nn As Integer
If insertAfterIndex < -1 Then
nn = 0
ElseIf insertAfterIndex >= sourceMenu.Items.Count Then
nn = sourceMenu.Items.Count
Else
nn = insertAfterIndex + 1
End If
' 倒序遍历移动项,避免索引错乱
For i As Integer = insertMenu.Items.Count - 1 To 0 Step -1
Dim item = insertMenu.Items(i)
sourceMenu.Items.Insert(nn, item)
Next
Return sourceMenu
End Function
#End Region
#Region "私有方法"
''' <summary>
''' 读取数据表并构建菜单结构。内部建立数据关系,递归创建菜单项。
''' </summary>
Private Sub mReadTable()
Try
oMenStip = New System.Windows.Forms.MenuStrip
Dim lDt As New System.Data.DataSet
Dim tb As New DataTable
tb = ltbMode.Copy ' 本可以直接用 ltbMode,但为了与原有代码一致,故意加这一行
lDt.Clear() ' 重新建立关联,所以这里要重新定义一个数据集
lDt.Relations.Clear()
lDt.EnforceConstraints = False
lDt.Tables.Add(tb)
Dim dr1 As New DataRelation("self", tb.Columns("BHmode"), tb.Columns("BHparent"))
lDt.Relations.Add(dr1)
Dim r1 As DataRow
For Each r1 In lDt.Tables(0).Rows
If r1.IsNull("BHparent") Then ' 根节点(无父级)
mMeuAdd(r1, Nothing)
End If
Next
Dim kk As Integer
Catch ex As Exception
Dim eexx As String = ""
Debug.WriteLine(ex)
Debug.WriteLine("在 BqGetMenuStrip类中 的 BqpTbmode 中")
End Try
End Sub
''' <summary>
''' 递归添加菜单项。根据数据行创建 ToolStripMenuItem 或 ToolStripSeparator。
''' </summary>
''' <param name="r">当前菜单项对应的数据行</param>
''' <param name="oMenuItem">父级菜单项(若为 Nothing 则添加到顶层)</param>
Private Sub mMeuAdd(ByVal r As DataRow, ByVal oMenuItem As ToolStripMenuItem)
Try
' 模块名称,如果名称为 "-" 则表示分隔符
Dim lmMc As String = Trim("" & r("MCmode"))
If Microsoft.VisualBasic.Left(lmMc, 1) = "-" Then
' 创建分隔符(ToolStripSeparator)
Dim mi As New ToolStripSeparator()
If oMenuItem IsNot Nothing Then
oMenuItem.DropDownItems.Add(mi)
End If
Else
' 获取当前菜单项的子记录(用于判断是否为末级以及递归)
Dim rs() As DataRow = r.GetChildRows("self")
Dim lbMj As Boolean = (rs.Length > 0) ' 记录数大于0表示非末级
Dim mi As New BqUMenuItem()
' 设置基础属性(权限、模块信息等)
mi.Name = lNameFir & "_" & r("BHmode") ' 用"前缀_模块编号"作为菜单名,方便后续识别
mi.BqPModMc = lmMc ' 原始模块名称(不含快捷键标记)
mi.BqpIsLeaf = Not lbMj ' 设置是否为末级(无子菜单)
mi.BqPMoRiAdd = r("blnAdd") ' 新增权限
mi.BqPMoRiEdi = r("blnEdi") ' 修改权限
mi.BqPMoRiDel = r("blnDel") ' 删除权限
mi.BqPMoRiPrn = r("blnPrn") ' 打印权限
mi.BqPMoRiBow = r("blnBow") ' 预览权限
mi.BqPMoRiOut = r("blnOut") ' 导出权限
mi.BqPModBh = "" & r("BHmode") ' 模块编号
mi.BqPModFrm = "" & r("FRMNAME") ' 表单名称
mi.BqPModTip = "" & r("MeM") ' 备注内容
mi.BqpModeAlt = "" & r("ModeAlt") ' 快速键(单个字符,如 "F")
mi.BqpModeKey = "" & r("ModeKey") ' 快捷键(如 "Ctrl+O")
mi.BqPMiIcon = "" & r("Icon") ' 图标文件路径
mi.BqPsID = "" & r("ID") '菜单ID
mi.BqPsValue = "" '用户自定义对象,相当于 Tag
mi.Text = mi.BqpText ' 自动生成菜单文本(根据 BqpIsLeaf 决定快捷键位置)
mi.ShowShortcutKeys = True ' 显示快捷键提示文字
mi.Enabled = r("MarKsy") ' 菜单是否可用(Enabled)
' 将菜单项添加到父级容器
If oMenuItem Is Nothing Then
oMenStip.Items.Add(mi) ' 顶层菜单 → 添加到 oMenStip
Else
oMenuItem.DropDownItems.Add(mi) ' 子菜单 → 添加到父级的下拉项中
End If
' 动态绑定事件
If Not lbMj AndAlso eEvClick IsNot Nothing Then
AddHandler mi.Click, eEvClick
End If
If eEvMouseEnter IsNot Nothing Then
AddHandler mi.MouseEnter, eEvMouseEnter
End If
If eEvMouseMove IsNot Nothing Then
AddHandler mi.MouseMove, eEvMouseMove
End If
' 递归处理子菜单
For Each r2 As DataRow In rs
mMeuAdd(r2, mi)
Next
End If
Catch ex As Exception
Debug.WriteLine($"在 BqGetMenuStrip类中 的 mMenuAdd 中 {ex}")
End Try
End Sub
''' <summary>辅助方法:输出所有菜单项的名称和文本到输出窗口,用于调试。</summary>
''' <param name="oMiStrip">要输出的 MenuStrip</param>
Private Sub mPrinMenuItems(oMiStrip As System.Windows.Forms.MenuStrip)
For Each mi1 As ToolStripMenuItem In oMiStrip.Items
Debug.WriteLine($"顶层: {mi1.Name} - {mi1.Text}")
mPrinMenuItemR(mi1, 1)
Next
End Sub
''' <summary>递归输出子菜单项。</summary>
''' <param name="mi1">当前菜单项</param>
''' <param name="n">当前缩进级别</param>
Private Sub mPrinMenuItemR(mi1 As ToolStripMenuItem, n As Integer)
For Each mi2 As ToolStripItem In mi1.DropDownItems
If TypeOf mi2 Is ToolStripMenuItem Then
Dim mi = DirectCast(mi2, ToolStripMenuItem)
Dim m = New String(" "c, n * 2)
Debug.WriteLine($"{m}子项: {mi.Name} - {mi.Text}")
If mi.HasDropDownItems Then
mPrinMenuItemR(mi, n + 1)
End If
End If
Next
End Sub
''' <summary>
''' 递归处理菜单项,为所有叶子菜单项绑定 Click 事件。
''' </summary>
''' <param name="mi1">当前父级菜单项</param>
''' <param name="levClick">要绑定的 Click 事件委托</param>
Private Sub mBindEvTag(mi1 As ToolStripMenuItem, levClick As EventHandler)
Try
For Each mi2 As ToolStripItem In mi1.DropDownItems
If TypeOf mi2 Is ToolStripMenuItem Then
Dim mi = DirectCast(mi2, ToolStripMenuItem)
If Not String.IsNullOrEmpty(mi.Text) AndAlso mi.Text <> "-" Then
' 绑定点击事件(注意:levClick 已经是委托,不需要 AddressOf)
' 将 AddressOf levClick 改为直接使用 levClick
' (因为 levClick 已经是 EventHandler 委托,不需要 AddressOf)
RemoveHandler mi.Click, levClick
AddHandler mi.Click, levClick '注意这里的方式有所不同。
End If
' 递归处理子菜单
If mi.HasDropDownItems Then
mBindEvTag(mi, levClick)
End If
End If
Next
Catch ex As Exception
Debug.WriteLine($"在 BqGetMenuStrip类中 的 强制绑定 mBindEvTag 中 {ex}")
End Try
End Sub
#End Region
#Region "私有函数"
''' <summary>
''' 根据目标对象和方法名创建指定类型的委托。支持 EventHandler 和 MouseEventHandler。
''' </summary>
''' <param name="oTarget">目标窗体实例</param>
''' <param name="sMethName">方法名称</param>
''' <param name="oDelegaType">委托类型(EventHandler 或 MouseEventHandler)</param>
''' <returns>创建的委托,失败返回 Nothing</returns>
Private Function mSetHandle(oTarget As Object, sMethName As String, oDelegaType As Type) As [Delegate]
If oTarget Is Nothing OrElse String.IsNullOrEmpty(sMethName) Then Return Nothing
Try
' 根据委托类型确定参数类型
Dim lTypes1 As Type()
If oDelegaType = GetType(EventHandler) Then
lTypes1 = {GetType(Object), GetType(EventArgs)}
ElseIf oDelegaType = GetType(MouseEventHandler) Then
lTypes1 = {GetType(Object), GetType(MouseEventArgs)}
Else
' 其他类型暂不支持
Return Nothing
End If
Dim method = oTarget.GetType().GetMethod(sMethName, BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, lTypes1, Nothing)
If method Is Nothing Then
Debug.WriteLine($"未找到方法: {sMethName} 参数类型 {String.Join(",", lTypes1.Select(Function(t) t.Name))}")
Return Nothing
End If
Return [Delegate].CreateDelegate(oDelegaType, oTarget, method)
Catch ex As Exception
Debug.WriteLine($"创建委托失败: {sMethName} -> {ex.Message}")
Return Nothing
End Try
End Function
''' <summary>
''' 递归查找指定名称的菜单项。
''' </summary>
''' <param name="parent">父级菜单项</param>
''' <param name="name">要查找的名称</param>
''' <returns>找到的菜单项,未找到返回 Nothing</returns>
Private Function mFindMenuItem(parent As ToolStripMenuItem, name As String) As ToolStripMenuItem
If parent.Name = name Then Return parent
For Each mi2 As ToolStripItem In parent.DropDownItems
If TypeOf mi2 Is ToolStripMenuItem Then
Dim mi = DirectCast(mi2, ToolStripMenuItem)
Dim mi3 = mFindMenuItem(mi, name)
If mi3 IsNot Nothing Then Return mi3
End If
Next
Return Nothing
End Function
#End Region
End Class
#End Region
使用示例:
一、用数组来创建菜单:
1、用下面方法,先准备菜单信息。
vbnet
'通过建立菜单数据表
Private Function mIniOldTable() As DataTable
Dim ltb As New DataTable
Try
olBqmenu = New BqGetMenuStrip
ltb = olBqmenu.BqCreateMenuTab '获得空的菜单数据表
'Dim rs(13) As DataRow
' 添加数据行(按照您提供的顺序)
' 定义列名顺序(与表结构一致,用于索引赋值)
Dim columnNames As String() = {"BHparent", "XHmode", "BHmode", "FrmName", "MCmode", "MeM", "ModeAlt", "ModeKey", "Icon"}
' 定义所有行数据(每行9个值,按上述列顺序)
Dim rowData As Object(,) = {
{Nothing, 1, "9001", "", "文件", "", "F", "", ""},
{Nothing, 2, "9002", "", "编辑", "", "E", "", ""},
{Nothing, 3, "9003", "", "窗口", "", "W", "", ""},
{Nothing, 4, "9004", "", "帮助", "", "H", "", ""},
{"9001", 1, "9101", "OPEN", "打开数据库", "", "O", "Ctrl+O", "..\资源\ico16\img0101.png"},
{"9001", 2, "9102", "", "-", "", "", "", ""},
{"9001", 3, "9103", "LOGIN", "系统登录", "", "L", "", "..\资源\ico16\img0102.png"},
{"9001", 4, "9104", "", "-", "", "", "", ""},
{"9001", 5, "9105", "PRINT", "打印", "", "P", "Ctrl+P", "..\资源\ico16\img0103.png"},
{"9001", 6, "9107", "", "-", "", "", "", ""},
{"9001", 6, "9106", "PRINTPREVIEW", "打印预览", "", "V", "Ctrl+V", "..\资源\ico16\img0104.png"},
{"9001", 7, "9108", "PRINTSETUP", "打印机设置", "", "", "", ""},
{"9001", 8, "9109", "EXIT", "退出", "", "X", "", ""},
{"9002", 1, "9201", "UNDO", "撤消", "", "U", "Ctrl+Z", "..\资源\ico16\img0201.png"},
{"9002", 2, "9202", "REDO", "重复", "", "R", "Ctrl+Y", "..\资源\ico16\img0202.png"},
{"9002", 3, "9203", "", "-", "", "", "", ""},
{"9002", 5, "9204", "CUT", "剪切", "", "T", "Ctrl+X", "..\资源\ico16\img0203.png"},
{"9002", 6, "9205", "COPY", "复制", "", "C", "Ctrl+C", "..\资源\ico16\img0204.png"},
{"9002", 7, "9206", "PASTE", "粘贴", "", "V", "Ctrl+V", "..\资源\ico16\img0205.png"},
{"9002", 10, "9207", "", "-", "", "", "", ""},
{"9002", 12, "9208", "SELECTALL", "全选", "", "A", "Ctrl+A", ""},
{"9003", 1, "9301", "HORIZONTAL", "水平平铺", "", "I", "", ""},
{"9003", 2, "9302", "VERTICAL", "垂直平铺", "", "H", "", ""},
{"9003", 3, "9303", "CASCADE", "层叠", "", "C", "", ""},
{"9003", 4, "9304", "ICONS", "排列图标", "", "A", "", ""},
{"9003", 7, "9305", "", "-", "", "", "", ""},
{"9003", 11, "9306", "MINIMIZE", "全部最小化", "", "U", "", ""},
{"9003", 12, "9307", "RESTORE", "全部还原", "", "M", "", ""},
{"9003", 13, "9308", "CLOSE", "全部关闭", "", "Z", "", ""},
{"9003", 16, "9309", "", "-", "", "", "", ""},
{"9003", 21, "9310", "TOOLBAR", "工具栏", "", "T", "", ""},
{"9003", 22, "9311", "STATUSBAR", "状态栏", "", "S", "", ""},
{"9003", 30, "9312", "", "-", "", "", "", ""},
{"9004", 1, "9401", "HELPCONTENTS", "目录", "", "C", "Ctrl+F1", ""},
{"9004", 2, "9402", "HELPINDEX", "索引", "", "I", "", "..\资源\ico16\img0402.png"},
{"9004", 3, "9403", "HELPSEARCH", "搜索", "", "S", "", "..\资源\ico16\img0403.png"},
{"9004", 6, "9404", "", "-", "", "", "", ""},
{"9004", 10, "9405", "ABOUT", "关于...", "", "A", "Ctrl+F2", ""},
{"9004", 11, "9406", "", "-", "", "", "", ""},
{"9004", 14, "9407", "COPR", "注册", "", "V", "", ""},
{"9004", 18, "9408", "", "-", "", "", "", ""},
{"9004", 19, "9409", "PASS", "修改密码", "", "D", "", ""},
{"9004", 21, "9410", "USER", "用户列表", "", "U", "", ""},
{"9004", 22, "9411", "", "-", "", "", "", ""}} '注意最后一行是两个大括号
' 循环添加行
For i As Integer = 0 To rowData.GetLength(0) - 1
Dim newRow = ltb.NewRow()
For j As Integer = 0 To columnNames.Length - 1
Dim val = rowData(i, j)
' 处理 DBNull:如果值为 Nothing 或 <NULL> 字符串(这里用 Nothing 表示 DBNull)
If val Is Nothing Then
newRow(columnNames(j)) = DBNull.Value
Else
newRow(columnNames(j)) = val
End If
Next
ltb.Rows.Add(newRow.ItemArray)
Next
Catch ex As Exception
End Try
Return ltb
End Function
2、然后,用下面代码生成菜单,
vbnet
lTabOldMenu = mIniOldTable() '先得到旧菜单的数据表
Dim oMenuOld As New System.Windows.Forms.MenuStrip '定义本窗体初始菜单,方便继续者调用
Dim oMenuNew As New System.Windows.Forms.MenuStrip
Dim olBqmenu As New BqGetMenuStrip '自定义类,用来生成菜单栏
Dim ltb1 As DataTable = lTabOldMenu.Copy
With olBqmenu
.BqSetTableMode = ltb1
.BqSetNameFist = "Old"
.BqSetTargetForm = Me '因为这是在窗体的new时执行,下边的绑定无效
.BqsEvClick = "mOldMenuClick"
.BqsEvMouseMove = "mEvMouseMove"
.BqsEvMouseEnter = "mEvMouseEnter"
oMenuOld = .BqGetStrip
End With
这样,oMenuOld 菜单就生成啦!!绑定给窗体即可。如图所示,文件、编辑、窗口、帮助这四个菜单就生成啦。

二、从数据库中读取模块权限数据表
同样道理,只要模块权限数据表ltb2,传递给olBqmen即可。
vbnet
With olBqmenu '产生新菜单
.BqSetTableMode = ltb2
.BqSetNameFist = "AA"
.BqSetTargetForm = Me
.BqsEvClick = "BqClickNewMenu"
.BqsEvMouseMove = "mEvMouseMove"
.BqsEvMouseEnter = "mEvMouseEnter"
oMenuNew = .BqGetStrip
End With
这样,动态菜单oMenuNew 就生成啦。跟业务系统相关的菜单都是这样生成的。

菜单图片有点丑,不要见怪哈。
相信大家也观察到了,新旧两个菜单,是分别生成的。那么又是如何合并在一起的呢?
老实说,从VB2003升级到VB2026,由于ToolStripMenuItem类 没有旧版本MenuItem类的MergeMenu方法,两个MenuStrip菜单合并,确实老费了一些精力。按以前VB2003的思路,默认的文件、编辑、窗口、帮助四组菜单,无论数据库是否连接成功,也无论是否已经登录,这四组菜单都一直保留在主窗口中,这样就被折磨了一个多星期。后来想到,反正这四项菜单数量又不多,干脆每次都重新生成一次(只是把这些菜单信息提前保存在lTabOldMenu中),也多不了多少花销,所以,成就了我的BqGetMenuStrip类。以下很简单的一条代码,就实现了合并,并绑定窗体。
vbnet
MenuStrip1 = olBqmenu.BqMergeMenuStrips(oMenuOld, oMenuNew, 1)
Me.Controls.Add(MenuStrip1)
Me.MainMenuStrip = MenuStrip1