2009-02-13 7 views
5

Chciałbym przechowywać zawartość struktury danych, kilka tablic i kilkanaście zmiennych w pliku, który można zapisać i ponownie załadowane przez moje oprogramowanie, a także opcjonalnie edytowane w edytorze tekstu przez użytkownika ponownie załadowanego. Do edycji tekstu, potrzeba mi dane być wyraźnie oznakowane, jak w dobrym ole ini file:Współczesny, przyjazny dla Unicode plik ".ini" do przechowywania danych konfiguracyjnych w VB6

AbsMaxVoltage = 17,5

Istnieje GUI, i można by argumentować, że użytkownik powinien po prostu załadować i zapisać i modyfikować z GUI, ale klient chce móc czytać i modyfikować dane jako tekst.

Łatwo jest napisać kod, aby go zapisać i przeładować (zakładając, że wszystkie etykiety znajdują się w tym samym miejscu i zmieniły się tylko dane). Przy większej ilości pracy (lub przy użyciu jakiegoś kodu INI R/W, który już tam jest, mógłbym zwrócić uwagę na etykietę, więc jeśli linia zostanie usunięta lub przeniesiona, zmienne nadal będą prawidłowo wypełnione, ale oba te podejścia wydają się dość stare- Więc interesuje mnie jak najbystrzejsze umysły w programowaniu podchodzą do tego dzisiaj (używając dziesięcioletniego VB6, który muszę przyznać, że nadal kocham)

Nota prawna: Jestem inżynierem elektrykiem, nie programistą . to nie jest mój dzień pracy. Więc może to tylko kilka% mojego dnia pracy.

Cheers!

+0

Ahhh. pytania vb6. ;)) +1 –

Odpowiedz

5

Wiele osób poleci Ci XML. Problem polega na tym, że XML jest nadal bardzo modny, some people używać go wszędzie bez tak naprawdę thinking o tym.

Podobnie jak Jeff Atwood said, jest to trudne dla osób niebędących programistami do odczytu XML, a zwłaszcza do jego edycji. Istnieje zbyt wiele reguł, takich jak usuwanie znaków specjalnych i zamykanie znaczników w odpowiedniej kolejności. Niektórzy eksperci recommend traktują XML jako format binarny, a nie format tekstowy w ogóle.

Zalecam używanie plików INI, pod warunkiem, że maksymalny limit 32 KB nie stanowi problemu. Nigdy nie osiągnąłem tego limitu w wielu podobnych sytuacjach w moim własnym VB6.Pliki INI są łatwe do edytowania przez zwykłych ludzi, a ich odczytanie i zapisanie z VB6 jest łatwe. Po prostu skorzystaj z doskonałego kodu drop-in dostępnego bezpłatnie w Internecie.

  • Jestem pewien, że class Jay Riggs przewidziane w jego odpowiedź jest doskonała, ponieważ to z VBAccelerator.
  • Polecam również ten class, , ponieważ wszystko od Karla Petersona będzie również doskonałe.

Kilka innych punktów, aby myśleć o:

  • Czy za which directory umieścić pliki do?
  • Wspomniał Pan o "przyjaznym Unicode" w pytaniu. Pliki INI aren't Unicode, ale to nie ma znaczenia w praktyce. Chyba, że ​​chcesz przechowywać znaki, które nie są obsługiwane na aktualnej stronie kodowej - jak chińskie na angielskim komputerze - nietypowe wymaganie, i takie, które i tak będą powodować other problems w programie VB6.
  • Legendarny guru Windows, Raymond Chen, opisał the advantages of XML configuration files over INI files. Wiele z nich polega na tym, że plik XML jest tylko do odczytu. Jedną prawnie korzystną zaletą jest to, że dane są wysoce skonstruowane - hierarchiczne lub podobne. Z Twojego opisu, który nie ma zastosowania.
+0

Wspomniałem "przyjazny dla unicode", ponieważ nasze oprogramowanie jest często używane przez klientów w Azji i czasami widzimy niespodziewane niezgodności, które zwykle sprowadzają się do unicode (funkcja len() zawodzi lub zgłasza złą wartość itp.). Dzięki za głęboką odpowiedź. –

+0

Nasze oprogramowanie jest używane przez klientów w Chinach - pliki INI działają dobrze. W VB6 są jednak pewne minusy. Książka Michaela Kaplana o internacjonalizacji w VB6 jest bardzo przydatna, szczególnie jeśli przetłumaczysz interfejs użytkownika. zobacz http://www.i18nwithvb.com/ – MarkJ

