0

I'm using the community version of C++ builder (10.3) under Windows 10 to develop an app to run on a Samsung Galaxy A40.

One thing I can't seem to get working is saving the contents of TListBox to a file.

Being a newbie to Android, I'm not sure about where this file should appear and what rights I need.

I've tried the following in a function to save the contents of a listbox to a file, but can't seem to open a file to write to. It always returns -1 :

int hFile;      // File Handle
int ByteCt=0;

hFile = FileOpen("Shopping.lst", fmOpenWrite);
if (hFile > 0)
{
    for (int i=0; i<ListBox1->Count; i++)
    {
        ByteCt+=FileWrite(hFile,ListBox1->Items[i].Text.c_str(),
                                ListBox1->Items->Strings[i].Length());
        ByteCt+=FileWrite(hFile,"\n\r",2);
    }
    FileClose(hFile);
}

Is there something basic I've missed, or what ?

Nigel Stevens
  • 147
  • 1
  • 1
  • 9

1 Answers1

1

Use the System::Ioutils::TPath class to determine various system paths that your app can access. See Standard RTL Path Functions across the Supported Target Platforms for details. For example:

String FileName = TPath::Combine(TPath::GetDocumentsPath(), _D("Shopping.lst"));
hFile = FileOpen(FileName, fmOpenWrite);
// or: hFile = FileCreate(FileName);

But FYI, ListBox1->Items is a TStrings, which has its own SaveToFile() method, eg:

ListBox1->Items->SaveToFile(FileName, TEncoding::UTF8);

You are just duplicating what SaveToFile() already does for you. So you don't need to write the strings manually - especially since you are not even writing them correctly to begin with!

String in C++Builder is an alias for UnicodeString, which is a UTF-16 encoded string. Its Length() specifies the number of WideChar (char16_t on Android) elements it contains, but FileWrite() deals in raw bytes only. So you are writing only 1/2 of the bytes of each String since sizeof(WideChar)=2. And also, "\n\r" is not a valid line break to use, either. You would need to use "\r\n" or just "\n", or use the RTL's sLineBreak global constant. But worse, you are trying to write the Strings in their original UTF-16 format but are writing the line breaks in an 8bit ANSI/UTF-8 format. So you end up with a mixed-encoding file, which many softwares won't be able to read correctly.

If you really want to write the Strings manually then it needs to look more like this instead:

int hFile;      // File Handle
int ByteCt=0, BytesWritten;

hFile = FileOpen(FileName, fmOpenWrite);
// or: hFile = FileCreate(FileName);
if (hFile > 0)
{
    for (int i=0; i < ListBox1->Count; i++)
    {
        String s = ListBox1->Items->Strings[i];
        BytesWritten = FileWrite(hFile, s.c_str(), s.Length() * sizeof(WideChar));
        if (BytesWritten < 0) break;
        ByteCt += BytesWritten;
        BytesWritten = FileWrite(hFile, _D("\r\n"), sizeof(WideChar) * 2);
        if (BytesWritten < 0) break;
        ByteCt += BytesWritten;

        /* alternatively:
        UTF8String s = ListBox1->Items->Strings[i];
        BytesWritten = FileWrite(hFile, s.c_str(), s.Length());
        if (BytesWritten < 0) break;
        ByteCt += BytesWritten;
        BytesWritten = FileWrite(hFile, "\r\n", 2);
        if (BytesWritten < 0) break;
        ByteCt += BytesWritten;
        */
    }
    FileClose(hFile);
}

However, rather than using FileWrite() directly, consider using TStreamWriter instead, eg:

int ByteCt=0;

std::unique_ptr<TStreamWriter> File(new TStreamWriter(FileName, TEncoding::UTF8));
// or: auto File = std::make_unique<TStreamWriter>(FileName, TEncoding::UTF8);

for (int i=0; i < ListBox1->Count; i++)
{
    String s = ListBox1->Items->Strings[i];
    File->WriteLine(s);
    ByteCt += (File->Encoding->GetByteCount(s) + File->Encoding->GetByteCount(File->NewLine));
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks, Remy, that's not only pointed me in the right direction, but also pointed out some of the bad habits I've picked up over the years. – Nigel Stevens Jun 01 '20 at 11:19