Изменить источник данных ComboBox после изменения другого значения ComboBox - PullRequest
0 голосов
/ 09 июля 2020

Я все еще новичок в разработке приложений в Visual Studio (недавно преобразованных из MS Access), и я пишу свой код на VB. net. Мой вопрос касается того, как изменить вторичное поле со списком на основе выбора другого. Все мои результаты хранятся в отдельной SQL серверной базе данных.

Когда вызывается моя форма, будет выполняться следующее:

Public Sub New(ByVal _PartID As String)

    InitializeComponent() 'Required at the begining of calling a new form'

    LoadCombobox(cboArea, "SELECT [ID], [Name], [MRPShow] FROM [LocationArea] WHERE [MRPShow] = 1", "Name", "ID")

    cboArea.SelectedValue = 0
    cboBay.SelectedValue = 0

    LoadPartData(_PartID)

End Sub

Public Sub LoadCombobox(_cbo As ComboBox, _query As String, _displayMember As String, _valueMember As String)

    Dim SQL As New SQLControl

    _cbo.Items.Clear()

    SQL.ExecQuery(_query)

    If SQL.HasException(True) Then Exit Sub

    _cbo.DataSource = SQL.DBDT

    _cbo.DisplayMember = _displayMember
    _cbo.ValueMember = _valueMember

End Sub

Public Sub LoadPartData(_ID As String)

    Sql.AddParam("@ID", _ID)
    Sql.ExecQuery("SELECT * FROM [Part] " &
                      "WHERE ID = @ID")

    If Sql.RecordCount < 1 Then
        MsgBox("No item found.")
        Exit Sub
    End If

    For Each r As DataRow In Sql.DBDT.Rows

        txtID.Text = r("ID").ToString()
        txtMRPID.Text = r("MRP_ID").ToString()
        txtPartName.Text = r("PartName").ToString()
        txtManufacturer.Text = r("Manufacturer").ToString()
        txtManufacturerPartNo.Text = r("PartNumber").ToString()

        cboVendor1.SelectedValue = r("Vendor1")
        txtWebsiteVendor1.Text = r("Vendor1Link").ToString()
        cboVendor2.SelectedValue = r("Vendor2")
        txtWebsiteVendor2.Text = r("Vendor2Link").ToString()
        cboVendor3.SelectedValue = r("Vendor3")
        txtWebsiteVendor3.Text = r("Vendor3Link").ToString()

        cboArea.SelectedValue = r("LocationArea")
        cboBay.SelectedValue = r("LocationBay")
        cboRack.SelectedValue = r("LocationRack")

        cboPartAssembly.SelectedValue = r("PartAssembly")
        txtDrawingNo.Text = r("DrawingNumber").ToString()
        txtImagePath.Text = r("Image").ToString()

    Next

    If Not String.IsNullOrEmpty(txtWebsiteVendor3.Text) Then

        Process.Start(txtWebsiteVendor3.Text)

    End If

End Sub

Фрагмент из моего класса SQLControl

Imports System.Data.SqlClient

Public Class SQLControl
    Private DBCon As New SqlConnection("---HIDDEN---")
    Private DBCmd As SqlCommand

    ' DB DATA
    Public DBDA As SqlDataAdapter
    Public DBDT As DataTable

    ' QUERY PARAMETERS
    Public Params As New List(Of SqlParameter)

    ' QUERY STATISTICS
    Public RecordCount As Integer
    Public Exception As String

    Public Sub New()
    End Sub

    ' ALLOW CONNECTION STRING OVERRIDE
    Public Sub New(ConnectionString As String)
        DBCon = New SqlConnection(ConnectionString)
    End Sub

    ' EXECUTE QUERY SUB
    Public Sub ExecQuery(Query As String, Optional ReturnID As Boolean = False)
        ' RESET QUERY STATS
        RecordCount = 0
        Exception = ""

        Try
            DBCon.Open()

            ' CREATE DB COMMAND
            DBCmd = New SqlCommand(Query, DBCon)

            ' LOAD PARAMS INTO DB COMMAND
            Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))

            ' CLEAR PARAM LIST
            Params.Clear()

            ' EXECUTE COMMAND & FILL DATASET
            DBDT = New DataTable
            DBDA = New SqlDataAdapter(DBCmd)
            RecordCount = DBDA.Fill(DBDT)

            If ReturnID = True Then

                Dim ReturnQuery As String = "SELECT @@IDENTITY As LastID;"
                DBCmd = New SqlCommand(ReturnQuery, DBCon)
                DBDT = New DataTable
                DBDA = New SqlDataAdapter(DBCmd)
                RecordCount = DBDA.Fill(DBDT)

            End If

        Catch ex As Exception
            ' CAPTURE ERROR
            Exception = "ExecQuery Error: " & vbNewLine & ex.Message
        Finally
            ' CLOSE CONNECTION
            If DBCon.State = ConnectionState.Open Then DBCon.Close()
        End Try
    End Sub

    ' ADD PARAMETERS
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New SqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub

    ' ERROR CHECKING
    Public Function HasException(Optional Report As Boolean = False) As Boolean
        If String.IsNullOrEmpty(Exception) Then Return False
        If Report = True Then MsgBox(Exception, MsgBoxStyle.Critical, "Exception:")
        Return True
    End Function