+0

Widzę, że masz siedzibę w USA. Jeśli użyjesz znaków z zakresu Chr $ (128) -Chr $ (255) w ciągach kodowanych na sztywno lub w innym miejscu, będą one powodować problemy w Azji. Znaki z zakresu chr $ (1) - chr $ (127) są obsługiwane na wszystkich stronach kodowych. – MarkJ

5

Rozważ użycie XML. to całkowicie standard, wiele edytorów zaprezentuje/zarządzać salo Rly, każdy język programowania i skryptowy na Ziemi ma dobre wsparcie w czytaniu i doskonale obsługuje Unicode.

Dla prostych par nazwa/wartość, jak sugerujesz, jest całkiem czytelny. Ale masz dodatkową zaletę, że jeśli pewnego dnia potrzebujesz czegoś bardziej złożonego - np. wartości wielowierszowe lub lista odrębnych wartości - XML ​​zapewnia naturalne, łatwe sposoby ich reprezentowania.

P.S. Here's how to read XML in VB6.

+0

Artykuł jest trochę przestarzały, odniesienie Msxml3. – AnthonyWJones

+0

Tak, ale pytanie dotyczy wyłącznie * VB6 *! Ponieważ jest to wyraźnie system starszego typu, uznałem za stosowne powiązanie ze starszą odpowiedzią! –

+0

Msxml3 nie jest ostrze;) Odwołanie do artykułu 2.6, które może prowadzić do różnego rodzaju dziwności. Referencja 3.0 jest znacznie czystsza i jest obecna nawet na dość "starszych" platformach. – AnthonyWJones

5

Już w dawnych czasach this klasa pomógł mi używać plików INI z moich programów VB6:

VERSION 1.0 CLASS 
BEGIN 
    MultiUse = -1 'True 
END 
Attribute VB_Name = "cInifile" 
Attribute VB_GlobalNameSpace = False 
Attribute VB_Creatable = True 
Attribute VB_PredeclaredId = False 
Attribute VB_Exposed = False 
Option Explicit 
' ========================================================= 
' Class: cIniFile 
' Author: Steve McMahon 
' Date : 21 Feb 1997 
' 
' A nice class wrapper around the INIFile functions 
' Allows searching,deletion,modification and addition 
' of Keys or Values. 
' 
' Updated 10 May 1998 for VB5. 
' * Added EnumerateAllSections method 
' * Added Load and Save form position methods 
' ========================================================= 

Private m_sPath As String 
Private m_sKey As String 
Private m_sSection As String 
Private m_sDefault As String 
Private m_lLastReturnCode As Long 

#If Win32 Then 
    ' Profile String functions: 
    Private Declare Function WritePrivateProfileString Lib "KERNEL32" Alias 
    "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal 
    lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As 
    Long 
    Private Declare Function GetPrivateProfileString Lib "KERNEL32" Alias 
    "GetPrivateProfileStringA" (ByVal lpApplicationName As Any, ByVal 
    lpKeyName As Any, ByVal lpDefault As Any, ByVal lpReturnedString As 
    String, ByVal nSize As Long, ByVal lpFileName As String) As Long 
#Else 
    ' Profile String functions: 
    Private Declare Function WritePrivateProfileString Lib "Kernel" (ByVal 
    lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As 
    Any, ByVal lpFileName As String) As Integer 
    Private Declare Function GetPrivateProfileString Lib "Kernel" (ByVal 
    lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As 
    Any, ByVal lpReturnedString As String, ByVal nSize As Integer, ByVal 
    lpFileName As String) As Integer 
#End If 

Property Get LastReturnCode() As Long 
    LastReturnCode = m_lLastReturnCode 
End Property 
Property Get Success() As Boolean 
    Success = (m_lLastReturnCode <> 0) 
End Property 
Property Let Default(sDefault As String) 
    m_sDefault = sDefault 
End Property 
Property Get Default() As String 
    Default = m_sDefault 
End Property 
Property Let Path(sPath As String) 
    m_sPath = sPath 
End Property 
Property Get Path() As String 
    Path = m_sPath 
End Property 
Property Let Key(sKey As String) 
    m_sKey = sKey 
End Property 
Property Get Key() As String 
    Key = m_sKey 
End Property 
Property Let Section(sSection As String) 
    m_sSection = sSection 
End Property 
Property Get Section() As String 
    Section = m_sSection 
