Juni 24, 2021, 21:15:08

Neuigkeiten:

Ist euer Problem gelöst, dann bitte den Knopf "Thema gelöst" drücken!


Noch'n Tipp: visuelle Selektion mit Suche

Begonnen von crystal, April 16, 2021, 23:06:05

⏪ vorheriges - nächstes ⏩

crystal

April 16, 2021, 23:06:05 Letzte Bearbeitung: April 16, 2021, 23:11:50 von crystal Grund: Tippfehler-Korrekturen
Hallo Gemeinde,
heute habe ich meine Spendierhosen an...

Oft ist es doch so, dass man einen Datensatz suchen will, man kennt ungefähr ein paar Suchkriterien.
Man kann das mit einem UFo realisieren und im Kopf des Formulars Suchfelder anbieten.
Leider sind UFos nicht in der Lage, Datensätze unter- UND nebeneinander oder auch nur nebeneinander darzustellen und so wenig ergonomisch.
Mit ein wenig Fleiß und relativ wenig VBA-Code geht's auch anders. Für eine kleine, private Video-Verwaltung möchte ich z. B. nach Schauspielern suchen und habe dafür folgendes Formular entworfen:

Sie dürfen in diesem Board keine Dateianhänge sehen.

Ja - das sieht recht komplex aus, ist aber nicht so schwierig.
Es funktioniert so:
Im Formularkopf (dunkelgelb) können Suchkriterien definiert werden. Bei Feldern, hinter denen sich ein Select-Button befindet, können via Auswahl-Fenster jeweils mehrere Tags ausgewählt werden. Deren interne Referenzen werden in den grünen Feldern zur späteren Verwendung gespeichert (unsichtbar).

Nach Klicken des Lupe-Buttons wird ein SQL-String für die Suche gebastelt und ein Recordset ReadOnly geöffnet (im Code einfach Mal auf 200 Records begrenzt); finden sich mehr als 200 Records, erfolgt eine entspr. Warnung, ebenso wenn nichts gefunden wird.

Der Recordset wird eingelesen und nur die relevanten Felder (Name, Bild und interner Schlüssel) werden in entspr. Arrays kopiert.
Danach werden die ersten 20 Records angezeigt, der User kann zu anderen Seiten blättern (falls mehr als 20 Records gefunden werden).
Zur Darstellung der Funde dienen (hier) 20 Blöcke mit je 3 Feldern:
Name "NameX", Bild "BildX" und darüber gelegt ein transparenter Button "ButtonX" mit X = 1 bis 20. Im OnClick-Event der Buttons steht "=ButtonGeklickt(X)".
Um das Formular aufzubauen, habe ich einen solchen Block einmal definiert, dann als Zeile 5mal dupliziert und die Zeilen schließlich 4mal eingefügt. Dann noch die Namen angepasst und den Aufruf in den Buttons. Ist halt etwas Fleißarbeit...

In der Funktion "ButtonGeklickt(intButton as integer)" passiert zur Laufzeit alles andere. Es wird ein Popup-Dialog aufgerufen, der den Schauspieler komplett mit Namen und weiteren Daten sowie einem größeren Bild darstellt. Dieser Dialog hat Buttons "Übernehmen" und "Abbruch". Wird "Übernehmen" geklickt, wird der Rand des aufrufenden Buttons etwas dicker und rot dargestellt (ansonsten Haarlinie und schwarz). Die erfolgte Selektion merke ich mir in einem 200er-Boolean-Array.

Beim Wechsel zu einer anderen Seite werden andere (bis zu 20) Records dargestellt und ggf. rot umrandet, weil sie schon selektiert wurden. Der Code hinter "Nächste Seite" z. B. lautet sehr simpel:
Private Sub btnNext_Click()
    Call CheckPage(txtPageNo + 1)
End Sub
"CheckPage" macht den Rest, z. B. Start-Position der 200er-Arrays setzen, (bis zu) 20 Datensätze anzeigen (bzw. die restlichen Boxen leeren. Alles sehr simpel gestrickt.

Wer Interesse an dieser Lösung hat, kann ja schreiben und ich stelle kostenlos eine abgespeckte DB zur Verfügung. Das mache ich aber erst, wenn tatsächlich Interesse besteht... lol.

Gruß,
crystal
Wer Fehler in meinen Antworten findet, darf sie behalten, muss sie aber kommentieren. ;-)
Dies ist keineswegs arrogant gemeint, sondern soll nur unterstreichen, dass meine Antworten - natürlich - nicht immer fehlerfrei sind und sein können.
Devise: bitte immer erst selbst probieren!

