Современный, удобный для Unicode файл .ini для хранения данных конфигурации в VB6 - PullRequest
5 голосов
/ 14 февраля 2009

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

AbsMaxVoltage = 17,5

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

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

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

Ура!

Ответы [ 5 ]

5 голосов
/ 15 февраля 2009

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

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

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

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

Несколько других моментов, о которых стоит подумать:

  • Рассматривали ли вы , в какой каталог помещать файлы?
  • Вы упомянули "Unicode-friendly" в вопросе. INI-файлы не являются Unicode , но на практике это не имеет значения. Если вы не хотите хранить символы, которые не поддерживаются в текущей кодовой странице - например, китайский на английском компьютере, - это необычное требование, которое в любом случае вызовет у вас других проблем в программе VB6.
  • Легендарный гуру Windows Раймонд Чен описал преимущества файлов конфигурации XML над файлами INI . Многие из них полагаются на то, что файл XML доступен только для чтения. Единственное законное преимущество заключается в том, что данные имеют высокую степень структурированности - классовые иерархии или тому подобное. Из вашего описания это не относится.
5 голосов
/ 14 февраля 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
5 голосов
/ 14 февраля 2009

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

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

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

1 голос
/ 14 февраля 2009

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

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

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

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

0 голосов
/ 13 мая 2009

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

AbsMaxVoltage=17.5
AbsMinVoltage=5.5

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

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

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

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

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

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

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

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

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

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

ака

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

Это перезагрузило бы Коллекцию, затем проверило бы Коллекцию для Предмета с ключом "someOtherAppPart.ForegroundColor" и создало бы его или установило его Значение "Красным", а затем сбросило бы Коллекцию на диск. Или вы можете отказаться от частого переписывания и использовать разные методы Load и Save.

Сделайте так, чтобы вы были простыми или причудливыми.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...