End Property 
Property Get Value() As String 
Dim sBuf As String 
Dim iSize As String 
Dim iRetCode As Integer 

    sBuf = Space$(255) 
    iSize = Len(sBuf) 
    iRetCode = GetPrivateProfileString(m_sSection, m_sKey, m_sDefault, sBuf, 
    iSize, m_sPath) 
    If (iSize > 0) Then 
     Value = Left$(sBuf, iRetCode) 
    Else 
     Value = "" 
    End If 

End Property 
Property Let Value(sValue As String) 
Dim iPos As Integer 
    ' Strip chr$(0): 
    iPos = InStr(sValue, Chr$(0)) 
    Do While iPos <> 0 
     sValue = Left$(sValue, (iPos - 1)) & Mid$(sValue, (iPos + 1)) 
     iPos = InStr(sValue, Chr$(0)) 
    Loop 
    m_lLastReturnCode = WritePrivateProfileString(m_sSection, m_sKey, sValue, 
    m_sPath) 
End Property 
Public Sub DeleteKey() 
    m_lLastReturnCode = WritePrivateProfileString(m_sSection, m_sKey, 0&, 
    m_sPath) 
End Sub 
Public Sub DeleteSection() 
    m_lLastReturnCode = WritePrivateProfileString(m_sSection, 0&, 0&, m_sPath) 
End Sub 
Property Get INISection() As String 
Dim sBuf As String 
Dim iSize As String 
Dim iRetCode As Integer 

    sBuf = Space$(8192) 
    iSize = Len(sBuf) 
    iRetCode = GetPrivateProfileString(m_sSection, 0&, m_sDefault, sBuf, iSize, 
    m_sPath) 
    If (iSize > 0) Then 
     INISection = Left$(sBuf, iRetCode) 
    Else 
     INISection = "" 
    End If 

End Property 
Property Let INISection(sSection As String) 
    m_lLastReturnCode = WritePrivateProfileString(m_sSection, 0&, sSection, 
    m_sPath) 
End Property 
Property Get Sections() As String 
Dim sBuf As String 
Dim iSize As String 
Dim iRetCode As Integer 

    sBuf = Space$(8192) 
    iSize = Len(sBuf) 
    iRetCode = GetPrivateProfileString(0&, 0&, m_sDefault, sBuf, iSize, m_sPath) 
    If (iSize > 0) Then 
     Sections = Left$(sBuf, iRetCode) 
    Else 
     Sections = "" 
    End If 

End Property 
Public Sub EnumerateCurrentSection(ByRef sKey() As String, ByRef iCount As Long) 
Dim sSection As String 
Dim iPos As Long 
Dim iNextPos As Long 
Dim sCur As String 

    iCount = 0 
    Erase sKey 
    sSection = INISection 
    If (Len(sSection) > 0) Then 
     iPos = 1 
     iNextPos = InStr(iPos, sSection, Chr$(0)) 
     Do While iNextPos <> 0 
      sCur = Mid$(sSection, iPos, (iNextPos - iPos)) 
      If (sCur <> Chr$(0)) Then 
       iCount = iCount + 1 
       ReDim Preserve sKey(1 To iCount) As String 
       sKey(iCount) = Mid$(sSection, iPos, (iNextPos - iPos)) 
       iPos = iNextPos + 1 
       iNextPos = InStr(iPos, sSection, Chr$(0)) 
      End If 
     Loop 
    End If 
End Sub 
Public Sub EnumerateAllSections(ByRef sSections() As String, ByRef iCount As 
Long) 
Dim sIniFile As String 
Dim iPos As Long 
Dim iNextPos As Long 
Dim sCur As String 

    iCount = 0 
    Erase sSections 
    sIniFile = Sections 
    If (Len(sIniFile) > 0) Then 
     iPos = 1 
     iNextPos = InStr(iPos, sIniFile, Chr$(0)) 
     Do While iNextPos <> 0 
      If (iNextPos <> iPos) Then 
       sCur = Mid$(sIniFile, iPos, (iNextPos - iPos)) 
       iCount = iCount + 1 
       ReDim Preserve sSections(1 To iCount) As String 
       sSections(iCount) = sCur 
      End If 
      iPos = iNextPos + 1 
      iNextPos = InStr(iPos, sIniFile, Chr$(0)) 
     Loop 
    End If 

