How to create your own non system Clipboard? - delphi

Is it possible, and if so how would you go about implementing your own clipboard?
By this I mean be able to Copy and Paste anything to and from it just like the Windows clipboard does, but without actually interfering with the system clipboard.
To give a better idea this is what I tried:
uses
ClipBrd;
...
procedure TMainForm.actCopyExecute(Sender: TObject);
var
MyClipboard: TClipboard;
begin
MyClipboard := TClipboard.Create;
try
MyClipboard.AsText := 'Copy this text';
finally
MyClipboard.Free;
end;
end;
That works in that it will copy the string "Copy this text" to the clipboard, but it overwrites whatever was on the Windows clipboard.
The above must just create an instance of the Windows clipboard, not actually creating your own.
Note that the custom clipboard could hold any data not just plain text. It should work just the same as the Windows clipboard, but without interfering with it (losing whatever was on it).
How could this be achieved?
Thanks.

Your question is confusing; you say you want to do it without affecting the system clipboard, but then (from your own comment to your question) you seem to be wanting to implement something like MS Office's Paste Special.
If it's the first, as others have said you can't do that using the TClipboard wrapper; you have to implement your own, and passing information between applications will be very difficult.
If it's the second, you do this by using the Windows API RegisterClipboardFormat to define your own format.
type
TForm1=class(TForm)
YourCustomFormat: Word;
procedure FormCreate(Sender: TObject);
end;
implementation
constructor TForm1.FormCreate(Sender: TObject);
begin
YourCustomFormat := RegisterClipboardFormat('Your Custom Format Name');
end;
To put info into the clipboard in a custom format, you have to use GlobalAlloc and GlobalLock to allocate and lock a global memory block, copy your data into that block, unlock the block using GlobalUnlock, use TClipboard.SetAsHandle to transfer the memory block into the clipboard. You then need to then call GlobalFree to free the memory block.
To retrieve things in your custom format, you do basically the same thing with a couple of steps reversed. You use GlobalAlloc/GlobalLock as before, use TClipboard.GetAsHandle to retrieve the clipboard's content, copy it into a local variable, and then call GlobalFree.
Here's an old example of putting a custom format (in this case, RTF text) into the clipboard - it's from a newsgroup post by Dr. Peter Below of TeamB. (The code and formatting are his from the original post; I've not tested it or even compiled it.) Reversing the process to get it back out should be clear from my instructions on what to change above, and I leave that to you to work out. :)
procedure TForm1.BtnSetRTFClick(Sender: TObject);
Const
testtext: PChar = '{\rtf1\ansi\pard\plain 12{\ul 44444}}';
testtext2: PChar = '{\rtf1\ansi'+
'\deff4\deflang1033{\fonttbl{\f4\froman\fcharset0\fprq2 Times New Roman;}}'
+'\pard\plain 12{\ul 44444}}';
flap: Boolean = False;
Var
MemHandle: THandle;
rtfstring: PChar;
begin
If flap Then
rtfstring := testtext2
Else
rtfstring := testtext;
flap := not flap;
MemHandle := GlobalAlloc( GHND or GMEM_SHARE, StrLen(rtfstring)+1 );
If MemHandle <> 0 Then Begin
try
StrCopy( GlobalLock( MemHandle ), rtfstring );
GlobalUnlock( MemHandle );
With Clipboard Do Begin
Open;
try
AsText := '1244444';
SetAsHandle( CF_RTF, MemHandle );
finally
Close;
end;
End;
Finally
GlobalFree( MemHandle );
End;
End
Else
MessageDlg('Global Alloc failed!',
mtError, [mbOK], 0 );
end;

You should define your own custom Clipboard. It may look something like this:
type
TMyCustomClipboard = class
private
FStream: TMemoryStream;
function GetAsText: string;
procedure SetAsText(const Value: string);
...
public
constructor Create;
destructor Destroy; override;
procedure Clear;
property AsText: string read GetAsText write SetAsText;
procedure AsAnyThing: AnyType read GetAsAnyThing write AsAnyThing;
...
end;
Then you can use FStream as custom clipboard container. You can store (Copy) any data inside that stream and use(Paste) it when you need it. You just need to write some Get/Set methods for your data types.

TClipboard is a class incapsulating system clipboard, so you can't use it to instantiate another copy of a clipboard. You should implement your own class, representing a universal buffer with setters and getters.

You cannot. You can have an internal memory buffer that you move data into and out of, you can call it "copy" and "paste" if you want, but don't put it in the user interface that way, or you'll just confuse your users. There is only one system clipboard, and you cannot put data in it without affecting other programs. If your next thought is to save the clipboard, overwrite with your stuff, then restore the original contents, don't bother.

Related

How to get unit path in runtime with Delphi?

I assume that it is possible to get the complete path of the unit (not just the name) at runtime, since when I generate an error, Delphi already has this stored information:
try
Assert (False, '#');
except
on E: EAssertionFailed from
begin
   ShowMessage (E.Message); // this show me the path
end;
end;
Would anyone know if there is any function that returns me the path of a specific unit, or something similar?
The complete path of the unit as it was on the machine that compiled the project is only possible using Assert. However personally I don't find that information incredibly useful unless you have many units with the same name in different folders or lost control over your source repository and and library paths in effect when compiling.
To get the unit name you can turn on map file or debug information (aka TD32) and do the following:
Use the FileByLevel function from JclDebug - in your case with the default value (0).
The level parameter tells the function how many calls it look up the callstack. If you put that method into a FormCreate of a VCL form for example and pass 1 it will give you Vcl.Forms.pas as the event handler was called from there.
I hesitate to write this answer as it shows a really dirty hack to get the unit name making use of the Assert compiler magic shown above.
Use the following unit:
unit UnitNameHack;
interface
const
cUnitNameSentinel = '$$$sentinel$$$';
var
HackUnitname: string = '';
implementation
var
OrgAssertErrorProc: TAssertErrorProc = nil;
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
if Message = cUnitNameSentinel then begin
HackUnitname:= Filename;
end
else begin
OrgAssertErrorProc(Message, Filename, LineNumber, ErrorAddr);
end;
end;
initialization
OrgAssertErrorProc := AssertErrorProc;
AssertErrorProc := MyAssertErrorProc;
finalization
AssertErrorProc := OrgAssertErrorProc;
end.
Now whenever you need the unit name call
Assert(False, cUnitNameSentinel);
and retrieve the unit name from HackUnitname.
Note that you cannot wrap the Assert call and reading HackUnitName into a function, not even if inlined.
You've said, something similar. I see that it might have been in a different context, but anyway, for objects you can inspect UnitName or UnitScope to get the name of the module where the object instance was declared, without the module path (which is not valuable information anyway).
Based on Uwe Raabe's answer:
// directly after the implementation uses:
var
ThisUnit: string = '<unknown>';
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
ThisUnit := Filename;
end;
procedure InitThisUnit;
var
OrgAssertErrorProc: TAssertErrorProc;
begin
OrgAssertErrorProc := AssertErrorProc;
try
AssertErrorProc := MyAssertErrorProc;
Assert(False);
finally
AssertErrorProc := OrgAssertErrorProc;
end;
end;
// [...]
// call InitThisUnit in the initialization section
initialization
InitThisUnit;
end.
It's still a hack, but a bit more elegant. ;-)

I want to know how to "fill" the TStings defined in CollectLangString?

What is the "engine" under TLang...
TLang is ok in my small project but with larger project It is difficult to manage. I try to figure how it works. I've fund many proc and functions in FMX.Types. I've focus on: CollectLangStart, CollectLangFinish and CollectLangStrings. Calling those function can be compiled but I don't know where and when this TStrings is filled, the TStrings stay empty. The documentation talk about "scene" but it is very limited.
TStyleManager.UpdateScenes must be called between CollectLangStart and copying CollectLangStrings
var
Str: TStrings;
begin
CollectLangStart;
TStyleManager.UpdateScenes;
Str := TStringList.Create;
try
Str.Assign(CollectLangStrings);
Str.SaveToFile(ExtractFilePath(ParamStr(0)) + 'lang.lng');
finally
Str.Free;
CollectLangFinish;
end;
end;

Delphi7 TIniFile Usage

Am trying to create an ini file to save the an application configuration.
the saving part works perfect with the input from the edit boxes 1,2,3 etc here is the ini sample
[1server]
SSHHost=ssh.com
SSHPort=443
Username=user
Password=123
[2server]
SSHHost=ssh.com
SSHPort=443
Username=user
Password=123
[ProxySettings]
Proxy=127.0.0.1
Port=8080
Type=http
how do i make the app read the saved ini setting on launching it and is it possible to hide or encrypt user saved password?
Simply read the Ini file in your main form's FormCreate event.
procedure TForm1.FormCreate(Sender: TObject);
var
Ini: TIniFile;
begin
Ini := TIniFile.Create(YourIniFileName);
try
// The final parameter to all of the `ReadXx` functions is a default
// value to use if the value doesn't exist.
Edit1.Text := Ini.ReadString('1server', 'SSHHost', 'No host found');
Edit2.Text := Ini.ReadString('1server', 'SSHPort', 'No port found');
// Repeat, using ReadString, ReadInteger, ReadBoolean, etc.
finally
Ini.Free;
end;
end;
One word of caution: TIniFile has known issues with writing to network locations, so if there's any chance of that you should use TMemIniFile instead. TIniFile wraps the WinAPI INI support functions (ReadPrivateProfileString and others), while TMemIniFile is written totally in Delphi code and doesn't suffer from the same problems. They're syntax-compatible and in the same unit, so it's a simple matter of changing TIniFile to TMemIniFile in your variable declaration and the line that creates the Ini:
var
Ini: TMemIniFile;
begin
Ini := TMemIniFile.Create(YourIniFileName);
...
end;
As far as encrypting the password, you can use any encryption algorithm you want, as long as the encrypted value can be converted to a textual representation. (Ini files don't handle binary values.) The choice of algorithm is up to you based on the level of security you're attempting to achieve.

Create and/or Write to a file

I feel like this should be easy, but google is totally failing me at the moment. I want to open a file, or create it if it doesn't exist, and write to it.
The following
AssignFile(logFile, 'Test.txt');
Append(logFile);
throws an error on the second line when the file doesn't exist yet, which I assume is expected. But I'm really failing at finding out how to a) test if the file exists and b) create it when needed.
FYI, working in Delphi XE.
You can use the FileExists function and then use Append if exist or Rewrite if not.
AssignFile(logFile, 'Test.txt');
if FileExists('test.txt') then
Append(logFile)
else
Rewrite(logFile);
//do your stuff
CloseFile(logFile);
Any solution that uses FileExists to choose how to open the file has a race condition. If the file's existence changes between the time you test it and the time you attempt to open the file, your program will fail. Delphi doesn't provide any way to solve that problem with its native file I/O routines.
If your Delphi version is new enough to offer it, you can use the TFile.Open with the fmOpenOrCreate open mode, which does exactly what you want; it returns a TFileStream.
Otherwise, you can use the Windows API function CreateFile to open your file instead. Set the dwCreationDisposition parameter to OPEN_ALWAYS, which tells it to create the file if it doesn't already exist.
You should be using TFileStream instead. Here's a sample that will create a file if it doesn't exist, or write to it if it does:
var
FS: TFileStream;
sOut: string;
i: Integer;
Flags: Word;
begin
Flags := fmOpenReadWrite;
if not FileExists('D:\Temp\Junkfile.txt') then
Flags := Flags or fmCreate;
FS := TFileStream.Create('D:\Temp\Junkfile.txt', Flags);
try
FS.Position := FS.Size; // Will be 0 if file created, end of text if not
sOut := 'This is test line %d'#13#10;
for i := 1 to 10 do
begin
sOut := Format(sOut, [i]);
FS.Write(sOut[1], Length(sOut) * SizeOf(Char));
end;
finally
FS.Free;
end;
end;
If you are just doing something simple, the IOUtils Unit is a lot easier. It has a lot of utilities for writing to files.
e.g.
procedure WriteAllText(const Path: string; const Contents: string);
overload; static;
Creates a new file, writes the specified string to the file, and then
closes the file. If the target file already exists, it is overwritten.
You can also use the load/save feature in a TStringList to solve your problem.
This might be a bad solution, because the whole file will be loaded into memory, modified in memory and then saved to back to disk. (As opposed to your solution where you just write directly to the file). It's obviously a bad solution for multiuser situations.
But this approach is OK for smaller files, and it is easy to work with and easy understand.
const
FileName = 'test.txt';
var
strList: TStringList;
begin
strList := TStringList.Create;
try
if FileExists(FileName) then
strList.LoadFromFile(FileName);
strList.Add('My new line');
strList.SaveToFile(FileName);
finally
strList.Free;
end;
end;

