Современный, удобный для юникода файл .ini для хранения данных конфигурации в VB6.

Я хотел бы сохранить содержимое структуры данных, несколько массивов и около дюжины переменных в файле, который может быть сохранен и перезагружен моим программным обеспечением, а также при необходимости отредактирован в текстовом редакторе пользователем, перезагруженным. Для редактирования текста мне нужно, чтобы данные были четко обозначены, как в хорошем старом .ini файле:

AbsMaxVoltage = 17,5

Есть графический интерфейс, и можно утверждать, что пользователь должен просто загружать, сохранять и изменять из графического интерфейса, но клиент хочет иметь возможность читать и изменять данные как текст.

Достаточно легко написать код, чтобы сохранить его и перезагрузить (при условии, что все метки находятся в одном месте и изменились только данные). При дополнительной работе (или использовании некоторого кода INI R / W, который уже существует, я мог бы обратить внимание на метку, поэтому, если строка удаляется или перемещается, переменные все равно заполняются правильно, но оба этих подхода кажутся довольно старыми - Так что меня интересует, как к этому подойдут самые блестящие умы в программировании (используя VB6 десятилетней давности, который, должен признаться, мне до сих пор нравится).

Отказ от ответственности: я инженер-электрик, а не программист. Это не моя основная работа. Ну, может быть, это несколько% моей дневной работы.

Ваше здоровье!


vb6
person Fred Hamilton    schedule 13.02.2009    source источник


Ответы (5)


Многие люди порекомендуют вам XML. Проблема в том, что XML по-прежнему в моде, некоторые люди используют он везде, даже не об этом.

Как сказал Джефф Этвуд, это сложно тем, кто -программисты для чтения XML и, в частности, для его редактирования. Слишком много правил, таких как экранирование специальных символов и закрытие тегов в правильном порядке. Некоторые эксперты рекомендуют рассматривать XML как двоичный формат. , это вообще не текстовый формат.

Я рекомендую использовать файлы INI при условии, что максимальный размер 32 КБ не является проблемой. Я никогда не достигал этого предела во многих подобных ситуациях в моем собственном VB6. Обычные люди легко редактируют файлы INI, их легко читать и записывать с помощью VB6. Просто используйте отличный вставной код, свободно доступный в Интернете.

  • Я уверен, что класс Джея Риггса предоставленный в его ответе превосходен, потому что он взят из VBAccelerator.
  • Я бы также порекомендовал этот класс, потому что все, что написано Карлом Петерсоном, тоже будет отличным.

Еще пара моментов, о которых стоит подумать:

  • Обдумывали ли вы, в какой каталог поместить файлы?
  • Вы упомянули "дружественный к Unicode" в вопросе. Файлы INI не являются Unicode, но это не имеет значения в упражняться. Если вы не хотите хранить символы, которые не поддерживаются на текущей кодовой странице - например, китайский язык на английском компьютере - необычное требование, которое приведет к в любом случае другие проблемы в программе VB6.
  • Легендарный гуру Windows Раймонд Чен описал преимущества файлов конфигурации XML перед INI. файлы. Многие из них полагаются на то, что файл XML доступен только для чтения. Одно законное преимущество заключается в том, что данные сильно структурированы - иерархии классов или тому подобное. Из вашего описания это не относится.
person MarkJ    schedule 14.02.2009
comment
Я упомянул поддержку Unicode, потому что наше программное обеспечение часто используется клиентами в Азии, и мы иногда видим неожиданные несовместимости, которые обычно сводятся к unicode (функция len () не работает или сообщает неправильное значение и т. Д.). Спасибо за подробный ответ. - person Fred Hamilton; 17.02.2009
comment
Наше программное обеспечение используется клиентами в Китае - файлы INI работают нормально. Однако в VB6 есть некоторые подводные камни. Книга Майкла Каплана по интернационализации в VB6 очень полезна, особенно если вы переводите пользовательский интерфейс. см. i18nwithvb.com - person MarkJ; 19.02.2009
comment
Я вижу, вы живете в США. Если вы используете символы из диапазона Chr $ (128) -Chr $ (255) в жестко заданных строках или где-либо еще, они вызовут проблемы в Азии. Символы из диапазона chr $ (1) - chr $ (127) поддерживаются на всех кодовых страницах. - person MarkJ; 19.02.2009

Рассмотрите возможность использования XML. Он полностью стандартен, многие текстовые редакторы будут правильно выделять / управлять им, каждый язык программирования и язык сценариев на Земле имеет хорошую поддержку для его чтения, и он отлично обрабатывает Unicode.

Для простых пар имя / значение, как вы предлагаете, это вполне читаемо. Но у вас есть дополнительное преимущество: если когда-нибудь вам понадобится что-то более сложное - например, многострочные значения или список отдельных значений - XML предоставляет естественные и простые способы их представления.

P.S. Вот как читать XML в VB6.

person Jason Cohen    schedule 13.02.2009
comment
Статья немного устарела, ссылка Msxml3. - person AnthonyWJones; 14.02.2009
comment
Да, но вопрос конкретно для VB6! Поскольку это явно устаревшая система, я счел уместным дать ссылку на устаревший ответ! - person Jason Cohen; 14.02.2009
comment
Msxml3 вряд ли можно назвать передовой;) Ссылка на статью 2.6, которая на самом деле может привести к разного рода странностям. Ссылка на 3.0 намного чище и присутствует даже на устаревших платформах. - person AnthonyWJones; 14.02.2009
comment
Спасибо за предложение, но я думаю, что XML (как некоторые предположили) - это немного избыточно для того, что мне нужно сделать, и более вероятно, что мои клиенты испортят его, так что я не смог его разобрать. Я увидел иронию в том, что просил современное решение проблемы VB6! :-) - person Fred Hamilton; 17.02.2009