End Sub 
Public Sub SaveFormPosition(ByRef frmThis As Object) 
Dim sSaveKey As String 
Dim sSaveDefault As String 
On Error GoTo SaveError 
    sSaveKey = Key 
    If Not (frmThis.WindowState = vbMinimized) Then 
     Key = "Maximised" 
     Value = (frmThis.WindowState = vbMaximized) * -1 
     If (frmThis.WindowState <> vbMaximized) Then 
      Key = "Left" 
      Value = frmThis.Left 
      Key = "Top" 
      Value = frmThis.Top 
      Key = "Width" 
      Value = frmThis.Width 
      Key = "Height" 
      Value = frmThis.Height 
     End If 
    End If 
    Key = sSaveKey 
    Exit Sub 
SaveError: 
    Key = sSaveKey 
    m_lLastReturnCode = 0 
    Exit Sub 
End Sub 
Public Sub LoadFormPosition(ByRef frmThis As Object, Optional ByRef lMinWidth = 
3000, Optional ByRef lMinHeight = 3000) 
Dim sSaveKey As String 
Dim sSaveDefault As String 
Dim lLeft As Long 
Dim lTOp As Long 
Dim lWidth As Long 
Dim lHeight As Long 
On Error GoTo LoadError 
    sSaveKey = Key 
    sSaveDefault = Default 
    Default = "FAIL" 
    Key = "Left" 
    lLeft = CLngDefault(Value, frmThis.Left) 
    Key = "Top" 
    lTOp = CLngDefault(Value, frmThis.Top) 
    Key = "Width" 
    lWidth = CLngDefault(Value, frmThis.Width) 
    If (lWidth < lMinWidth) Then lWidth = lMinWidth 
    Key = "Height" 
    lHeight = CLngDefault(Value, frmThis.Height) 
    If (lHeight < lMinHeight) Then lHeight = lMinHeight 
    If (lLeft < 4 * Screen.TwipsPerPixelX) Then lLeft = 4 * 
    Screen.TwipsPerPixelX 
    If (lTOp < 4 * Screen.TwipsPerPixelY) Then lTOp = 4 * Screen.TwipsPerPixelY 
    If (lLeft + lWidth > Screen.Width - 4 * Screen.TwipsPerPixelX) Then 
     lLeft = Screen.Width - 4 * Screen.TwipsPerPixelX - lWidth 
     If (lLeft < 4 * Screen.TwipsPerPixelX) Then lLeft = 4 * 
     Screen.TwipsPerPixelX 
     If (lLeft + lWidth > Screen.Width - 4 * Screen.TwipsPerPixelX) Then 
      lWidth = Screen.Width - lLeft - 4 * Screen.TwipsPerPixelX 
     End If 
    End If 
    If (lTOp + lHeight > Screen.Height - 4 * Screen.TwipsPerPixelY) Then 
     lTOp = Screen.Height - 4 * Screen.TwipsPerPixelY - lHeight 
     If (lTOp < 4 * Screen.TwipsPerPixelY) Then lTOp = 4 * 
     Screen.TwipsPerPixelY 
     If (lTOp + lHeight > Screen.Height - 4 * Screen.TwipsPerPixelY) Then 
      lHeight = Screen.Height - lTOp - 4 * Screen.TwipsPerPixelY 
     End If 
    End If 
    If (lWidth >= lMinWidth) And (lHeight >= lMinHeight) Then 
     frmThis.Move lLeft, lTOp, lWidth, lHeight 
    End If 
    Key = "Maximised" 
    If (CLngDefault(Value, 0) <> 0) Then 
     frmThis.WindowState = vbMaximized 
    End If 
    Key = sSaveKey 
    Default = sSaveDefault 
    Exit Sub 
LoadError: 
    Key = sSaveKey 
    Default = sSaveDefault 
    m_lLastReturnCode = 0 
    Exit Sub 
End Sub 
Public Function CLngDefault(ByVal sString As String, Optional ByVal lDefault As 
Long = 0) As Long 
Dim lR As Long 
On Error Resume Next 
    lR = CLng(sString) 
    If (Err.Number <> 0) Then 
     CLngDefault = lDefault 
    Else 
     CLngDefault = lR 
    End If 
End Function 
+0

Dzięki. I nie chciałem obrażać się z komentarzy "dawnych czasów" - widziałem, że ludzie robią naprawdę imponujące (dla mnie) rzeczy w VB6. –

+0

Ja na pewno nie byłam obrażona! Zajrzę do klasy, którą zasugerowałeś, kiedy wrócę do pracy (na wakacjach w tym tygodniu). –

+0

Obecnie link jest już martwy. –

1

Would i XML plik do zaakceptowania: -