Aus gesundheitlichen Gründen nur noch selten dabei...

Xoar

Moin,

ich bin grundsätzlich interessiert. Weniger um die Nutzung der DB, sondern eher an die Art der Umsetzung.

Frei nach dem Motto, man muss das Rad ja nicht neu erfinden, könnte ich ggf. was für mich übernehmen.

🤗

crystal

@Xoar
Hallo!
Hier einfach mal die Code-Sequenzen.

Aufruf des Such-Formulars im Movie-Formular:
Private Sub btnSearchActor_Click()
Dim i As Integer
Dim strActorsToAdd() As String
Dim strSQL As String

    DoCmd.OpenForm "modalSearchActors", acNormal, , , , acDialog
    If g_bCancel Then Exit Sub
    strActorsToAdd = Split(g_sActorsSelected, ",")
    For i = LBound(strActorsToAdd) To UBound(strActorsToAdd)
        If strActorsToAdd(i) <> "" Then
            strSQL = "INSERT INTO [MovieActors]([MovieActorMovieRef], [MovieActorActorRef]) VALUES (" & _
                g_lMovieId & ", " & strActorsToAdd(i) & ");"
            'Debug.Print strSQL
            CurrentDb.Execute strSQL, 128
        End If
    Next
    Me.subMovieActors.Form.Requery
   
End Sub

Anmerkungen: Variable, deren Name mit "g_" beginnt, sind global, also in einem Modul definiert.

Im Movie-Formular gibt's ein Ufo, in dem die Darsteller des Films angezeigt werden. Ich fülle einfach die zugrunde liegende Tabelle MovieActors per SQL mit den im Such-Formular gewählten Darstellern, die als "Nr1,Nr2,Nr3," in einem globalen String stehen....

Und hier der komplette Code des Such-Formulars.
Der Einfachheit halber habe ich im Formularkopf alle Felder außer Name entfernt (s. Bild im ersten Post).
Option Compare Database
Option Explicit

Dim lngActorId(1 To 200) As Long
Dim strActorName(1 To 200) As String
Dim strFileName(1 To 200) As String
Dim blnActorSelected(1 To 200) As Boolean
Dim intActorsMax As Integer

Public Function TriggerButton(intButton As Integer)
Dim i As Integer

    i = (txtPageNo - 1) * 20 + intButton
   
    If Me.Controls("btnDummy" & intButton).BorderColor = vbRed Then
        Me.Controls("btnDummy" & intButton).BorderColor = vbBlack
        Me.Controls("btnDummy" & intButton).BorderWidth = 1
        blnActorSelected(i) = False
        txtSelected = txtSelected - 1
    Else
'vvv uncomment to use modal actor form
'        DoCmd.OpenForm "Actors", acNormal, , , , acDialog, "Actor=" & lngActorId(i)
'        If g_bActorSelected Then
            Me.Controls("btnDummy" & intButton).BorderColor = vbRed
            Me.Controls("btnDummy" & intButton).BorderWidth = 3
            blnActorSelected(i) = True
            txtSelected = txtSelected + 1
'        End If
    End If
    Call Check_txtSelected
   
End Function

Public Function TriggerBox(intBox As Integer, blnVisible As Boolean)
   
    Me.Controls("txtActor" & intBox).Visible = blnVisible
    Me.Controls("PicMain" & intBox).Visible = blnVisible
    Me.Controls("btnDummy" & intBox).Visible = blnVisible
   
End Function

Private Sub btnDelFilter_Click()
Dim i As Integer
   
    txtName = ""
   
    cboOrder = ""
   
    txtCount.Visible = False
    txtPageNo.Visible = False
    txtPageMax.Visible = False
    txtSelected.Visible = False
    btnRetFound.Visible = False
   
    btnFirst.Visible = False
    btnNext.Visible = False
    btnPrevious.Visible = False
    btnLast.Visible = False
   
    Call ClearAll
   
    txtName.SetFocus

End Sub

Private Sub btnExit_Click()
   
    g_sTagListActor = ""
    g_bCancel = True
    Call ClearAll
    DoCmd.Close acForm, Me.Form.Name