В былые времена помогли этому классу я использую файлы INI с моими программами 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
person Jay Riggs    schedule 13.02.2009
comment
Спасибо. И я не хотел обидеться комментариями «старых дней» - я видел, как люди делали действительно впечатляющие (для меня) вещи в VB6. - person Jay Riggs; 14.02.2009
comment
Я уж точно не обиделась! Я изучу предложенный вами класс, когда вернусь на работу (на этой неделе в отпуске). - person Fred Hamilton; 17.02.2009
comment
В настоящее время ссылка не работает. - person Ju-chan; 27.11.2014

Будет ли XML файл приемлемым: -

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

Его очень легко использовать в VB6, вам не нужно беспокоиться о позиционировании и т. Д.

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

person AnthonyWJones    schedule 13.02.2009
comment
Это обратная сторона, о которой вы упомянули, беспокоит меня (к тому же большинство моих клиентов немного простоваты и сочтут вложенную структуру и форматирование проблемой). Спасибо за предложение - я уже несколько месяцев слушаю подкаст Stack Overflow; Приятно видеть сообщество в действии! - person Fred Hamilton; 17.02.2009
comment
На самом деле он не более вложен, чем типичный .ini, хотя может быть страшнее, он не должен фазировать кого-то, кто хочет что-то настроить, называет AbsMaxVoltage;) - person AnthonyWJones; 17.02.2009

Если мы можем предположить, что ваши сохраненные настройки представляют собой просто набор пар имя / значение без требования двухуровневой иерархии (например, INI «Ключи» в «Разделах»), вы можете просто сохранить их как таковые:

AbsMaxVoltage=17.5
AbsMinVoltage=5.5

Для записи формата сохраняемости это тот случай, когда вы можете рассмотреть FSO, поскольку объем доступа в любом случае невелик. FSO может обрабатывать чтение / запись текстовых файлов Unicode.

Я думаю, что я бы сделал что-то вроде чтения строк и их синтаксического анализа с помощью Split () на «=», указав только 2 части (таким образом, разрешив «=» и внутри значений). Для их загрузки я бы сохранил их в простом экземпляре класса, где у класса есть два свойства (имя и значение), и добавлял каждое из них в коллекцию, используя имя в качестве ключа. При желании сделайте Value свойством по умолчанию.

Возможно, даже реализовать некоторую форму строки текста комментария, используя сгенерированное специальное значение Name с порядковым номером, хранимое как, скажем, Name = "% 1" Value = "comment text" с сгенерированными уникальными именами, чтобы избежать конфликтов ключей коллекции. Пустые строки могут быть сохранены аналогичным образом.

Затем сохранение по мере необходимости означает просто использование For Each в коллекции и использование FSO для записи Name = Value на диск.

Чтобы смоделировать иерархию, вы можете просто использовать такие имена, как:

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

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

Синтаксический анализ дешев, поэтому любому исследованию Коллекции может предшествовать полный повторный анализ (как это делают вызовы INI API). Любое изменение значений в программе может привести к полной перезаписи на диск (как это делают вызовы INI API).

Часть этого можно автоматизировать, просто обернув коллекцию некоторой логикой в другой класс. Результатом может быть такой синтаксис:

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

он же

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

Это перезагрузит коллекцию, затем исследует коллекцию на предмет элемента с ключом «someOtherAppPart.ForegroundColor» и создаст его или установит для него значение «красный», а затем сбросит коллекцию на диск. Или вы можете отказаться от частой перезаписи и использовать разные методы загрузки и сохранения.

Сделайте это настолько простым или необычным, насколько хотите.

В любом случае результатом является текстовый файл, который пользователи могут взломать с помощью Блокнота. Единственная причина для FSO - иметь простой способ чтения / записи текста Unicode. Можно также обойтись с вводом-выводом байтового массива и явными преобразованиями (массив в String) и синтаксическим анализом на уровне строки, если это необходимо, чтобы избежать FSO. Если это так, просто не забывайте о спецификации UTF-16LE.

person Bob    schedule 13.05.2009