Buchempfehlung
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
"Der" Petzold, das über 1000 Seiten starke Standardwerk zum Win32-API - besonders nützlich u. a. bei der GUI-Programmierung in FreeBASIC! [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Tutorial

Erste Schritte in der WinAPI mit Freebasic und FBEdit

von MitgliedstephanbrunkerSeite 9 von 13

Datei öffnen / Speichern Dialoge

Wenn wir vom User eine Datei auswählen lassen wollen, dann stellt Windows dafür fertige Dialoge zur Verfügung. Die Funktion heißt einfach GetOpenFileName, aber damit ist es natürlich nicht getan. Die dafür nötigen Parameter speichert Windows in einer OPENFILENAME Struktur, also einem UDT, dessen Member die Parameter sind, und die auch für die Ausgabe verwendet werden. Am einfachsten ist es dabei, sich einmal eine universell verwendbare Function zu bauen, die mit ein paar Anpassungen universell einbaubar ist. In der Beispieldatei öffnen wir einmal eine einzelne und dann mehrere Dateien und zeigen diese in einer Listbox an, die wir damit gleich auch abhandeln. Wenn wir die Anwendung mit der Tastatur bedienen wollen - dann noch ein Hinweis: Buttons und Checkboxen kann man mit der Leertaste auslösen bzw. umschalten - während die Return-Taste ja für den Default-Button reserviert ist.

WinTut17.jpg

Zuerst müssen wir unsere Includes um die "win\shlobj.bi" ergänzen. Wenn der Compiler meckert dass er die Funktion nicht findet und man nicht weiß, in welcher der vielen *.bi Dateien sie enthalten ist, dann empfehle ich die Windows-Suche im Verzeichnis FB\inc\win mit der Option "Dateiinhalte durchsuchen".

Um eine universell einsetzbare Helperfunction für die OPENFILENAME Struktur zu bekommen, geben wir der Funktion eine Variable modus mit, die in den unteren 4 Bits den gewünschten Filter angibt und in den oberen 4 Bits weitere Flags, im Moment ist das nur die Option, mehrere Dateien auf einmal zu laden (ALLOWMULTISELECT). Außerdem können wir das Startverzeichnis vorgeben, indem wir in die Variable Filepath einen Wert schreiben.

Die Function sieht dann so aus:

Function file_getopenname( ByVal hWnd As HWND, ByVal modus As UByte, ByRef filepath As String, filetitle () As String) As Integer
'Open File Dialog, copies the path and an array of filenames, with flags in 'modus'
'to define an inital Directory and Filename, send filepath and filetitle(0) not empty

    'Init Variables
    Dim As ZString * 2048 filelist = filetitle(0)   'Size of the buffer for the filelists and initial filename
    Dim As ZString * MAX_PATH filename
    Dim As String tempstring
    Dim As Integer i
    Dim As OPENFILENAME ofn
    Dim As UShort nfiles, pathlen,extenlen

    'Members OPENFILENAME Structure
    With ofn
        .lStructSize        = SizeOf( OPENFILENAME )
        .hwndOwner          = hWnd
        .hInstance          = hInstance
        .lpstrCustomFilter  = NULL              'no User customisation
        .nMaxCustFilter     = 0
        .nFilterIndex       = 1                 'we define our Filters seperately, preselect the first
        .lpstrFile          = @filelist         'proposed filename if first character not 0
        .nMaxFile           = SizeOf( filelist )
        .lpstrFileTitle     = @filename
        .nMaxFileTitle      = SizeOf( filename )
        .lpstrInitialDir    = StrPtr(filepath)  'start Directory - use filepath for in/out
        .nFileOffset        = pathlen
        .nFileExtension     = extenlen
        .lpstrDefExt        = NULL              'no Default extension
        .lCustData          = 0
        .lpfnHook           = NULL
        .lpTemplateName = NULL
    End With

    'Customisation File-Type-List and Multiselect
    '--------------------------------------------
    'Title = Title of the Open ... Window
    'Filter in 0-separated pairs, containing a description text and an ";" separated list of extensions
    'terminate with extra 0
    Select Case modus And 15    'Lower 4 Bits
        Case 0
            ofn.lpstrTitle  = @"Datei laden"
            ofn.lpstrFilter = StrPtr( !"Alle Dateien, (*.*)\0*.*\0\0" )
        Case 1
            ofn.lpstrTitle  = @"ausführbare Datei laden"
            ofn.lpstrFilter = StrPtr( !"Anwendung, (*.exe)\0*.exe\0\0" )
        Case 2
            ofn.lpstrTitle  = @"Freebasic-Datei laden"
            ofn.lpstrFilter = StrPtr( !"Basic-Code, (*.bas *.bi)\0*.bas;*.bi\0\0" )
        Case 3
            ofn.lpstrTitle  = @"Datei laden"
            ofn.lpstrFilter = StrPtr( !"Basic-Code, (*.bas *.bi)\0*.bas;*.bi\0Alle Dateien, (*.*)\0*.*\0\0" )
    End Select

    'Our Flag for the multiselect option
    Select Case modus Shr 4     'Higher 4 Bits
        Case 0
            ofn.Flags = OFN_EXPLORER Or OFN_FILEMUSTEXIST Or OFN_PATHMUSTEXIST
        Case 1
            ofn.Flags = OFN_EXPLORER Or OFN_FILEMUSTEXIST Or OFN_PATHMUSTEXIST Or OFN_ALLOWMULTISELECT
    End Select

    'Init Array of filenames
    '-----------------------
    ReDim filetitle (0 To 0)
    filetitle(0) = ""

    If( GetOpenFileName( @ofn ) = FALSE ) Then  'no File selected or buffer to small
        filepath = ""
        Return FALSE
    Else
        pathlen  = ofn.nFileOffset                      'get output of Openfile Function
        extenlen = ofn.nFileExtension

        If extenlen <> 0 Then                             'Single-selection
            filepath = Left(filelist,pathlen)       'devide path and filename
            filetitle(0) = filename
        Else                                                    'Multiselect
            filepath = filelist & "\"                   'Copy only the path, because of the 0-separation
            For i = pathlen To SizeOf(filelist)-1
                If filelist[i] <> 0 then                'filename continues, copy bytes
                    tempstring &= Chr(filelist[i])
                Else                                                                '0-separator
                    If tempstring = "" Then Exit For                        '00 means end of filelist
                    ReDim Preserve filetitle(0 To nFiles) As String 'extend the Array
                    filetitle(nFiles) = tempstring                      'copy to Array
                    tempstring = ""
                    nFiles += 1                                                 'next Array index
                End If
            Next i
        End If

        Return TRUE
    End If

End Function

Eine ganze Menge Code, aber ich habe das hoffentlich gut kommentiert - man kann der Funktion jede Menge Optionen mitgeben, aber man braucht nur die wichtigsten, die meisten Member der OPENFILENAME-Struktur kann man fixieren. Die Variable modus wählt aus verschiedenen Kombinationen von Fenstertitel und Filter und wählt mit einer OR-Verknüpfung die Mehrfachauswahl-Option. ALLOWMULTISELECT muss in dem Fall auf 16 definiert werden. Es macht den Code lesbarer, wenn man seine eigenen Flags verwendet und die dann einen sinnvollen Namen haben. Die Filter sind zur Auswahl bestimmter Dateitypen oder in Gruppen, jeweils mit einem 0-Zeichen separiert - was man wenn man die Strings mit einem "!" beginnen lässt mit "\0" schreiben kann. Beim Dateiöffnen sind existierende Pfade eine sinnvolle Voreinstellung und wenn man die Strings, die man der Funktion übergibt mit einem Pfad und einem Namen füllt, dann zeigt der Öffnen-Dialog direkt aufs richtige Verzeichnis und schlägt einen Dateinamen vor. Der zweite Teil der Funktion klaubt dann das Ergebnis auseinander und kopiert es in ein Array aus Strings. Tricky ist dabei, dass GetOpenFileName unterschiedlich arbeitet wenn nur eine oder mehrere Dateien ausgegeben werden. Im ersteren Fall wird ein zusammenhängender String für Pfad und Dateiname ausgeworfen, während die Längen für Pfad und Erweiterung angegeben werden. Im zweiten Fall kommt zuerst der Pfad, dann eine Null und dann die Null-separierten Dateinamen.

Die Combobox für die Auswahl der Filter hatten wir ja schon in einem vorherigen Beispiel. Der Code für den Klick auf den Button ist ziemlich simpel:

        Case WM_COMMAND 'Message sent by Usercommand
            Select Case HiWord(wParam)
                Case BN_CLICKED 'Left Mousebutton
                    Select Case LoWord(wParam)
                        Case IDC_BTN1       'Click on the Button
                            'Check Combobox
                            modus = ComboBox_GetCurSel(hCBO1)
                            'Check Checkbox and OR Multiselect Flag
                            If Button_GetCheck(hCHK1) = BST_CHECKED Then    modus Or= ALLOWMULTISELECT
                            'OPENFILENAME helper function
                            filepath="C:\Windows\"      'start Directory
                            If file_getopenname(hWin,modus,filepath,filetitle()) = TRUE Then
                                'copy the result in the Listbox
                                For i=0 To UBound(filetitle)
                                    tempstring = filepath & filetitle(i)
                                    ListBox_AddString(hLST1,StrPtr(tempstring))
                                Next i
                            End If
                            'Set focus to listbox for Keyboard Input
                            SetFocus(hLST1)

                    End Select
            End Select

Dabei werden nur der Status der Checkbox und der Combobox abgefragt (CB_GETCURSEL und BM_GETCHECK, dann die Helperfunktion aufgerufen und mit LB_ADDSTRING die Elemente des Dateinamen-Arrays in die List geschrieben. SetFocus setzt dann den Fokus auf die Liste für den Keyboardinput. Denn wir wollen die Elemente ja auch wieder löschen können mit ENTF. Wie im vorherigen Kapitel beschrieben, müssen wir dafür die Listbox in eine Subklasse verlagern:

'initializing Handles, Subclasses
    hLST1 = GetDlgItem(hMain,IDC_LST1)
    hCBO1 = GetDlgItem(hMain,IDC_CBO1)
    hCHK1 = GetDlgItem(hMain,IDC_CHK1)
    SetWindowSubclass(hLST1, @SubProc, 1, 1 )

Die erste "1" ist die ID der Subklasse die zusammen mit der Callbackfunction die Klasse eindeutig definiert. Die zweite "1" ist eine ID, die wir zusätzlich mitgeben können. Damit können wir die Aufrufe von verschiedenen Elementen auseinanderklamüsern, wenn wir für alle die gleiche Subklasse verwenden. Der Code in der Callbackfunction für den Keyboardinput sieht dann so aus:

'Subclass Callback Function
Function SubProc(ByVal hWin As HWND,ByVal uMsg As UINT,ByVal wParam As WPARAM,ByVal lParam As LPARAM,ByVal uIdSubclass As UINT_PTR, dwRefData As DWORD_PTR) As Integer
    Dim As Integer i,nStrings,sel

    Select Case uMsg
        Case WM_KEYDOWN         'Keyboard input
            Select Case wParam
                Case VK_DELETE      'Delete Key
                    'get number of selected Items to delete
                    nStrings = ListBox_GetSelCount(hLST1)
                    If nStrings = 0 Then
                        Return 0
                    ElseIf nStrings = LB_ERR Then
                        'Single select version
                        'get selected Item
                        sel = ListBox_GetCurSel(hLST1)
                        If sel = LB_ERR Then
                            Return 0
                        Else
                            'delete it
                            ListBox_DeleteString(hLST1,sel)
                        EndIf
                    Else
                        'Multiselect version
                        'allocate memory for item numbers
                        Dim psel As Integer Ptr = New Integer [nStrings]
                        'get Array of selected items and delete them
                        ListBox_GetSelItems(hLST1,nStrings,psel)
                        For i = nStrings - 1 To 0 Step -1
                            ListBox_DeleteString(hLST1,psel[i])
                        Next i
                        'free the array
                        Delete[] psel
                    EndIf
            End Select

        Case Else
            'Proceed with Default Callback Function
            Return DefSubclassProc(hWin, uMsg, wParam, lParam)
    End Select
    Return 0
End Function

Die Message heißt WM_KEYDOWN und die Werte der einzelnen Tasten stehen unter Externer Link!Virtual Key Codes. Weil es mir besser gefiel, habe ich die fehlenden Makros für die Controls verwendet und dann eben selbst in der *.bi definiert:

'ListBox Macros
#Define ListBox_DeleteString(hLST,index) SendMessage(hLST,LB_DELETESTRING,index,NULL)
#Define ListBox_AddString(hLST,htext) SendMessage(hLST,LB_ADDSTRING,NULL,Cast(LPARAM,htext))
#Define ListBox_GetCurSel(hLST) SendMessage(hLST,LB_GETCURSEL,NULL,NULL)
#Define ListBox_GetSelCount(hLST) SendMessage(hLST,LB_GETSELCOUNT,NULL,NULL)
#Define ListBox_GetSelItems(hLST,nStrings,psel) SendMessage(hLST,LB_GETSELITEMS, nStrings, Cast(LPARAM,psel))

'Button Macros
#Define Button_GetCheck(hBTN) SendMessage(hBTN,BM_GETCHECK,NULL,NULL)
#Define Button_SetCheck(hBTN) SendMessage(hBTN,BM_SETCHECK,NULL,NULL)

'Combo Box Macros
#Define ComboBox_AddString(hCBO,htext) SendMessage(hCBO,CB_ADDSTRING,NULL,Cast(LPARAM,htext))
#Define ComboBox_GetCurSel(hCBO) SendMessage(hCBO,CB_GETCURSEL,NULL,NULL)
#Define ComboBox_SetCurSel(hCBO,index) SendMessage(hCBO,CB_SETCURSEL,index,NULL)

Damit der Code universell verwendbar bleibt, wird zuerst die Anzahl der selektierten Elemente abgefragt. Das ist ein Property der Listbox die wir in der Resocurce einstellen können, Multiselect TRUE oder FALSE. Bei nur einem Element zu löschen ist es ganz einfach, in dem anderen wird ein Pointerarray übergeben. Wir reservieren zuerst einmal genügend Speicher mit NEW[], dann holen wir uns den Pointer, lesen das Array stückweise aus und löschen die Elemente von oben nach unten, denn die rutschen automatisch zusammen, am Ende nicht vergessen, den Speicher wieder freizugeben!

Das fertige Projekt: Externer Link!Tutorial6.zip

 

Gehe zu Seite Gehe zu Seite  1  2  3  4  5  6  7  8  9  10  11  12  13  
Zusätzliche Informationen und Funktionen
  Bearbeiten Bearbeiten  

  Versionen Versionen