(Someone edit the title if you can understand and define my problem better.)
The problem which I am having is with style formatting of a RichEdit "reverting" back to the default "nothing" aka [] and then back to whatever I set it to, bold or italic for example.
The thing that is at fault - I assume, since I have no idea how it is breaking things - is a procedure (REMainLinesCheck) that checks for amount of lines in the RichEdit and deletes the first one until a certain point is reached (to show a maximum of 14 lines at once) like so:
while REMain.Lines.Count > 14 do
REMain.Lines.Delete(0);
I have 6 occurrences of the above procedure in other procedures that add lines to the RichEdit, but none of them change RichEdit.SelAttributes.Style but one, which was adding only one Bold line like so:
REMain.SelAttributes.Style := [fsBold];
REMain.Lines.Add('something');
REMainLinesCheck;
So I have removed all occurrences except that one and started poking around, it didn't take long to see that it was working in fact fine, regular and bold lines where being added normally and excess lines where being deleted - no problems. But as soon as I reintroduced REMainLinesCheck procedure into another procedure (for clarity purposes, lets call it Proc3Lines, because that's what it does: adds 3 lines and then calls the check for excess lines), every line that follows this Proc3Lines that should be Bold is not... From what I have experienced here it seems that REMainLinesCheck does something in Proc3Lines, since without it everything is fine.
Obviously it's not a circle of procedures that call each other, but the other parts of the code have nothing to do with this RichEdit, not to mention that I don't change RichEdit.SelAttributes.Style anywhere for REMain except that one place that I have shown, there is another RichEdit in the same unit that I do change its line's style like that, but that cannot possibly be related in any way... could it? (No it does not, I just checked.)
Basically: what the hell Delphi? It cannot get any simpler than this and I am still managing to fail, can someone explain and/or fix this? Ask questions, I'll elaborate as much as I can if something is not clear.
To apply a format to a new added line, use the following:
procedure TForm1.Button1Click(Sender: TObject);
var
LineIndex: Integer;
begin
LineIndex := RichEdit1.Lines.Add('Something');
RichEdit1.SelStart := RichEdit1.Perform(EM_LINEINDEX, LineIndex, 0);
RichEdit1.SelLength := RichEdit1.Perform(EM_LINELENGTH, RichEdit1.SelStart, 0);
RichEdit1.SelAttributes.Style := [fsBold];
end;
This has worked for me:
procedure TformStart.Proc;
var
endtxtpos: integer;
begin
endtxtpos := Length(REMain.Text);
REMain.Lines.Add('something');
REMain.SelStart := endtxtpos-(REMain.Lines.Count-1);
REMain.SelLength := Length('something');
REMain.SelAttributes.Style := [fsBold];
end;
But since I don't know any better, please criticize and suggest how I can do it better.
Related
I'm using TSynEdit as a more user-friendly TMemo, mostly for the advanced shortcuts, UNDO/REDO, and so on.
Other things are OK, except the wordwrap behavior, please check the attached screenshot below, SynEdit has a strange space shown on the left-most side.
How to avoid that and make it look like TMemo?
The TSynEdit's key property settings:
synEdit1.UseCodeFolding := False;
synEdit1.Options := [eoAutoIndent, eoDragDropEditing, eoEnhanceEndKey,
eoGroupUndo, eoScrollPastEol, eoSmartTabDelete,
eoSmartTabs, eoTabsToSpaces];
synEdit1.ScrollBars := ssVertical;
synEdit1.TabWidth := 4;
synEdit1.WantTabs := True;
synEdit1.WordWrap := True;
synEdit1.FontSmoothing := fsmNone;
This is not a complete, tested answer to the q, but may offer the determined
reader a jumping-of point to a functional solution.
The word-wrapping behaviour of a TSynEdit is determined by its current
TSynWordWrapPlugin. The default plugin is defined in SynEditWordWrap.Pas
and contains the procedure TSynWordWrapPlugin.WrapLines method, starting at
line 512 in the version I downloaded yesterday using the D10.2.3 GetIt Manager.
Starting at line 560 there is the block of code which, as far as I can tell,
accounts for the space at the start of each wrapped line as illustrated in the q:
if Editor.IsWordBreakChar(vRunner^) then
begin
vRowEnd := vRunner;
break;
end;
Dec(vRunner);
vRunner and vRowEnd are among a number of PWideChar variables used in the WrapLines method.
Watching the behaviour of this code, which is inside a while loop (which is looking for a place to do a word-wrap), it operates
so that when Editor.IsWordBreakChar(vRunner^) returns true, the vRunner pointer
has already moved backwards past the word-break char, which is why it (the space) ends
up on the following line, causing the problem noted by the OP.
Changing the code to
if Editor.IsWordBreakChar(vRunner^) then
begin
{ma} Inc(vRunner); // WARNING: not fully tested
vRowEnd := vRunner;
break;
end;
Dec(vRunner);
forces the vRunner pointer forwards past the word-break character so that the space is included at the end of the line rather than at the start of the next one, so the SynEdit
then displays its wrapped text like the standard TMemo.
Personally, I would not use this change, but would instead see if I could persuade
the SynEdit developers to provide an official solution. if I did use the change
shown above, I certainly wouldn't do it by changing the source of SynEditWordWrap.Pas,
I would do it by writing a replacement for TSynWordWrapPlugin and I would include a check that the inc(vRunner) does not exceed the valid bounds of the buffer being used to do the word-wrapping.
For this question please refer to a previous question I asked a while back: Do I need TThreads? If so can I pause, resume and stop them? LU RD answered the question with a provided demo and some comments.
I stopped using Delphi for quite a long time but now I am getting back into it and redoing a project. This project has time consuming operations such as opening a Gif, extracting the frames and then adding those frames (bitmaps) into a TImageList and TListView. This time I actually add the bitmaps directly into a TObjectList, as seen here: How to add and retrieve Bitmaps to and from a TList?
Typically nothing special needs doing here to speed it up as most Gif animations are small, but with medium to large Gifs the application can hang. This is going to get worse though as those bitmaps are going to be modified at runtime using various imaging filters such as grayscale, change hue etc. So I am sure I need multi-threading for this otherwise accessing each bitmap and then manipulating is going to be very slow (as I found out before).
So with that said, I am foolishly trying to adapt (without a clue of what I am doing) some of my procedures to work with the TThread example posted by LU RD I linked to at the top.
I wish I spent a bit more time with the original question to ask for more information but I guess I got sidetracked and moved onto something else, which meant I learned nothing.
Take this snippet from the threading example:
const
cWorkLoopMax = 500;
function TForm1.HeavyWork: boolean; // True when ready
var
i, j: integer;
begin
j := 0;
for i := 0 to 10000000 do
Inc(j);
Inc(workLoopIx);
Result := (workLoopIx >= cWorkLoopMax);
end;
For a start I have no idea what cWorkLoopMax is for, and why its value is set to 500?
Secondly I guess the HeavyWork procedure is just a sample, which runs in a loop a 10000000 times whilst incrementing the j variable?
Then we have the workLoopIx which I am unsure what is for? Maybe something to do with the position within the thread maybe?
So, here I have my current code (no threading) which handles opening the Gif and adding to the TListView and TImageList. The procedures I use are in another unit, if needed I will post it also, but this is what I use inside a TAction (actOpen):
if OpenPictureDialog.Execute then
begin
Screen.Cursor := crHourGlass;
try
BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);
ListView1.Items.BeginUpdate;
try
ListView1.Items.Clear;
for I := 0 to BitmapCollection.BitmapList.Count - 1 do
begin
with ListView1.Items.Add do
begin
Caption := 'bitmap' + IntToStr(I+1);
ImageIndex := I;
end;
end;
finally
ListView1.Items.EndUpdate;
end;
finally
Screen.Cursor := crDefault;
end;
end;
What I dont understand is how to put that into a thread procedure, such as HeavyWork? I just created a new one called Job_Open and did this:
procedure TForm1.actOpenExecute(Sender: TObject);
begin
if OpenPictureDialog.Execute then
begin
if not Assigned(MyThread) then
begin
workLoopIx := 0;
btnStartTask.Enabled := false;
btnPauseResume.Enabled := true;
btnCancelTask.Enabled := true;
MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, Job_Open);
end;
end;
end;
function TForm1.Job_Open: boolean;
var
I: Integer;
begin
BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);
for I := 0 to BitmapCollection.BitmapList.Count - 1 do
begin
with ListView1.Items.Add do
begin
Caption := 'bitmap' + IntToStr(I+1);
ImageIndex := I;
end;
Inc(workLoopIx);
end;
Result := (workLoopIx >= BitmapCollection.BitmapList.Count);// cWorkLoopMax);
end;
This is clearly not right, the performance is slower and I am getting all kind of errors such as Invalid Handle.
I would be extremely grateful if someone could take some time to explain my comments, what I am doing wrong and what I should be doing instead, updated code and comments in source are welcome but I am hoping to learn a bit more of what is going on with the code ideally.
In a perfect world if there is a library of sorts that exists out there that is easy to use then that would be a massive help if I cannot understand what is happening above. Is there such a library that can do something like:
procedure DoSomething;
begin
BeginThreading();
HeavyWork;
StopThreading();
end;
Thanks in advance, and apologies for the lengthy post.
DISCLAIMER: While my answer is not an answer directly to your question it is a psobile solution to your problem.
After reading your questions I have one question to you:
Are you applying same image graphical effects on all ImageList images?
If you are then I must say that you started on working on your problem from wrong approach.
First you need to know that Imagelist doesen't store all those images seperately but that it is storing them all in same verry wide image. So when you read any ImageList image internally ImageList creates output bitmap and then uses Canvas.CopyRect which is quite. When you save image to image list it internally uses Canvas.Draw.
So when you do this many times you create lots of unnecessary data movment.
So instead of your approach where you work on seperate images I recomend you work on ImageLists internal image whose handle you can get using ImageList.GetImageBitmap. This will alow you to apply same graphical effect on all ImageList images at once. And if you don't need to apply graphical effects to all ImageList images I bet you can modify your mage processing method to work only on parts of the image.
In order to learn more about ImageList I recomed you read its documentation:
Image list explanation: http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.Controls.TImageList
Image list GetImageBitmap explanation
http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.ImgList.TCustomImageList.GetImageBitmap
Is there any way to disable selecting of text in a memo control because it's very anoying.
The memo is Read Only.
I think you should rethink. I realise that your control is used in read-only mode, but still, what if the end user wishes to copy a part of the text? Then he needs to be able to select the part in question.
Still, if you are certain that you need to disable every kind of selection, the easiest approach is to use a TRichEdit instead of the TMemo, and do simply
procedure TForm1.RichEdit1SelectionChange(Sender: TObject);
begin
RichEdit1.SelLength := 0;
end;
You could also use the onMouseUp event
procedure TForm1.Memo1MouseUp(Sender: TObject: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Memo1.SelLength > 0 then
Memo1.SelLength := 0;
end;
But, that doesn't stop selecting with the keyboard..
or you could also use the onEnter, and just change the focus to another control on your form.
procedure TForm1.Memo1Enter(Sender: TObject);
begin
Edit1.SetFocus;
end;
I played around with TRichEdit and TMemo until I was bored to tears. Yes, you can do a few tricks with event handling on the object, but it still is not the desired effect - and the cursor winds up blinking somewhere. So the best thing I could find was to use TLabel. I'm using Borland C++ Builder 6, and the \n is translated correctly with inline text strings for TLabel. So,
Label1->Caption = "this is a test of the emergency\n"
"broadcast station, this is only\n"
"a test. If this had been an\n"
"actual emergency, blah blah blah...\n";
Works just fine. I haven't tried to read in from a file, but I'm certain that if the stream were exactly as seen it would also work. Since you are going to have to enter or read in the text you want displayed anyway - this should work well instead of using a bunch of TLabels for each line. If you have a concern for word wrapping, you will have to process that portion separately. If it static, then just do it by hand like I did in the example. I sure hope this helps or at least gives an idea...
atomkey -
As i understand you would like to use memo as label actually (and sometimes it really have sense).
When i need to use TcxMemo (memo component from DeveloperExpress) as label i use such simple procedure:
procedure ShowMemoAsLabel(m: TcxMemo);
begin
m.Enabled := False;
m.Properties.ReadOnly := True;
// AH: Unfortunately it doesn't copy some important properties, maybe it will
// be fixed in future versions of DEX, but at moment we do some job ourselves.
m.StyleDisabled := m.Style;
m.StyleDisabled.BorderColor := m.Style.BorderColor;
m.StyleDisabled.BorderStyle := m.Style.BorderStyle;
m.StyleDisabled.Color := m.Style.Color;
m.StyleDisabled.Edges := m.Style.Edges;
m.StyleDisabled.Shadow := m.Style.Shadow;
m.StyleDisabled.TextColor := m.Style.TextColor;
m.StyleDisabled.TextStyle := m.Style.TextStyle;
m.StyleDisabled.TransparentBorder := m.Style.TransparentBorder;
end;
In the OnFormShow event I need (for a particular set of conditions) not to show the form.
Something like "if counter > 15 don't show the form".
I could of course refactor and move many things on form create, but this is a lot of work, because this is a common form and there are too many changes involved.
Now I close the form at the end of OnFormShow but anyway I see the form appear for some milliseconds.
Unfortunately the condition that tells me not to show the form is decided inside OnFormShow. Is there a trick to avoid the form to show?
Refactor your code so that it doesn't show at all until you are ready. Either refrain from calling Show, or set Visible to False if you have not yet done so.
I suspect it's too late by the time you reach OnShow but even so doing it that way would be indicative of poor design. Moving code out of OnShow into a different method really should not be very much trouble at all.
+1 on the refactoring, but in the mean time, try this:
AlphaBlend := true;
AlphaBlendValue := 0;
That should make the form invisible, and seemed to work in my OnShow test app (D2010/XP). I'm guessing you'll need to add code to make the form close, possibly a timer?
A very bad solution is to do
procedure TForm1.FormShow(Sender: TObject);
begin
inc(n);
if n > 15 then
begin
Left := Screen.DesktopWidth + 32;
Top := Screen.DesktopHeight + 32;
PostMessage(Handle, WM_CLOSE, 0, 0);
end;
end;
I need to copy a form (Delphi 2007) to the clipboard as an image to paste what the user can see into a word document. The clipboard part is not really a problem. The questions is how to get a bitmap for the form.
Searching has turned up multiple options.
Call GetFormImage
Use the PrintWindow API function that is part of the GDI+
Send a WM_PRINT message
Copy the Canvas for the current form using Canvas.CopyRect
I also found a component called TExcellentFormPrinter that has a solution that claims to works better than any of these options, but I don't know what method it is using.
All of these options seem to have different problems. Most of the information I am finding seems to be outdated. I can't seem any good source that compares the different options with enough detail for me to make a choice. Any advice on which option to go with.
I have tried these on my form and they all seem to work OK, I just trying to avoid problems down the road. Any advice on what solution to go with?
Update: What Potential Problems with GetFormImage?
Andreas asked what the problem is with GetFormImage. Hopefully nothing anymore, that is part of what I am trying to get an answer to. What has me concerned is so many of my search results seem to be suggesting creative alternatives to using GetFormImage. I was hoping the answers would clear up the waters a little bit.
I would be really happy with an answer that got a lot of up votes that said - GetFormImage used to have some problems but there is no reason not to use it now. :-)
As to the actual problem with GetFormImage. One issue for some users was only the visible part of the form would appear in the image (i.e. you can't capture a hidden or overlapped window). That is not really an issue for me as my entire form is visible.
1) The bigger issues deal with specific support required from the controls on your form. The Delphi 4 Fixes and Known issues page list has this entry (note it is listed as "Deferred to Next"). I could not find a QC entry that showed this resolved:
Area: vcl\core vcl classes
Reference Number: 1088 (Published: 12/16/98)
Status: Deferred to Next
Rel Date Reported: 8/6/98 Severity:
Commonly Encountered Type: Basic
Functionality Failure Problem:
The problem is with GetFormImage most nest windows controls like comboboxes, etc. are drawn blank.
2) I am also using the DevExpress controls. At one time their controls (fixed at the end of 2006) did not support the PaintTo messages that GetFormImage was using. This is fixed in the DevExpress release I am using, but it raises other issues with me, what is the chance that other control I am using may not work correctly?
3) Here is a more recent (2010) post on the Embarcadero Groups. The user was having trouble using GetFormImage where part of the graph they were showing on screen did not appear in the final image. They also needed the form caption included (which I do not) and they took the Canvas.CopyRect approach outlined in this post.
4) Here is the quote from the TExcellentImagePrinter page. I would have no problem buying their product if needed. There component looks like it was last updated in 2002 (There is a Delphi 2007 trial version though). I can't tell if I really need to go that direction or not.
You can try using GetFormImage or
Form.Print. Try dropping a ComboBox
down on a form, then call GetFormImage
or Form.Print. If you get a
printout, do you see the text in the
ComboBox? No? Neither does anyone
else! This is only a small example of
the problems you will encounter when
printing VCL forms.
You can also try using Borland's
TI-3155 "A better way to print a
form". I wrote the TI when I worked at
Borland as a stop gap measure. While
it will print the combobox text, it
will fail on many printers, it can't
print the entire form if your user has
resized the form, and it can't print
forms that are hidden from view or is
located partially off the screen. The
code basically produces a screenshot,
and to print an image reliably, you
would probably want to take a look at
our TExcellentImagePrinter product!
Why? Simply put, it can require a
couple of thousand lines of low level
graphics code to get bitmaps to print
well under Windows.
I do not know what the problem is with GetFormImage, but an option that you have not tried (at least not explicitly) is
procedure TForm1.FormClick(Sender: TObject);
var
bm: TBitmap;
begin
bm := TBitmap.Create;
try
bm.SetSize(ClientWidth, ClientHeight);
BitBlt(bm.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, Canvas.Handle, 0, 0, SRCCOPY);
Clipboard.Assign(bm);
finally
bm.Free;
end;
end;
In almost all cases I would expect this to produce the same result as
bm := GetFormImage;
try
Clipboard.Assign(bm);
finally
bm.Free;
end;
though. (Also, the Canvas.CopyRect procedure employes StretchBlt which I would expect to produce the same result as BitBlt when no stretching is applied.)
Method 2
You can always use Print Screen:
procedure TForm1.FormClick(Sender: TObject);
begin
keybd_event(VK_SNAPSHOT, 1, 0, 0);
end;
This will also capture the border and the title bar. If you only wish to obtain the client area, you can crop the image:
procedure TForm1.FormClick(Sender: TObject);
var
bm, bm2: TBitmap;
DX, DY: integer;
begin
Clipboard.Clear;
keybd_event(VK_SNAPSHOT, 1, 0, 0);
repeat
Application.ProcessMessages;
until Clipboard.HasFormat(CF_BITMAP);
bm := TBitmap.Create;
try
bm.Assign(Clipboard);
bm2 := TBitmap.Create;
try
bm2.SetSize(ClientWidth, ClientHeight);
DX := (Width - ClientWidth) div 2;
DY := GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME );
BitBlt(bm2.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, bm.Canvas.Handle, DX, DY, SRCCOPY);
Clipboard.Assign(bm2);
finally
bm2.Free;
end;
finally
bm.Free;
end;
end;