VB2026 菜单生成基类 BqGetMenuStrip

继上一篇,今天为大家分享如何利用 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
相关推荐
洛水水1 小时前
Redis 分布式锁详解:实现与缺陷
数据库·redis·分布式
韶博雅1 小时前
oracle中表和列转大写
数据库·oracle
智者知已应修善业2 小时前
【51单片机8位数码管动态显示日期小数点风格】2023-11-13
c++·经验分享·笔记·算法·51单片机
智者知已应修善业2 小时前
【51单片机有三个LED 分别第一个灯闪三下 再到第二个灯又闪三下 再到第三个灯又闪三下 就这样循环程序】2023-11-16
c++·经验分享·笔记·算法·51单片机
暴躁小师兄数据学院2 小时前
【AI大数据工程师特训笔记】第04讲:PostgreSQL 数据库内置函数详解
大数据·数据库·笔记·ai·语言模型
苏渡苇3 小时前
Spring Cloud Alibaba:将 Sentinel 熔断限流规则持久化到 Nacos 配置中心
数据库·spring boot·mysql·spring cloud·nacos·sentinel·持久化
杨云龙UP3 小时前
Oracle Recycle Bin 回收站详解:DROP TABLE 后还能找回吗?
linux·运维·数据库·sql·mysql·oracle
熊猫不是猫QAQ3 小时前
NAS上的文字版《星露谷物语》,大型种田经营游戏
经验分享
吃好睡好便好3 小时前
创建魔方矩阵和单位矩阵
开发语言·人工智能·学习·线性代数·matlab·矩阵