End Sub

Private Sub btnFilter_Click()
Dim i As Integer
Dim s1 As String
Dim s2 As String
Dim rst As DAO.Recordset

    Call ClearAll
   
    s2 = ""
   
    If Nz(txtName, "") <> "" Then
        If InStr(txtName, "*") > 0 Then
            s2 = "(Actors.ActorName LIKE ^" & txtName & "^)"
        Else
            s2 = "(Actors.ActorName = ^" & txtName & "^)"
        End If
    End If
   
   
    If s2 = "" Then
        MsgBox "No search criteria given", vbOKOnly + vbExclamation, "Note"
        Exit Sub
    End If
       
    s1 = "SELECT Actors.ActorId, Actors.ActorName," & _
         " IIf(Actors.ActorMainPic > ^^,ReturnActorPic(Actors.ActorMainPic),^^) AS FullName" & _
         " FROM Actors"

    s1 = s1 & " WHERE ("
    s1 = s1 & s2 & ") "
   
    s1 = Replace(s1, "^", Chr$(34))
   
    Select Case Nz(cboOrder, "0")
        Case 0
            'nothing
        Case 1
            s1 = s1 & "ORDER BY Actors.ActorName"
        Case 2
            s1 = s1 & "ORDER BY Actors.ActorName DESC"
    End Select
   
    s1 = s1 & ";"
   
    'Debug.Print s1
   
    Set rst = CurrentDb.OpenRecordset(s1)
    txtCount.Visible = True
   
    If rst.RecordCount = 0 Then
        txtCount = 0
        MsgBox "No records found", vbOKOnly + vbExclamation, "Note"
        Exit Sub
    End If
   
    rst.MoveLast
    rst.MoveFirst
   
    txtCount = rst.RecordCount
    If rst.RecordCount > 200 Then
        MsgBox "Too many records found", vbOKOnly + vbExclamation, "Note"
        Exit Sub
    End If

    txtPageNo = 1
    txtPageMax = (txtCount + 19) \ 20
    Call CheckPage(1)
   
    i = 0
    Do Until rst.EOF
        i = i + 1
        lngActorId(i) = CLng(rst.Fields("ActorId"))
        strActorName(i) = rst.Fields("ActorName")
        strFileName(i) = rst.Fields("FullName")
        blnActorSelected(i) = False
        rst.MoveNext
    Loop
    intActorsMax = i
    rst.Close
    Set rst = Nothing
   
    txtPageNo.Visible = True
    txtPageMax.Visible = True
    txtSelected.Visible = True
    txtSelected = 0
   
    Call DisplayActors(1)
   
End Sub

Private Sub btnLast_Click()
    Call CheckPage(txtPageMax)
End Sub

Private Sub btnNext_Click()
    Call CheckPage(txtPageNo + 1)
End Sub

Private Sub btnPrevious_Click()
    Call CheckPage(txtPageNo - 1)
End Sub

Private Sub btnFirst_Click()
    Call CheckPage(1)
End Sub

Private Sub btnRetFound_Click()
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim lngActorsSelected(1 To 20) As Long
Dim blnAlreadySet As Boolean
Dim s As String

    If txtSelected = 0 Then
        If MsgBox("No Actor selected. Exit now?", vbYesNo + vbQuestion, "No Actor") = vbYes Then
            g_sTagListActor = ""
            g_bCancel = True
            DoCmd.Close acForm, Me.Form.Name
            Exit Sub
        End If
    End If
   
    i = 0
    For j = 1 To txtCount
        If blnActorSelected(j) Then
            If i = 0 Then
                i = 1
                lngActorsSelected(i) = lngActorId(j)
            Else
                For k = 1 To i
                    If lngActorsSelected(i) = lngActorId(j) Then
                        blnAlreadySet = True
                        Exit For
                    End If
                    If Not blnAlreadySet Then
                        i = i + 1
                        lngActorsSelected(i) = lngActorId(j)
                    End If
                Next
            End If
        End If
    Next
    For j = 1 To i
        s = s & lngActorsSelected(j) & ", "
    Next
    'MsgBox "Selected: " & s
    g_sActorsSelected = s
    DoCmd.Close acForm, Me.Form.Name

End Sub

