extending delphi's dbrichedit (TJvRichEdit) - delphi

We are upgrading our application for rich text editing. In the old versions there were just plain text with dbmemo, now there should be some formatting and I want to use the TJvRichEdit for this.
The problem: The texts that already exists in the database is plaintext.
When I open a mask and don't change anything in the richEdit, its still plaintext without the rtf formatting tags.
What I need: the old plain text should be automatically converted to rtf text after displaying. (I mean, I open a mask with a richEdit which displays plaintext, and at this moment the plaintext should be enhanced with the rtf tags and saved to the databse).
For this purpose I've created a descendant of the TDBRichEdit.
But I have problems to finda propper place to make this happebs.
For now, I've overwritten the setBounds method with the following code:
procedure TMyRichEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited;
...
if (assigned(DataSource)) AND (assigned(DataSource.DataSet)) AND
(self.Text <> '') AND
(LeftStr(sDataSource.DataSet.FieldByName(DataField).AsString, 5) <> '{\rtf') then
begin
self.DataSource.DataSet.Edit;
// After a call to UpdateMemo the plaintext gets extended with the rtf tags
self.UpdateMemo;
self.DataSource.DataSet.Post;
self.DataSource.DataSet.Edit;
end;
...
end;
So this works, but only if the richEdit has an anchor set. If not it doesn't work.
I know thats the wrong place for this code, I've tried overwrite more propper methods but without access.
How can I solve something like this?
Thanks guys!

You don't need to derive a new class, you can test and process the field contents in the OnAfterScroll event of the DataSet;
procedure TForm1.MyDataSet1AfterScroll(DataSet: TDataSet);
begin
if LeftStr(DataSet.FieldByName('MyRTF').AsString, 5) <> '{\rtf') then begin
DataSet.Edit;
JvDBRichEdit1.UpdateMemo;
DataSet.Post;
end;
end;
Honestly, I don't even see the need for updating each record as soon as they're exposed. I would leave them alone until they're edited, test for the the rtf signature and update them just before posted.
Otherwise, if you'd insist on your approach, and if you decide to use TJvDBRichEdit, LoadMemo would be a good place to override. But editing there would probably cause recursion, so a user message could be posted to the owner form for notifying edit contents have been loaded. Nah, not good.. ;)

Related

How can I filter only plain text files in the TOpenDialog in delphi

I need to be able to put text from a file into a memo but that I have that part covered.
What I would like to do is filter only plain text files(.txt; .html; .c; .cs; ect) in the file dialogue
Is there some quick way to do that or do I just manually filter all plain text files?
The answer given in the comment by Andreas Rejbrand is correct.
Here is a code fragment that implement it:
procedure TForm1.Button1Click(Sender: TObject);
begin
OpenDialog1.Filter := 'Text files|*.TXT;*.HTML;*.C;*.CS';
if OpenDialog1.Execute(Handle) then
Memo1.Lines.LoadFromFile(OpenDialog1.FileName)
else
Memo1.Lines.Add('**** cancel ****');
end;
This code write load the selected file content into the memo.

How to know when the USER changed the text in a TMemo/TEdit?

I was always bugged by the fact that TMemo (and other similar controls) only have the OnChange event. I would like to know when the USER changed the text, not when the text was changed programmatically.
I know two methods to discriminate between the user changed text and programmatically changed text:
Put OnChange:= NIL before you change the text programmatically. Then restore the OnChange. This is error prone as you need to remember to do it every time you change text from the code (and to which memos/edits needs this special treatment to be applied). Now we know that every time the OnChange is called, the control was edited by user.
Capture the OnKeyPress, MouseDown, etc events. Decide if the text was actually changed and manually call the code that needs to be called when user edited the ext. This could add a big amount of procedures to an already large file.
There is a more elegant way to do it?
You can write a helper procedure to do your option 1, and use it in your framework whenever you want to ensure no OnChange event is triggered when you set the text. e.g.:
type
TCustomEditAccess = class(TCustomEdit);
procedure SetEditTextNoEvent(Edit: TCustomEdit; const AText: string);
var
OldOnChange: TNotifyEvent;
begin
with TCustomEditAccess(Edit) do
begin
OldOnChange := OnChange;
try
OnChange := nil;
Text := AText;
finally
OnChange := OldOnChange;
end;
end;
end;
TMemo has also the Lines property which also triggers OnChange, so you can make another similar procedure that accepts Lines: TStrings argument.
How about using the Modified property?
procedure TForm1.MyEditChange(Sender: TObject);
begin
if MyEdit.Modified then
begin
// The user changed the text since it was last reset (i.e. set programmatically)
// If you want/need to indicate you've "taken care" of the
// current modification, you can reset Modified to false manually here.
// Otherwise it will be reset the next time you assign something to the
// Text property programmatically.
MyEdit.Modified := false;
end;
end;

Is there any way to disable selecting of text in a memo control?

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;

Under what conditions will a TForm fire OnResize on show?