End Class

Я надеюсь достичь того, что при изменении значения [cboArea] следующее поле со списком [cboBay] будет обновлено следующим образом:

Private Sub cboArea_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboArea.SelectedIndexChanged

    cboBay.SelectedValue = 0

    Try

        SQL.AddParam("@SelectedValueOfcboArea", cboArea.SelectedValue)
        SQL.ExecQuery("SELECT [ID], [Name], [LocationAreaID] FROM [LocationBay] WHERE [LocationAreaID] = @SelectedValueOfcboArea")

        If SQL.HasException(True) Then Exit Sub

        cboBay.DataSource = SQL.DBDT

        cboBay.DisplayMember = "Name"
        cboBay.ValueMember = "ID"

    Catch ex As Exception

    End Try

End Sub

То, что у меня выше, похоже, работает правильно, однако, когда я впервые открываю форму с определенным c partID, я получаю следующие сообщения об ошибках:

enter image description here введите описание изображения здесь

Я считаю, что это ошибки из кода инициализации, где данные детали загружаются в каждый элемент управления, вызывая, таким образом, событие onValueChanged, которое продолжает генерировать ошибку исключения, однако я изо всех сил пытаюсь определить, как чтобы решить эту проблему, поскольку я говорю, что код работает нормально после того, как форма загружена, и пользователь сделает свое дело. лучший способ сделать это? Я знаю, что большинство людей следуют этому принципу, но я также изучал наборы данных и привязывал их к элементам управления, но я не видел статьи, в которой говорилось бы, какой стиль реализации лучше / быстрее?

1 Ответ

0 голосов
/ 12 июля 2020

Рекомендуется отделять код пользовательского интерфейса от кода базы данных. Ваше разделение очень хорошее, за исключением нескольких окон сообщений, которые вы показываете на своем SqlControl. Эту функциональность не следует смешивать с кодом вашей базы данных. Предположим, пользовательский интерфейс изменен на веб-приложение. Обратное также верно. Пользовательский интерфейс не должен заботиться о том, откуда берутся данные. Это может быть текстовый файл на локальном компьютере веб-службы на Марсе. SQLControl. Класс «разделяет» одни и те же данные со всеми экземплярами. Единственные данные, которые имеет класс, - это ConStr, так что это нормально.

Подключения и команды должны быть локальными для метода, в котором они используются. Использование ... Конец Использование блоков убедитесь, что они закрыты и утилизированы даже в случае ошибки. .Dispose важен для того, чтобы позволить соединениям возвращаться в пул соединений и освобождать неуправляемые ресурсы.

Параметры должны включать SqlDbType и Size, где это применимо. См. Следующие статьи, чтобы понять, почему. http://www.dbdelta.com/addwithvalue-is-evil/ и https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/ и еще один: https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications Вот еще https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html

Обычно идентификаторы - целые числа. Вот как я настроил код. Вам придется что-то изменить, если это действительно строки.

Возможно, вы слишком рано пытались получить слишком общий c. Как только вы освоитесь с SQL Server и vb. net, вы можете захотеть перекомбинировать часть кода, чтобы избежать дублирования.

Вы не показали использование @@Identity. ScopeIdentity немного более ограничен в том, что он возвращает, поэтому у вас больше шансов получить ожидаемое значение. В отличие от Access, Sql Сервер может выполнять более одной команды в одном операторе. Я добавил функцию для демонстрации.

К сожалению, ничего из этого не тестировалось, но я делал то же самое много раз.