Private Sub Form_Load()
Dim i As Integer

    Call ClearAll
   
    btnLast.Visible = False
    btnNext.Visible = False
    btnPrevious.Visible = False
    btnFirst.Visible = False
   
    txtCount.Visible = False
    txtPageNo.Visible = False
    txtPageMax.Visible = False
    txtSelected.Visible = False
   
    btnRetFound.Visible = False
   
End Sub

Public Sub DisplayActors(intStart As Integer)
Dim i As Integer
Dim intTarget As Integer

    For i = 1 To 20
        intTarget = intStart + i - 1
        If Nz(lngActorId(intTarget), 0) > 0 Then
            Me.Controls("txtActor" & i) = strActorName(intTarget)
            Me.Controls("picMain" & i).Picture = strFileName(intTarget)
            If blnActorSelected(intTarget) Then
                Me.Controls("btnDummy" & i).BorderColor = vbRed
                Me.Controls("btnDummy" & i).BorderWidth = 3
            Else
                Me.Controls("btnDummy" & i).BorderColor = vbBlack
                Me.Controls("btnDummy" & i).BorderWidth = 1
            End If
            Call TriggerBox(i, True)
        Else
            Call TriggerBox(i, False)
        End If
       
    Next
   
End Sub
Public Sub CheckPage(ByVal intNewPage As Integer)
Dim i As Integer

    If intNewPage > 20 Then intNewPage = 20
    If intNewPage > txtPageMax Then intNewPage = txtPageMax
    If intNewPage < 1 Then intNewPage = 1
   
    txtPageNo = intNewPage
    btnDummy.SetFocus
   
    If (txtPageNo = 1) And (txtPageMax = 1) Then
        btnLast.Visible = False
        btnNext.Visible = False
        btnPrevious.Visible = False
        btnFirst.Visible = False
    ElseIf txtPageNo = txtPageMax Then
        btnLast.Visible = False
        btnNext.Visible = False
        btnPrevious.Visible = True
        btnFirst.Visible = True
    ElseIf txtPageNo = 1 Then
        btnLast.Visible = True
        btnNext.Visible = True
        btnPrevious.Visible = False
        btnFirst.Visible = False
    Else
        btnLast.Visible = True
        btnNext.Visible = True
        btnPrevious.Visible = True
        btnFirst.Visible = True
    End If
   
    i = (txtPageNo - 1) * 20 + 1
    Call DisplayActors(i)

End Sub
Public Sub ClearAll()
Dim i As Integer

    If intActorsMax > 0 Then
        For i = 1 To intActorsMax
            lngActorId(i) = 0
            strActorName(i) = ""
            strFileName(i) = ""
            blnActorSelected(i) = False
            intActorsMax = 0
        Next
    End If
   
    For i = 1 To 20
        Call TriggerBox(i, False)
        Me.Controls("txtActor" & i) = ""
        Me.Controls("PicMain" & i).Picture = ""
    Next

End Sub

Private Sub Check_txtSelected()
    If txtSelected > 0 Then
        btnRetFound.Visible = True
    Else
        btnRetFound.Visible = False
    End If
End Sub

Noch 'ne Anmerkung:
in einer globalen Funktion stze ich den vollständigen Namen der Bilddatei wie folgt zusammen:
Public Function ReturnActorPic(strPic As String) As String
Dim strFile As String

    If Nz(strPic, "") = "" Then
        ReturnActorPic = ""
        Exit Function
    ElseIf strFile = "_nopic.jpg" Then
        ReturnActorPic = g_sDirActorPics & "_nopic.jpg"
        Exit Function
    End If
   
    If Asc(Left(strPic, 1)) < 64 Then
        strFile = g_sDirActorPics & "0\" & strPic
    Else
        strFile = g_sDirActorPics & Left(strPic, 1) & "\" & strPic
    End If
   
    ReturnActorPic = strFile
   
End Function
Die Bilder liegen in Verzeichnissen unterhalb von g_sDirActorPics (z. B. "d:\bilder\"), die aus dem ersten Buchstaben des Namens bestehen.

Viel Spaß und Gruß,
crystal
Wer Fehler in meinen Antworten findet, darf sie behalten, muss sie aber kommentieren. ;-)
Dies ist keineswegs arrogant gemeint, sondern soll nur unterstreichen, dass meine Antworten - natürlich - nicht immer fehlerfrei sind und sein können.
Devise: bitte immer erst selbst probieren!

Aus gesundheitlichen Gründen nur noch selten dabei...

Xoar