Fast read/write from file in delphi

I am loading a file into a array in binary form this seems to take a while
is there a better faster more efficent way to do this.
i am using a similar method for writing back to the file.
procedure openfile(fname:string);
var
myfile: file;
filesizevalue,i:integer;
begin
assignfile(myfile,fname);
filesizevalue:=GetFileSize(fname); //my method
SetLength(dataarray, filesizevalue);
i:=0;
Reset(myFile, 1);
while not Eof(myFile) do
begin
BlockRead(myfile,dataarray[i], 1);
i:=i+1;
end;
CloseFile(myfile);
end;
If your really want to read a binary file fast, let windows worry about buffering ;-) by using Memory Mapped Files. Using this you can simple map a file to a memory location an read like it's an array.
Your function would become:
procedure openfile(fname:string);
var
InputFile: TMappedFile;
begin
InputFile := TMappedFile.Create;
try
InputFile.MapFile(fname);
SetLength(dataarray, InputFile.Size);
Move(PByteArray(InputFile.Content)[0], Result[0], InputFile.Size);
finally
InputFile.Free;
end;
end;
But I would suggest not using the global variable dataarray, but either pass it as a var in the parameter, or use a function which returns the resulting array.
procedure ReadBytesFromFile(const AFileName : String; var ADestination : TByteArray);
var
InputFile : TMappedFile;
begin
InputFile := TMappedFile.Create;
try
InputFile.MapFile(AFileName);
SetLength(ADestination, InputFile.Size);
Move(PByteArray(InputFile.Content)[0], ADestination[0], InputFile.Size);
finally
InputFile.Free;
end;
end;
The TMappedFile is from my article Fast reading of files using Memory Mapping, this article also contains an example of how to use it for more "advanced" binary files.
You generally shouldn't read files byte for byte. Use BlockRead with a larger value (512 or 1024 often are best) and use its return value to find out how many bytes were read.
If the size isn't too large (and your use of SetLength seems to support this), you can also use one BlockRead call reading the complete file at once. So, modifying your approach, this would be:
AssignFile(myfile,fname);
filesizevalue := GetFileSize(fname);
Reset(myFile, 1);
SetLength(dataarray, filesizevalue);
BlockRead(myFile, dataarray[0], filesizevalue);
CloseFile(myfile);
Perhaps you could also change the procedure to a boolean function named OpenAndReadFile and return false if the file couldn't be opened or read.
It depends on the file format. If it consists of several identical records, you can decide to create a file of that record type.
For example:
type
TMyRecord = record
fieldA: integer;
..
end;
TMyFile = file of TMyRecord;
const
cBufLen = 100 * sizeof(TMyRecord);
var
file: TMyFile;
i : Integer;
begin
AssignFile(file, filename);
Reset(file);
i := 0;
try
while not Eof(file) do begin
BlockRead(file, dataarray[i], cBufLen);
Inc(i, cBufLen);
end;
finally
CloseFile(file);
end;
end;
If it's a long enough file that reading it this way takes a noticeable amount of time, I'd use a stream instead. The block read will be a lot faster, and there's no loops to worry about. Something like this:
procedure openfile(fname:string);
var
myfile: TFileStream;
filesizevalue:integer;
begin
filesizevalue:=GetFileSize(fname); //my method
SetLength(dataarray, filesizevalue);
myFile := TFileStream.Create(fname);
try
myFile.seek(0, soFromBeginning);
myFile.ReadBuffer(dataarray[0], filesizevalue);
finally
myFile.free;
end;
end;
It appears from your code that your record size is 1 byte long. If not, then change the read line to:
myFile.ReadBuffer(dataarray[0], filesizevalue * SIZE);
or something similar.
Look for a buffered TStream descendant. It will make your code a lot faster as the disk read is done fast, but you can loop through the buffer easily. There are various about, or you can write your own.
If you're feeling very bitheaded, you can bypass Win32 altogether and call the NT Native API function ZwOpenFile() which in my informal testing does shave a tiny bit off. Otherwise, I'd use Davy's Memory Mapped File solution above.

Resources