I am trying to save the contents of a TRichMemo to TMemoryStream, and then be able to load the formatted data back from the stream into the rich memo.
The problem is LoadRichText is failing for some reason. I know the data is been saved to my stream because I can actually save it fo file as rtf and view it externally.
This is basically what I have:
var
FMyStream: TMemoryStream;
To save:
RichMemo1.SaveRichText(FMyStream);
To load:
FMyStream.Seek(0, soBeginning);
if not RichMemo1.LoadRichText(FMyStream) then
raise Exception.Create('Failed to load data from stream.');
As I said the data is saved to stream correctly, but trying to load into the rich memo is hitting my exception everytime.
What could be the problem?
The code for the LoadRichText function is:
function TCustomRichMemo.LoadRichText(Source: TStream): Boolean;
begin
if Assigned(Source) and HandleAllocated then begin
Result := TWSCustomRichMemoClass(WidgetSetClass).LoadRichText(Self, Source);
if not Result and Assigned(RTFLoadStream) then begin
Self.Lines.BeginUpdate;
Self.Lines.Clear;
Result:=RTFLoadStream(Self, Source);
Self.Lines.EndUpdate;
end;
end else
Result := false;
end;
and SaveRichText code:
function TCustomRichMemo.SaveRichText(Dest: TStream): Boolean;
begin
if Assigned(Dest) and HandleAllocated then begin
Result := TWSCustomRichMemoClass(WidgetSetClass).SaveRichText(Self, Dest);
if not Result and Assigned(RTFSaveStream) then
Result:=RTFSaveStream(Self, Dest);
end else
Result := false;
end;
Thanks.
Ok, I found the solution to my problem.
At first I created a simple test project and LoadRichText and SaveRichText worked, which meant the problem was within my code somewhere...
My stream is declared in a class in a separate unit. In another form I have my rich memo control, when the form is closed the data is saved to the stream, that part I knew worked because I could save it to file and view it externally.
The problem was when I was creating the form that contains my rich memo, I was calling LoadRichText from the FormCreate event. So I moved it into FormActivate and now it works without error.
Related
I have a Delphi app which prints HTML to PDF by opening the document in IE11 in the background and using the default PDF printer to print to PDF. This process works fine.
The issue is the following:
(Example I want to print 5 HTML documents to PDF.)
On the first run, it processes the first document fine, but then skips the rest with the following errors:
On Win Server 2016:
EOleSysErrorOLE A system shutdown has already been scheduled.
On Windows 10:
EOleSysErrorOLE error 8150002E
Then I wait until "iexplore.exe" closes, which takes a few sec.
From then, it processes all documents just fine regardless the number of the documents.
If I do not use the app for a long time (approx a day), it does the same as above.
It skips on the first run, then waits a few seconds and then us fine.
I tried to use OleVariant and IWebBrowser2, but both have the same outcome.
I close the Object with .Quit. (see in code below). I also tried Unassigned, Free, setting the Object to Null before creating a new object. None of them worked. Same outcome.
Here are a few thing which I tried as a workaround:
If I do not use .Quit, it works fine, but obviously won't close any iexplore.exe.
Also, if I open an IE window (GUI) and minimize it, the HTML-PDF process works fine.
I also tried to call to create a background IE object on TMainForm.FormCreate() when the app starts, and it works as well.
When it gets to the HTML-PDF process, it creates a new IE background object (additional "iexplore.exe") and closes it by leaving the one created on FormCreate().
I would like to figure out why it just cannot create and close an object fast enough on the first run (without having an IE opened or without using .Quit).
Here is the code:
Note: The program also writes some stuff to the registry, but I cut some lines for simplicity (I also might cut a few end here and there. Note that the function works fine apart than the issue above).
function THTMLMergeDocument.FilePrint: boolean;
var
BrowserObject: OleVariant;
ie : IWebBrowser2;
vaIn, vaOut: OleVariant;
OldHeader, OldFooter, OldPrinterName: String;
OldOrientation: Integer;
Registry : TRegistry;
ST, TOutVal: TDateTime;
sUrl : string;
Flag, TargetFrameName, PostData, Headers : OleVariant;
begin
result := false;
try
if fPrinterName = VTPrint.GetDefaultPrinter then
begin
try
//Tried OleVariant
{
BrowserObject := Unassigned;
BrowserObject := CreateOleObject('InternetExplorer.Application');
BrowserObject.Silent := true;
BrowserObject.Visible := false;
BrowserObject.Navigate('file:\\'+DocumentFileName);
BrowserObject.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER);
while BrowserObject.Busy or BrowserObject.ReadyState <> READYSTATE_COMPLETE do
begin
Application.ProcessMessages;
end;
}
//Tried IWebBrowser2
ie := CoInternetExplorer.Create;
sUrl := 'file:\\'+DocumentFileName;
ie.Navigate(sUrl, Flag, TargetFrameName, PostData, Headers);
ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER,vaIn,vaOut);
while ie.ReadyState <> READYSTATE_COMPLETE do
begin
Application.ProcessMessages;
end;
if (PDFOutput) then
begin
ST := Now;
TOutVal := EncodeTime(0,DocumentServerOptions.PDFConverterTimeOutInterval,0,0);
while not PDF.Completed and (Now-ST<TOutVal) do
Application.ProcessMessages;
if PDF.Completed then
result := true
else
WinWordLogProc('ERROR: No response received from PDF converter');
end
else
begin
result := true;
end;
ie.Quit; //close IWebBrowser2 object
BrowserObject.Quit; //close OleVariant object
except on E: Exception do
WinWordLogProc( 'Error class: ' + E.ClassName + #13 + E.Message);
end;
end
else
begin
WinWordLogProc('Error setting default printer to '+fPrinterName);
end;
finally
VTPrint.SetDefaultPrinter(OldPrinterName);
end;
`
I suspect that it has something do do with the IE object handling, but I'm not sure, hence asking for help here.
The browser's Navigate() method is asynchronous. You should wait for the document to finish loading before you then call ExecWB() to print the document, not after calling it.
So this is my setup for the 'intent':
Intent cameraACTION_VIDEO_CAPTURE = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
tempUri = accessLocalStorage.getThisAppsStorageUriPath();
//Crashed for tempUri = "/data/user/0/hardy.android.go/app_files/test.mp4"
//Crashed for tempUri = "/data/user/0/hardy.android.go/app_files/"
cameraACTION_VIDEO_CAPTURE.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
cameraACTION_VIDEO_CAPTURE.setFlags(cameraACTION_VIDEO_CAPTURE.getFlags() | Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivityForResult(cameraACTION_VIDEO_CAPTURE,
Integer.parseInt( DataModel.SETVIDEORECORDING.toString()));
Video intent starts as expected and crashes once I finish recording - it doesn't even make it to 'onActivityResult'. Error is:
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
Don't know why there is a Bitmap floating around in there?
Anywayz, so in an attempt to try and pinpoint the issue, I comment out the following and go again:
cameraACTION_VIDEO_CAPTURE.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
and it works :( - video is stored here:
/storage/emulated/0/DCIM/Camera/VID_20181004_213440310_HDR.mp4
Ok, I have a partial answer. In terms of the application not crashing, I made progress by using FileProvider to generate the Uri.
tempUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
new File(accessLocalStorage.getThisAppsStorageUriPath().getPath())
);
However, the video File saved at the path in the Uri was of size/length 0 () and I didn't have time to work through this and so, this story ends here :( - hope this is of some help for others!
I am using TWebBrowser in DesignMode (Doc.DesignMode := 'On') to compose a HTML document. There is no document (HTML file on disk) loaded in TWebBrowser. I create the document from zero directly in TWebBrowser. The html code will be extracted from TWebBrowser and saved as c:/MyProjects/SomeHtmlName.html.
The problem is that it won't show images I insert if they have relative path.
More exactly, if I paste this code in the WebBrowser it will instantly display the image:
<IMG src="file:///c:/MyProjects/resources/R.PNG">
However, if I enter:
<IMG border=0 src="resources\R.PNG">
<IMG border=0 src="resources/R.PNG"> <---- preferred so it will work on Linux
it will display an image placeholder instead of the actual image.
I need relative paths so the web site will still work if I change the root path OR if I upload it on FTP.
procedure TForm1.Button1Click(Sender: TObject);
begin
LoadDummyPage;
SetHtmlCode('<img src="resources/test_img.PNG">');
Memo1.Text:= GetHtmlCode;
end;
function TForm1.LoadDummyPage: Boolean;
const FileName: string= 'c:\MyProject\_ONLINE WEB SITE\dummy.html';
begin
if not Assigned(wbBrowser.Document)
then wbBrowser.Navigate('about:blank');
Result := FileExists(FileName);
if Result
then wbBrowser.Navigate('file://' + FileName)
else Caption:= 'file not found';
end;
procedure TForm1.SetHtmlCode(const HTMLCode: string);
var
Doc: Variant;
begin
if not Assigned(wbBrowser.Document)
then wbBrowser.Navigate('about:blank');
Doc := wbBrowser.Document;
Doc.Write(HTMLCode);
Doc.Close;
Doc.DesignMode := 'On';
WHILE wbBrowser.ReadyState < READYSTATE_INTERACTIVE
DO Application.ProcessMessages;
Doc.body.style.fontFamily := 'Arial';
Doc.Close;
end;
function TForm1.GetHtmlCode: string; { Get the HTML code from the browser }
var
Doc: IHTMLDocument2;
BodyElement: IHTMLElement;
begin
if Assigned(wbBrowser.Document) and (wbBrowser.Document.QueryInterface(IHTMLDocument2, Doc) = S_OK) then
begin
BodyElement := Doc.body;
if Assigned(BodyElement) then
Result := BodyElement.innerHTML;
end;
if Result > ''
then Result := StringReplace(Result, '="about:', '="', [rfReplaceAll, rfIgnoreCase]); { Fix the 'How stop TWebBrowser from adding 'file:///' in front of my links' bug }
end;
You need to pre-load a valid HTML "template" string/stream including the BASE tag where you set the desired path (with trailing slash) e.g. "file:///c:/MyProjects/".
And switch to edit mode, where your images SRC should be relative e.g. "resources/R.PNG". Your final extracted HTML ater editing should be the body.innerHTML or body.outerHTML (whatever you need). You can even take the whole document source (google it).
Wrap the extracted source with valid HTML/Body WITHOUT the BASE tag and save to disk at c:\MyProjects.
but the code resulted for IMG SRC is full path!
Nothing much you can do about it. this is how the DOM represent the HTML - it's not necessary the HTML source code. this behavior is not consistent. and also depend on how you insert images (I do not use execCommand and have my own dialog and insert my own html code). You need to manually replace the extracted source "file:///c:/MyProjects/" with empty string. at least, this is how I do it.
Edit: You don't need to Navigate() to an external file. you can write the "template"/"empty" HTML via document.write(HTML).
Try this:
const
HTML_TEMPLATE = '<html><head><base href="file:///%s"></head><body style="font-family:Arial">%s</body></html>';
procedure TForm1.LoadHTML(HTMLCode: string);
var
Doc: Variant;
HTML, Path: string;
begin
Path := 'D:\Temp\';
HTML := Format(HTML_TEMPLATE, [Path, HTMLCode]);
WebBrowser1.Navigate('about:blank');
Doc := WebBrowser1.Document;
Doc.Write(HTML);
Doc.Close;
Doc.DesignMode := 'On';
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
LoadHTML('<b>Hello</b><img SRC="resources/1.png">');
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Doc: IHTMLDocument2;
begin
Doc := WebBrowser1.Document as IHTMLDocument2;
if Assigned(Doc) then
begin
ShowMessage(Doc.body.innerHTML);
end;
end;
The output for me is: <B>Hello</B><IMG src="resources/1.png">. in some cases the src might contain the full path. I can't 100% be sure to when this happens. but you need to be ready to deal with this situation by manually replacing the path. there is no conclusive documentation about this so I always deal with this issue in any case.
I suggest to use a small embedded web server such as Internet Direct (Indy) TIdHttpServer, which is able to serve all HTTP requests in a standard way. This removes all potential file system trouble.
In the Data Flow Task, I need re-direct each row to exe and get the output for each row, similar to script component.
I tried to use Process in Script Component, but it is throwing exception "StandardOut has not been redirected or the process hasn't started yet.".
The code used in Script Component is:
Process myApp = new Process();
myApp.StartInfo.FileName = #"Someexe.exe";
myApp.StartInfo.Arguments = "param1";
myApp.StartInfo.UseShellExecute = false;
myApp.StartInfo.RedirectStandardOutput = false;
myApp.Start();
while (!myApp.HasExited)
{
string result= myApp.StandardOutput.ReadToEnd();
}
Any suggestions? I would like to know if there is any better way to do this?
Your error message is helpful: "StandardOut has not been redirected..."
Since you want to capture the output, redirecting it to the Destination Component, don't you want to change the line:
myApp.StartInfo.RedirectStandardOutput = false;
to be:
myApp.StartInfo.RedirectStandardOutput = true;
Consider the example at this link, on BOL:
ProcessStartInfo.RedirectStandardOutput Property
I've implemented a client/server that communicate using a TCP socket. The data that I'm writing to the socket is stringified JSON. Initially everything works as expected, however, as I increase the rate of writes I eventually encounter JSON parse errors where the beginning on the client receives the beginning of the new write on the end of the old one.
Here is the server code:
var data = {};
data.type = 'req';
data.id = 1;
data.size = 2;
var string = JSON.stringify(data);
client.write(string, callback());
Here is how I am receiving this code on the client server:
client.on('data', function(req) {
var data = req.toString();
try {
json = JSON.parse(data);
} catch (err) {
console.log("JSON parse error:" + err);
}
});
The error that I'm receiving as the rate increases is:
SyntaxError: Unexpected token {
Which appears to be the beginning of the next request being tagged onto the end of the current one.
I've tried using ; as a delimiter on the end of each JSON request and then using:
var data = req.toString().substring(0,req.toString().indexOf(';'));
However this approach, instead of resulting in JSON parse errors seems to result in completely missing some requests on the client side as I increase the rate of writes over 300 per second.
Are there any best practices or more efficient ways to delimit incoming requests via TCP sockets?
Thanks!
Thanks everyone for the explanations, they helped me to better understand the way in which data is sent and received via TCP sockets. Below is a brief overview of the code that I used in the end:
var chunk = "";
client.on('data', function(data) {
chunk += data.toString(); // Add string on the end of the variable 'chunk'
d_index = chunk.indexOf(';'); // Find the delimiter
// While loop to keep going until no delimiter can be found
while (d_index > -1) {
try {
string = chunk.substring(0,d_index); // Create string up until the delimiter
json = JSON.parse(string); // Parse the current string
process(json); // Function that does something with the current chunk of valid json.
}
chunk = chunk.substring(d_index+1); // Cuts off the processed chunk
d_index = chunk.indexOf(';'); // Find the new delimiter
}
});
Comments welcome...
You're on the right track with using a delimiter. However, you can't just extract the stuff before the delimiter, process it, and then discard what came after it. You have to buffer up whatever you got after the delimiter and then concatenate what comes next to it. This means that you could end up with any number (including 0) of JSON "chunks" after a given data event.
Basically you keep a buffer, which you initialize to "". On each data event you concatenate whatever you receive to the end of the buffer and then split it the buffer on the delimiter. The result will be one or more entries, but the last one might not be complete so you need to test the buffer to make sure it ends with your delimiter. If not, you pop the last result and set your buffer to it. You then process whatever results remain (which might not be any).
Be aware that TCP does not make any guarantees about where it divides the chunks of data you recieve. All it guarantees is that all the bytes you send will be received in order, unless the connection fails entirely.
I believe Node data events come in whenever the socket says it has data for you. Technically you could get separate data events for each byte in your JSON data and it would still be within the limits of what the OS is allowed to do. Nobody does that, but your code needs to be written as if it could suddenly start happening at any time to be robust. It's up to you to combine data events and then re-split the data stream along boundaries that make sense to you.
To do that, you need to buffer any data that isn't "complete", including data appended to the end of a chunk of "complete" data. If you're using a delimiter, never throw away any data after the delimiter -- always keep it around as a prefix until you see either more data and eventually either another delimiter or the end event.
Another common choice is to prefix all data with a length field. Say you use a fixed 64-bit binary value. Then you always wait for 8 bytes, plus however many more the value in those bytes indicate, to arrive. Say you had a chunk of ten bytes of data incoming. You might get 2 bytes in one event, then 5, then 4 -- at which point you can parse the length and know you need 7 more, since the last 3 bytes of the third chunk were payload. If the next event actually contains 25 bytes, you'd take the first 7 along with the 3 from before and parse that, and look for another length field in bytes 8-16.
That's a contrived example, but be aware that at low traffic rates, the network layer will generally send your data out in whatever chunks you give it, so this sort of thing only really starts to show up as you increase the load. Once the OS starts building packets from multiple writes at once, it will start splitting on a granularity that is convenient for the network and not for you, and you have to deal with that.
Following this response :
var chunk = "";
client.on('data', function(data) {
chunk += data.toString(); // Add string on the end of the variable 'chunk'
d_index = chunk.indexOf(';'); // Find the delimiter
// While loop to keep going until no delimiter can be found
while (d_index > -1) {
try {
string = chunk.substring(0,d_index); // Create string up until the delimiter
json = JSON.parse(string); // Parse the current string
process(json); // Function that does something with the current chunk of valid json.
}
chunk = chunk.substring(d_index+1); // Cuts off the processed chunk
d_index = chunk.indexOf(';'); // Find the new delimiter
}
});
I get a problem with the delimiter because ; was part of my sent data.
It is possible to use this update in order to implement a custom delimiter :
var chunk = "";
const DELIMITER = (';;;');
client.on('data', function(data) {
chunk += data.toString(); // Add string on the end of the variable 'chunk'
d_index = chunk.indexOf(DELIMITER); // Find the delimiter
// While loop to keep going until no delimiter can be found
while (d_index > -1) {
try {
string = chunk.substring(0,d_index); // Create string up until the delimiter
json = JSON.parse(string); // Parse the current string
process(json); // Function that does something with the current chunk of valid json.
}
chunk = chunk.substring(d_index+DELIMITER.length); // Cuts off the processed chunk
d_index = chunk.indexOf(DELIMITER); // Find the new delimiter
}
});
I know this question is old but I have an answer for the people still looking at this.
As said in the answers above, the data event will be fired with a nodejs Buffer containing the data received.
res.on('data', function(chunk) {
//chunk contains the data
})
This next part doesnt seem to be commonly known. The end event is fired when all data is consumed. The close event is fired when the client disconnects
res.on('end', function() {
//the response body has been consumed
})
The full code to get the entire body is below
var body = Buffer.from('');
res.on('data', function(chunk) {
if (chunk && chunk.byteLength > 0) {
body = Buffer.concat([body, chunk]);
}
})
res.on('end', function() {
var data = JSON.parse(body.toString());
//data contains the response json
})
End event is fired when the data is all consumed: source
close event is fired when the request is closed: source
Try with end event and no data
var data = '';
client.on('data', function (chunk) {
data += chunk.toString();
});
client.on('end', function () {
data = JSON.parse(data); // use try catch, because if a man send you other for fun, you're server can crash.
});
Hope help you.