<config> 
    <someAppPart 
     AbsMaxVoltage="17.5" 
     AbsMinVoltage="5.5" 
    /> 
    <someOtherAppPart 
     ForegroundColor="Black" 
     BackgroundColor="White" 
    /> 
</config> 

bardzo łatwo spożywać w VB6 , nie musisz się martwić o pozycjonowanie itp.

Wadą jest to, że użytkownik może poprawić swoje parametry, ale to prawda, jeśli napiszesz własny parser dla pliku konfiguracyjnego.

+0

To jest wadą, o której wspomniałeś, że mnie to niepokoi (również większość moich klientów jest trochę niewyszukana i zobaczy zagnieżdżoną strukturę i formatowanie jako ból). Dzięki za sugestię - od miesięcy słuchałem podcastu Stack Overflow; wspaniale jest widzieć społeczność w akcji! –

+0

To nie jest tak naprawdę bardziej zagnieżdżone niż typowe .ini, chociaż może być bardziej przerażające, nie powinno się fazować kogoś, kto chce poprawić coś, co nazywa się AbsMaxVoltage;) – AnthonyWJones

0

Jeśli możemy założyć zapisanych ustawienia są po prostu zbiorem par nazwa/wartość bez wymogu hierarchii dwupoziomowego (tj INI „Keys” wewnątrz „sekcjami”), użytkownik może po prostu utrzymują je jako takie:

AbsMaxVoltage=17.5 
AbsMinVoltage=5.5 

W przypadku pisania formatu trwałości jest to przypadek, w którym można rozważyć FSO, ponieważ i tak poziom dostępu jest niski. FSO może obsłużyć pliki tekstowe Unicode do odczytu/zapisu.

Myślę, że zrobiłbym coś w rodzaju czytania linii i analizowania ich za pomocą Split() na "=" określając tylko 2 części (co pozwala również "=" wewnątrz wartości). Aby załadować te pliki, zapisałbym je w prostej instancji klasy, w której klasa ma dwie właściwości (nazwę i wartość) i dodaje każdą do kolekcji, używając nazwy jako klucza. Ustaw wartość jako domyślną właściwość, jeśli chcesz.

Może nawet zaimplementować jakąś formę linii tekstu komentarza za pomocą wygenerowanej specjalnej wartości o numerach seryjnych przechowywanej jako say Name = "% 1" Wartość = "tekst komentarza" z wygenerowanymi niepowtarzalnymi nazwami w celu uniknięcia kolizji kluczy zbiorczych. Puste linie mogą być podobnie zachowane.

Kontynuowanie w razie potrzeby oznacza po prostu użycie opcji Dla każdego zbioru i użycie FSO do zapisania nazwy = wartość na dysk.

Aby symulować hierarchię można po prostu używać nazw takich jak:

%Comment: somAppPart settings 
someAppPart.AbsMaxVoltage=17.5 
someAppPart.AbsMinVoltage=5.5 

%someOtherPart settings 
someOtherAppPart.ForegroundColor=Black 
someOtherAppPart.BackgroundColor=White 

parsowanie jest tanie, więc każdy sondowania z Kolekcji może być poprzedzone pełnym ponownej analizy (jak zrobić wywołania API INI). Każda zmiana wartości w programie może całkowicie przerobić na dysk (tak jak robią to wywołania interfejsu INI API).

Niektóre z nich można zautomatyzować, po prostu opakowując kolekcję z pewną logiką w innej klasie.Rezultatem może być składnia jak:

Settings("someOtherAppPart", "ForegroundColor") = "Red" 

aka

Settings.Value("someOtherAppPart", "ForegroundColor") = "Red" 

To przeładowanie Collection, następnie zbadaj kolekcja dla elementu nadwozia „someOtherAppPart.ForegroundColor” i utworzyć lub ustawić jego wartość na " Czerwony ", a następnie przepłukać kolekcję na dysk. Możesz też uniknąć częstego przepisywania i używać różnych metod Load and Save.

Ułóż to tak proste lub fantazyjne, jak chcesz.

W każdym przypadku wynikiem jest plik tekstowy, z którego użytkownicy mogą włamać się za pomocą Notatnika. Jedynym powodem dla FSO jest łatwy sposób odczytu/zapisu tekstu w Unicode. Można również wkręcić z użyciem macierzy Byte array I/O i jawnych konwersji (array to String) i parsowania poziomu linii zgodnie z wymaganiami, aby uniknąć FSO. Jeśli tak, nie zapomnij o zestawieniu komponentów UTF-16LE.