As an extension of this question:
TForm.OnResize is sometimes fired before a form is first shown, but not always. For example, if BorderStyle is either bsDialog or bsNone, then OnResize will not fire. For all other BorderStyle values (and with all other properties at their defaults), OnResize does fire.
Are there other things that affect whether OnResize will fire before the form is shown? For example, other properties, or combinations of properties, that can affect this?
The OnResize event is a result of the ShowWindow API function sending a WM_SIZE message to the window. That bears repeating: the message is coming from Windows, not from Delphi. It's a Windows function (ShowWindow) that's (sometimes) sending the message that triggers the event -- so the VCL source code is not really helpful in this case.
Bonus points for definitive answers based on documented ShowWindow / WM_SIZE behavior, e.g. references to MSDN documentation or Petzold books.
Maybe it even depend on user's display settings or desktop theme or Windows version. If OnResize were giving me problems like this, I would build my program to always expect it and handle it in any situation, no matter what I think to be the cause.
I believe that OnResize will fire when an event dispatch a message
saying that form size (left, bottom, width, height) will be modified.
Since you already discovered which message fires that event, you need
now trace where the message is sent in the vcl.
Look at the vcl source code to see if you can spot those operations.
Edit: let's go low level. Forms in windows (grossly talking) have what
is called "window class" (it's not a class like we know it oop). All times the window class of the form is resized (and form is visible), the WM_SIZE is sent.
So it will not happen all the times the form is shown, but only the it's dimensions are changed compared with underlying window class.
As you have observed, many properties valuez change the dimensions of the form (even a few pixels).
This is a very superficial explanation, that's a ton of other details - but it's my understanding how things works "under the hood".
There's no substitute for testing. How about creating a form in code, setting the properties you're interested in and recording when the resize event is called.
If you'll excuse the ugliness of the code, here's a rough proof of concept that tests all combinations of BorderStyle and Position without explicitly coding for each one. You can add more properties and take it as far as you like. A tool like CodeSite would make the logging cleaner and easier, too.
Create an application with 2 forms. Make sure the second one isn't auto-created.
In the second form, add a property and add a little logging code to the form's Resize event:
private
FOnResizeFired: TNotifyEvent;
public
property OnResizeFired: TNotifyEvent read FOnResizeFired write FOnResizeFired;
end;
...
procedure TForm2.FormResize(Sender: TObject);
begin
if Assigned(FOnResizeFired) then
FOnResizeFired(self);
end;
In the main form, add TypInfo to the uses clause and drop a button and a memo on the form.
Add a simple procedure:
procedure TForm1.ResizeDetected(Sender: TObject);
begin
Memo1.Lines.Add(' *** Resize detected');
end;
Now add the following to the ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
lBorderStyle: TFormBorderStyle;
lBorderStyleName: string;
lPosition: TPosition;
lPositionName: string;
lForm: TForm2;
begin
Memo1.Clear;
for lBorderStyle in [low(TFormBorderStyle) .. high(TFormBorderStyle)] do
begin
for lPosition in [low(TPosition) .. high(TPosition)] do
begin
lBorderStyleName := GetEnumName(TypeInfo(TFormBorderStyle), Integer(lBorderStyle));
lPositionName := GetEnumName(TypeInfo(TPosition), Integer(lPosition));
Memo1.Lines.Add(Format('Border: %s Position: %s', [lBorderStyleName, lPositionName]));
Memo1.Lines.Add(' Creating form');
lForm := TForm2.Create(self);
try
Memo1.Lines.Add(' Form Created');
lForm.OnResizeFired := ResizeDetected;
Memo1.Lines.Add(' Setting border style');
lForm.BorderStyle := lBorderStyle;
Memo1.Lines.Add(' Setting Position');
lForm.Position := lPosition;
Memo1.Lines.Add(' Showing form');
lForm.Show;
Memo1.Lines.Add(' Form Shown');
lForm.Close;
Memo1.Lines.Add(' Form Closed');
finally
FreeAndNil(lForm);
Memo1.Lines.Add(' Form Freed');
end;
end;
end;
end;
You'll notice that resize fires when some properties are set before the form is shown, and I see that in some combinations, resize seems to fire twice when the form is shown. Interesting.

Is there some better way to copy all DataSet Fields and their properties to another DataSet?

I'm cloning a TClientDataSet and I want to copy all the fields to the clone (which is a new DataSet), I know I can loop through the Fields and copy the info, or make 2 instances of my class and just clone the cursor, but is there some better way? Something like create a new DataSet and assign the fields info?
EDIT:
The following class helper method works for me:
procedure TDataSetHelper.CopyFieldDefs(Source: TDataSet);
var
Field, NewField: TField;
FieldDef: TFieldDef;
begin
for Field in Source.Fields do
begin
FieldDef := FieldDefs.AddFieldDef;
FieldDef.DataType := Field.DataType;
FieldDef.Size := Field.Size;
FieldDef.Name := Field.FieldName;
NewField := FieldDef.CreateField(Self);
NewField.Visible := Field.Visible;
NewField.DisplayLabel := Field.DisplayLabel;
NewField.DisplayWidth := Field.DisplayWidth;
NewField.EditMask := Field.EditMask;
if IsPublishedProp(Field, 'currency') then
SetPropValue(NewField, 'currency', GetPropValue(Field, 'currency'));
end;
end;
Anyone has a better way for doing this?
If you just want to copy the field definitions you can do the following:
ds2.FieldDefs.Assign(ds1.FieldDefs);
ds2.CreateDataSet;
ds2.Open;
Of course this assumes you created FieldDefs for ds1.
Are you looking for a more aesthetic way of doing it or a faster way of doing it?
If the former, create your own classes that hide the loop.
If the latter, don't even worry about it. A very wise coder once said to me: disk access costs; network access costs; maybe screen access costs; everything else is free.
Don't confuse the size of the source code with execution time. Looping a thousand times through memory and copying bits is undetectable compared to the initial handshake of a database connection.
Cheers
If you're going to loop through the dataset to make a copy, remember to call DisableControls on it before, and EnableControl afterwards.
Without that, things can get really slow if you've got visual controls showing the data of the dataset on your form.
Would CloneCursor work for you?
NON-PROGRAMMABLE METHOD
first tclientdataset: open fields editor. add all fields if not already shown. select all fields. copy to clipboard.
second tclientdataset: open fields editor. paste clipboard in fields editor.
done
you should now see identical fieldDefs for both tclientdatasets now.

Resources