I'm trying to load the text from a text file to a synmemo by using
procedure TForm1.btn7Click(Sender: TObject);
begin
if dlgOpen1.Execute then
synm1.Lines.LoadFromFile(dlgOpen1.Files.Text);
end;
But as soon as I select a file i get this error:
Cannot open file "C:\Users\adria\Desktop\New Text Document.txt
". The filename, directory name, or volume label syntax is incorrect.
Component: https://github.com/TurboPack/SynEdit
The problem is in the use of the Files property of the dialog to access the selected filename.
The Files property is a list of strings intended for use when you have enabled multiple selection in the dialog and need to process more than one filename selected by the user.
The Text property of string list returns a formatted representation of all entries in that list with each entry delimited by an EOL character (or characters).
You might expect that where only a single file is involved that this Textproperty would contain only the name of that file. But in fact it also includes an EOL character. i.e. the filename you are trying to open by using this technique is actually:
'C:\Users\adria\Desktop\New Text Document.txt'#13#10
There was actually a clue to this in the way that the message was being displayed, with the closing quotes on a separate line as a result of that EOL.
The correct way to work with the selected filename depends on whether you are supporting multiple selection or single.
In the case of single selection (your case here) the simplest approach is to use the Filename property of the dialog:
if dlgOpen1.Execute then
synm1.Lines.LoadFromFile(dlgOpen1.Filename);
For multiple selection you would use the Files property, but access each filename by index in the list:
if dlgOpen1.Execute then
for i := 0 to Pred(dlgOpen1.Files.Count) do
begin
// Do something with each dlgOpen1.Files[i] ...
end;
Related
Embarcadero RAD Studio VCL has the TClipboard.HasFormat Method, with a usage e.g. Clipboard.HasFormat(CF_TEXT) or Clipboard.HasFormat(CF_BITMAP) etc..
But I did not find any supported CF_RTF or CF_RICHTEXT format-descriptor which indicates a rich-text format in the clipboard.
So I created some formatted text in Microsoft WordPad and copied it to the clipboard. Then I used a clipboard-spy program to inspect the formats on the clipboard:
This lists 3 RichText formats with the format-descriptors C078, C16B and C1A5.
Are these format-descriptors universal or dependent from the individual system or from the current situation? I.e., can I generally use Clipboard.HasFormat($C078) to detect any RichText format on the clipboard? Or is there another method?
Can I generally use Clipboard.HasFormat($C078) to detect any
RichText format on the clipboard?
No, You need to register the RTF clipboard format via RegisterClipboardFormat function. The returned value is generated by the system and may vary.
Registers a new clipboard format. This format can then be used as a
valid clipboard format.
If a registered format with the specified name already exists, a new
format is not registered and the return value identifies the existing
format. This enables more than one application to copy and paste data
using the same registered clipboard format.
var
CF_RTF: UINT;
...
initialization
CF_RTF := RegisterClipboardFormat('Rich Text Format');
Then check for:
if Clipboard.HasFormat(CF_RTF) then ...
{ or // if Windows.IsClipboardFormatAvailable(CF_RTF) then ... }
Edit: After reading the documentation: How to Use Rich Edit Clipboard Operations
The constant CF_RTF is already declared in RichEdit unit as:
CF_RTF = 'Rich Text Format';
CF_RTFNOOBJS = 'Rich Text Format Without Objects';
CF_RETEXTOBJ = 'RichEdit Text and Objects';
So it might be a better idea to use other naming for the returned value of RegisterClipboardFormat. e.g.
uses RichEdit;
...
var
CF_RICHTEXT: UINT;
...
initialization
CF_RICHTEXT := RegisterClipboardFormat(RichEdit.CF_RTF);
And:
if Clipboard.HasFormat(CF_RICHTEXT) then ...
Note: There are already a few reserved system clipboard formats such as CF_TEXT (=1), CF_BITMAP(=2) etc ... but the "CF_RTF" or "CF_RICHTEXT" is not one of them. it is a custom format used by the RICHEDIT common control, and registered via RegisterClipboardFormat as already mentioned.
When you create a Delphi project and add a combobox and set ComboBox1.Items.Add('Zebra & Zulu') it shows "Zebra & Zulu" when you dropdown the list. Perfect.
When you create a Firemonkey project and add a comboxbox or comboedit and set ComboBox1.Items.Add('Zebra & Zulu') or ComboEdit1.Items.Add('Zebra & Zulu') it shows "Zebra Zulu" (no ampersand shows) when you dropdown the list. However, when you select it using the comboedit the text field part shows "Zebra & Zulu". Just weird.
Now there is a work around (sort of) but to me it is questionable. Add a second ampersand and the dropdown list shows the ampersand. However, adding the second ampersand shows up on the comboedit text field part. Bad.
My question is can you force these combo controls dropdown list to show the ampersand? And why is the ampersand missing in the first place?
This seems to be inbuilt behaviour that is likely leftover from the VCL's accelerator key handling. There does not seem to be a way to modify this behaviour with styles or options :
procedure TTextControl.DoChanged;
var
TextStr: string;
begin
if Assigned(FITextSettings) then
FITextSettings.TextSettings.BeginUpdate;
try
if Assigned(FITextSettings) then
FITextSettings.TextSettings.Assign(ResultingTextSettings);
TextStr := DelAmp(Text); // **! Here deleting ampersands unconditionally
if Assigned(FTextObject) then
begin
UpdateTextObject(FTextObject, TextStr);
// ... etc - method continues
One workaround is to use the unicode full-width ampersand :
ComboBox1.Items.Add('Zebra & Zulu');
Obviously not appealing for a number of reasons.
From the code above, this naturally affects all FMX TTextControls - even a TLabel, for example, will not display an ampersand when assigned :
Label1.Text := 'Zebra & Zulu';
Even in a VCL application, for interest, this:
Label1.Caption := 'Zebra & Zulu';
will render as
Zebra _Zulu
Although a VCL TComboBox will correctly render an item with a single ampersand...
This is an open QC, albeit one that does not appear in any hurry of being addressed :
http://qc.embarcadero.com/wc/qcmain.aspx?d=122564
I am able to open a CHM file by passing a ShortInteger and casting it as a Word for the dwData parameter. I.E.
Unit Help; //this is where the Id's are set with their description
Interface
Const
Address_File = 35; //delphi identifies Address_File as a shortint
etc..
Call get help pass my ID
GetHelp(Address_File); //call get help pass my ID to open to the Address_File topic
GetHelp procedure
procedure GetHelp(HelpID : Word);
begin
Application.HelpFile := ProgramPath + 'help.chm';
HtmlHelpW(0, PWideChar(Application.HelpFile),HH_HELP_CONTEXT , HelpID);
end;
HtmlHelpW function
function HtmlHelpW(hwndCaller : HWND; pszFile: PWideChar; uCommand : Integer;
dwData : DWORD) : HWND; stdcall; external 'hhctrl.ocx' name 'HtmlHelpW';
As I pass different ShortIntegers I am able to initialize the help file at different sections.
However I can't figure out how the values are mapped. There are some sections in the chm file that I want to be able to map to but the short Integer or context ID associated with them is not documented in the program or is not mapped.
Free Pascal comes with a chmls.exe util that has a command that tries to recover the alias (context) data:
chmls, a CHM utility. (c) 2010 Free Pascal core.
Usage: chmls [switches] [command] [command specific parameters]
Switches :
-h, --help : this screen
-p, --no-page : do not page list output
-n,--name-only : only show "name" column in list output
Where command is one of the following or if omitted, equal to LIST.
list <filename> [section number]
Shows contents of the archive's directory
extract <chm filename> <filename to extract> [saveasname]
Extracts file "filename to get" from archive "filename",
and, if specified, saves it to [saveasname]
extractall <chm filename> [directory]
Extracts all files from archive "filename" to directory
"directory"
unblockchm <filespec1> [filespec2] ..
Mass unblocks (XPsp2+) the relevant CHMs. Multiple files
and wildcards allowed
extractalias <chmfilename> [basefilename] [symbolprefix]
Extracts context info from file "chmfilename"
to a "basefilename".h and "basefilename".ali,
using symbols "symbolprefix"contextnr
extracttoc <chmfilename> [filename]
Extracts the toc (mainly to check binary TOC)
extractindex <chmfilename> [filename]
Extracts the index (mainly to check binary index)
This might be a start, since at least you'll know which pages are exported using an ID, and maybe the URL names will give some information.
The util is in recent releases (make sure you get 2.6.0) and also available in Free Pascal source, which should be convertable to Delphi with relatively minor effort.
Basically the chmls tool was created out of various test codebases. The testprograms decompiled and printed contents of different CHM sections and were used while creating the helpfile compiler, chmcmd, which is also part of FPC.
In Delphi, calling a help file is rather easy. In any VCL Forms application, you can set the HelpContext property of almost any control to a unique Context ID, which corresponds to a particular topic in the Help File. The Help File was compiled with these mappings, but when you decompile it, these mappings are no longer there. You must have access to the original help file project in order to know these ID's.
Set HelpContext of controls to the corresponding Context ID in the Help File
Set HelpType of controls to htContext to use the HelpContext ID
Assign Application.HelpFile to the appropriate location of the CHM file
When pressing F1 anywhere in your application, the help file will open based on the Help Context ID on the control, or its parent control
If you don't have the original project, and you don't want to re-create it, then you would have a long task of iterating through the Context ID's of your help file. Try to call the help file starting from 0 through 1,000 or possibly 50,000, depending on the size of it.
A practice I implement is a set of constants in a designated unit called HelpConstants.pas which is shared across our common application base. Each constant name uniquely and briefly describes the topic which it represents. Upon starting the application, I dynamically assign these Context ID's to their corresponding controls (usually forms) and VCL takes care of the rest.
I got the utility Marco suggested from
https://github.com/alrieckert/freepascal_arm/blob/master/packages/chm/bin/i386-win32/chmls.exe
(download by selecting View Raw).
I was able to extract all the context tags from the .chm help file and add the one I was interested in to my C++ Builder program by calling Application->HelpJump().
HTH
I read an Excel 2003 file with a text editor to see some markup language.
When I open the file in Excel it displays incorrect characters. On inspection of the file I see that the encoding is Windows 1252 or some such. If I manually replace this with UTF-8, my file opens fine. Ok, so far so good, I can correct the thing manually.
Now the trick is that this file is generated automatically, that I need to process it automatically (no human interaction) with limited tools on my desktop (no perl or other scripting language).
Is there any simple way to open this XL file in VBA with the correct encoding (and ignore the encoding specified in the file)?
Note, Workbook.ReloadAs does not function for me, it bails out on error (and requires manual action as the file is already open).
Or is the only way to correct the file to go through some hoops? Either: text in, check line for encoding string, replace if required, write each line to new file...; or export to csv, then import from csv again with specific encoding, save as xls?
Any hints appreciated.
EDIT:
ADODB did not work for me (XL says user defined type, not defined).
I solved my problem with a workaround:
name2 = Replace(name, ".xls", ".txt")
Set wb = Workbooks.Open(name, True, True) ' open read-only
Set ws = wb.Worksheets(1)
ws.SaveAs FileName:=name2, FileFormat:=xlCSV
wb.Close False ' close workbook without saving changes
Set wb = Nothing ' free memory
Workbooks.OpenText FileName:=name2, _
Origin:=65001, _
DataType:=xlDelimited, _
Comma:=True
Well I think you can do it from another workbook. Add a reference to AcitiveX Data Objects, then add this sub:
Sub Encode(ByVal sPath$, Optional SetChar$ = "UTF-8")
Dim stream As ADODB.stream
Set stream = New ADODB.stream
With stream
.Open
.LoadFromFile sPath ' Loads a File
.Charset = SetChar ' sets stream encoding (UTF-8)
.SaveToFile sPath, adSaveCreateOverWrite
.Close
End With
Set stream = Nothing
Workbooks.Open sPath
End Sub
Then call this sub with the path to file with the off encoding.
I am an engineer and not a software programmer, so please excuse my ignorance.
I have written a Delphi(7SE) program to read “real” datatype from a USB port connected to two digital thermometers.
I have completed this much of the program.
What I have not completed as yet is explained by the following:
I wish to save this “real” data to a Binary File(s). A text file would be fine as well, but i'm concerned about having a big data file.
I also wish to read this data back from the Binary/Text File to display the data using my Delphi application.
I don’t think this would be too difficult. I currently save my data in .CSV format.
The twist here is that the binary file should contain data from different sessions initiated by the user of my application.
So when I click on say, a button called “ historical” data, a new window/form would pop up that would show different session times that I had started & stopped from earlier times. Then a session would be selected and data then retrieved for displaying.
Can this be done in one binary files or would you have to use 2 files: one for the “real” data and another which indexes the different session times?
My requirement for this way of saving binary data is that I would not have to keep typing in filenames and therefore keeping track of many data files.
For example a thermo.hst(historical data) and a thermo.idx (index file) file would contain all the information such as actual temp data, time of read data, session start & end times etc.
Any useful pointers and hopefully code with as much detail would be greatly appreciated.
I hope this sample code isn't too late to be helpful.
(I've added this as another answer from me so that I can cleanly list the code. If this or my previous post answers your question, please click the answer icon so I get reputation points!)
Below is some rough code that shows how to read the sections in an ini file and find the largest filename. I confirmed it compiles and seems to return valid values, but you'll need confirm it does what you need. It's more to show you the idea...
Note that if your data filenames have an extension, you'll have add code to remove the extension in my sample code using something like: FileName := ChangeFileExt(Filename, '').
// Call with an open inifile. Returns the name of the next filename, or '' if trouble
Function GetNextFileName( const IniFile: TInifile):String;
const
BASE_FILENAME = 'File.'; // sections in the ini file will be [File.1], [File.2], ... [File.100], etc.
var
Sections: TStringList;
NumericPartAsString: String;
NumericPartAsInteger: Integer;
ListIndex: Integer;
LargestFileNumberSeenSoFar: Integer;
begin
Result := '';
Sections := TStringList.Create;
IniFile.ReadSections(Sections); // fills StringList with the names of all sections in the ini file
if( Sections.Count = 0) then
Result := BASE_FILENAME + '1'
else
begin // find largest extension
LargestFileNumberSeenSoFar := -1;
ListIndex := 0;
while ListIndex <= (Sections.Count - 1) do // for every string (which is also a filename) in the string list:
begin
NumericPartAsString := StringReplace(Sections.Strings[ListIndex], BASE_FILENAME, '', []); // remove base filename
if (NumericPartAsString <> '') then
begin
NumericPartAsInteger := StrToIntDef(NumericPartAsString, -1);
if (NumericPartAsInteger > LargestFileNumberSeenSoFar) then
LargestFileNumberSeenSoFar := NumericPartAsInteger;
end;
inc(ListIndex);
end;
if (LargestFileNumberSeenSoFar > -1) then
Result := BASE_FILENAME + IntToStr(LargestFileNumberSeenSoFar + 1);
end;
Sections.Free;
end; { GetNextFileName }
procedure TForm1.Button1Click(Sender: TObject);
var
IniFile: TInifile;
NewFileName: String;
begin
IniFile := TInifile.Create('c:\junk\ini.ini');
NewFileName := GetNextFileName(Inifile);
if (NewFileName = '') then
ShowMessage('Error finding new filename')
else
ShowMessage('New filename is ' + NewFileName);
IniFile.Free;
end;
By using a database, you've in part just renamed part of the problem from "typing in file names and keeping track of many data files" to "typing in data set name and keeping track of many data sets."
In both cases, as an example, either the user or the program has to create a new file/data set name, and choose from a list of files/data sets to open later. And in both cases you have to make a call to a function named something like "DeleteDataSet".
You might re-consider whether you really need a database (with associated learning curve for API, how to define the data structure, update it in the field when something changes, browse the data in a viewer, access the data programatically, proprietary format, database maintenance, repair tools, installation, etc.) I'm sure that you could learn all these, and they might be valuable in future projects. But, maybe a simpler approach would be more appropriate and adequate for this one-time project by a non-software engineer.
If you're willing to have a proliferation of many unique, standalone data files on one folder, I'd encourage you to stick with what's working: use one CSV file per data set. (Have you run into speed or size issues with CSV files containing a single data set thus far? That would be an enormous amount of data!) One nice thing about CSV files is that you can just pop them into an editor to view or edit...
And, then, add a second file that contains filenames, and other descriptive information. This file would be a simple TIniFile:
[My Name one]
Date=06 June 2010
StartTime=12:30pm
StopTime=3:15pm
FileName=Data1.csv
[My Name two]
...
The tools available in Delphi for TIniFile will let you easily manage this list, including ReadSections into a string list that you can just assign to a combo box for the user to select a data set. (See example below) And, like the CSV files, you can just edit the .ini file in any text editor.
You'll need to build a UI to allow a user to delete a dataset (section in the ini file and associated .csv file). To give you an idea how the ini file would be used, here's the pseudo-code for deleting a data set:
(In IDE Object Inspector, set ComboBox.Style := csDropDownList to prevent user from typing in a name that doesn't exist.)
Load a combo-box that shows available data sets.
1. ComboBox.Items := IniFile.ReadSections;
In the combo-box's OnSelect event handler:
2. DeleteFile(IniFile.ReadString(CombBox.Text, 'FileName', ''));
3. IniFile.EraseSection(ComboBox.Text); // remove the section from the inifile
Heck, that's not a lot of code, even after you add a bit of protection and error checking!
Maybe the above solution will be voted down by others here as trying to put a round peg in a square hole or re-inventing the wheel. And I might agree with them. There are good arguments against this approach, including proliferation of many files. But, if it was me, I'd at least consider this approach as keeping-it-simple and not requiring anything new but that you learn the TIniFile object, which is quite powerful.
The data can be interleaved. Just start every block (a set of history) with a header that identifies the block and contains its length. When reading you can then easily separate that.
You can hold an additional index file next to this for fast access if you require, but if this is the case, I would start studying some embedded database. (TDBF, sqlite or embedded firebird).
I would also head in the database direction if I expected that my querying would get more complicated in the future.
If it is all about logging, the data doesn't get gigantic and the performance of the view is fine, I would keep the binary file and avoid the hassle of having users to install and maintain a databsae solution. (TDBF is maybe an exception to that, since it is completely statically linked)