Public Class Form2
    PartID As Integer
    Public Sub New(ByVal _PartID As Integer)
        InitializeComponent() 'Required at the begining of calling a new form'            
    End Sub

    Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            cboArea.DataSource = SQLControl.GetAreaData
            cboArea.DisplayMember = "Name"
            cboArea.ValueMember = "ID"
        Catch ex As Exception
            MessageBox.Show($"Error filling Area drop down list. {vbCrLf}{ex.Message}")
        End Try
        LoadPartData(PartID)
    End Sub

    Public Sub LoadPartData(_ID As Integer)
        Dim dt As DataTable
        Try
            dt = SQLControl.GetPartByID(_ID)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
            Exit Sub
        End Try
        txtID.Text = dt(0)("ID").ToString()
        txtMRPID.Text = dt(0)("MRP_ID").ToString()
        txtPartName.Text = dt(0)("PartName").ToString()
        txtManufacturer.Text = dt(0)("Manufacturer").ToString()
        txtManufacturerPartNo.Text = dt(0)("PartNumber").ToString()
        cboVendor1.SelectedValue = dt(0)("Vendor1")
        txtWebsiteVendor1.Text = dt(0)("Vendor1Link").ToString()
        cboVendor2.SelectedValue = dt(0)("Vendor2")
        txtWebsiteVendor2.Text = dt(0)("Vendor2Link").ToString()
        cboVendor3.SelectedValue = dt(0)("Vendor3")
        txtWebsiteVendor3.Text = dt(0)("Vendor3Link").ToString()
        cboArea.SelectedValue = dt(0)("LocationArea")
        cboBay.SelectedValue = dt(0)("LocationBay")
        cboRack.SelectedValue = dt(0)("LocationRack")
        cboPartAssembly.SelectedValue = dt(0)("PartAssembly")
        txtDrawingNo.Text = dt(0)("DrawingNumber").ToString()
        txtImagePath.Text = dt(0)("Image").ToString()
        If Not String.IsNullOrEmpty(txtWebsiteVendor3.Text) Then
            Process.Start(txtWebsiteVendor3.Text)
        End If
    End Sub

    Private Sub cboArea_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboArea.SelectedIndexChanged
        Dim dt As DataTable
        Try
            dt = SQLControl.GetBayDataByArea(CInt(cboArea.SelectedValue))
        Catch ex As Exception
            MessageBox.Show(ex.Message)
            Exit Sub
        End Try
        cboBay.DataSource = dt
        cboBay.DisplayMember = "Name"
        cboBay.ValueMember = "ID"
    End Sub

End Class

Public Class SQLControl

    Private Shared ConStr As String = "Your connection string" 'Set from a secure location

    Public Shared Function GetPartByID(PartID As Integer) As DataTable
        Dim dt As New DataTable
        Using cn As New SqlConnection(ConStr),
                cmd As New SqlCommand("SELECT * FROM [Part] WHERE ID = @ID", cn)
            cmd.Parameters.Add("@ID", SqlDbType.Int).Value = PartID
            cn.Open()
            dt.Load(cmd.ExecuteReader)
        End Using
        Return dt
    End Function

    Public Shared Function GetAreaData() As DataTable
        Dim dt As New DataTable
        Using cn As New SqlConnection(ConStr),
                cmd As New SqlCommand("SELECT [ID], [Name], [MRPShow] FROM [LocationArea] WHERE [MRPShow] = 1", cn)
            cn.Open()
            dt.Load(cmd.ExecuteReader)
        End Using
        Return dt
    End Function

    Public Shared Function GetBayDataByArea(AreaID As Integer) As DataTable
        Dim dt As New DataTable
        Using cn As New SqlConnection(ConStr),
                cmd As New SqlCommand("SELECT [ID], [Name], [LocationAreaID] FROM [LocationBay] WHERE [LocationAreaID] = @AreaID", cn)
            cmd.Parameters.Add("@AreaID", SqlDbType.Int).Value = AreaID
            cn.Open()
            dt.Load(cmd.ExecuteReader)
        End Using
        Return dt
    End Function

    Public Shared Function InsertData(Name As String, SomeInfo As String) As Integer
        Dim id As Integer
        Using cn As New SqlConnection(ConStr),
                cmd As New SqlCommand("Inset Into SomeTable (Name, SomeInfo) Values (@Name, @SomeInfo); Select SCOPE_IDENTITY;", cn)
            cmd.Parameters.Add("@Name", SqlDbType.NVarChar, 100).Value = Name
            cmd.Parameters.Add("@SomeInfo", SqlDbType.NVarChar, 300).Value = SomeInfo
            cn.Open()
            id = CInt(cmd.ExecuteScalar)
        End Using
        Return id
    End Function

End Class
...