I've noticed that it's possible to set the TextHint property on a TDBEdit in code (it's not visible in the Object Inspector), however it doesn't display, is there an easy way of making this work?
The following setup works in XE2. Drop a TClientDataSet, TDataSource, and 2 TDBEdit controls on a Form, and make the OnCreate event handler of the Form look like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
DataSource1.DataSet := ClientDataSet1;
DBEdit1.DataSource := DataSource1;
DBEdit2.DataSource := DataSource1;
ClientDataSet1.FieldDefs.Add('First', ftString, 20);
ClientDataSet1.FieldDefs.Add('Last', ftString, 20);
ClientDataSet1.CreateDataSet;
ClientDataSet1.Open;
DBEdit1.DataField := ClientDataSet1.Fields[0].FieldName;
DBEdit1.TextHint := 'first name';
DBEdit2.DataField := ClientDataSet1.Fields[1].FieldName;
DBEdit2.TextHint := 'last name';
ClientDataSet1.Insert;
end;
One potential problem is the TDBEdits being read-only. For instance, remove the Insert() call from the snippet and the edits will remain empty. This behavior is similar with regular edits, which is reasonable - when an edit control does not allow editing, there's no point in showing a hint about what the user should enter.
Related
I would like to create a non visual component (like TTimer for example) that I can drop on the form and that I can set up directly from the Object Inspector, but I don't want to see its icon on the form (it'd just obstruct anything). For example TFloatAnimation works like this but I don't understand how.
The GExperts library (http://www.gexperts.org/) has a plug-in which can toggle the visibility
of non-visual components on a form, and it is apparently not Delphi-version-specific but it is
not exactly trivial.
The method which does this is
procedure THideNonVisualCompsExpert.ToggleNonVisualVisible(Form: TCustomForm);
const
NonVisualClassName = 'TContainer';
var
VisibleState: Boolean;
FormHandle: THandle;
CompHandle: THandle;
WindowClass: string;
FirstCompFound: Boolean;
WinControl: TWinControl;
ChildControl: TWinControl;
i: Integer;
begin
Assert(Assigned(Form));
Assert(Form.Handle > 0);
FirstCompFound := False;
WinControl := Form;
if InheritsFromClass(WinControl.ClassType, 'TWinControlForm') then
begin
for i := WinControl.ComponentCount - 1 downto 0 do
begin
if WinControl.Controls[i] is TWinControl then
begin
ChildControl := WinControl.Controls[i] as TWinControl;
if InheritsFromClass(ChildControl.ClassType, 'TCustomFrame') then
begin
WinControl := ChildControl;
Break;
end;
end;
end;
end;
FormHandle := GetWindow(WinControl.Handle, GW_CHILD);
CompHandle := GetWindow(FormHandle, GW_HWNDLAST);
VisibleState := False;
GxOtaClearSelectionOnCurrentForm;
while (CompHandle <> 0) do
begin
WindowClass := GetWindowClassName(CompHandle);
if AnsiSameText(WindowClass, NonVisualClassName) then
begin
if not FirstCompFound then
begin
VisibleState := not IsWindowVisible(CompHandle);
FirstCompFound := True;
end;
if VisibleState then
ShowWindow(CompHandle, SW_SHOW)
else
ShowWindow(CompHandle, SW_HIDE);
end;
CompHandle := GetWindow(CompHandle, GW_HWNDPREV);
end;
end;
in the unit GX_HideNonVisualComps.Pas.
As written, it toggles the visibility of all the non-visual components on the
target form, but looking at the code of the ToggleNonVisualVisible method it looks like it
ought to be possible (but I have not tried) to adapt it to operate on a selected component class and
force instances of the class to a non-visible state. Once you have done that, you would probably
need to experiment with how and when to invoke the method at design-time; if I was doing it, I would probably start
with somewhere like the target component's Loaded method.
(I would feel more comfortable posting this "answer" as a comment but obviously it would be too long)
I have thought about this. A Non Visual Component does not do any painting, in a Windows environment (like the IDE) it has no Window, and therefore cannot influence how the IDE chooses to render it.
One approach would be to derive from TWinControl, making your component a Visual Component, and then to ensure that it is not drawn. Try setting the positioning properties to be non-published, and when you are parented, always set your position outside the parent window. This means that your control is always clipped and never painted.
I haven't tried this, but I can see no reason why it wouldn't work.
You can also use this approach to have an apparently non visual component that renders information in the IDE at designtime, but not at runtime.
I've encountered a very strange DBComboBox problem in a master/detail app using Access via ADO. If you have a DBComboBox (.Style=csDropDown) containing a list of items and you enter some text that doesn't exist in the list, the value in the table's DBComboBox field won't appear when navigating back to that record. I've used the DBNavigator.OnClick code below to attempt to resolve this problem but it only works if the first record in the table contains a value not in the list. When you change the value of the DBComboBox in the first record to one that is in the list, no nonconforming items will appear in the DBComboBox text. Has anyone found a solution to this?
procedure TForm1.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
var
SavePlace : TBookmark;
begin
if (DBComboBox1.Text='') then begin
SavePlace := TADODataSet(DBNavigator1.DataSource.DataSet).GetBookmark;
TADODataSet(DBNavigator1.DataSource.DataSet).Requery;
TADODataSet(DBNavigator1.DataSource.DataSet).GotoBookMark(SavePlace);
TADODataSet(DBNavigator1.DataSource.DataSet).FreeBookMark(SavePlace);
end;
end;
Unfortunately I don't have XE installed, but I have made a sample project which
reproduces your problem in D7 and Seattle. The code is shown below and I think
you will find that if you follow the exact steps below, it shows that there is something
rather strange going on. Update See the bottom of the answer for a possible work-around, which I think is preferable to the code you quote in your q.
As you'll see, except for Form1 itself, all the components are created at runtime
entirely in code. This is to remove any doubt whether the behaviour is caused
by some obscure property setting (it isn't) and in case you wish to submit it
to EMBA as a bug report. For a similar reason I've used a TClientDataSet so that
the app does not depend on any external data.
Steps (please follow steps 4-7 exactly the first time you try them)
Restart the IDE and create a new project and edit the .Pas file for the main form as shown below. The reason for restarting the IDE is that I discovered that if it has been running for a long time (two days in my case) the details of the misbehaviour
of the app change slightly).
Compile and run.
The app will start with the first from in the DBGrid selected.
Type anything (an 'X' will do) into the DBComboBox, then click the Save toolbutton
on the DBNavigator.
Click the Next (>) toolbutton on the DBNavigator once only. The DBComboBox now displays
'Two'.
Click the Prior (<) toolbutton on the DBNavigator once only. The DBComboBox is now empty.
Click the Prior (<) toolbutton on the DBNavigator once only. The DBComboBox now displays
what you typed in step 4.
Close the app. Most likely the IDE debugger will catch a fault and open the CPU window.
This fault occurs on the line
DestroyWindow(FHandle);
in TApplication.Destroy. I am no Windows internals expert but I think it's likely that this is because of some corruption being caused by whatever causes the blank result in step 6. The fact that
step 7 causes the DBComboBox to correctly display what you typed makes me suspect that cause is actually
in the way the DBComboBox interacts with its FieldDataLink which connects it to the dataset.
Btw, the fact that the fault does not occur if you call DBComboBox1.Free in TForm1's FormDestroy
seems to me to confirm that the fault is related to whatever is causing your problem.
All this, and the fact that it has apparently passed unnoticed in the 25 years of Delphi, seems very strange
to me. This demo app can show up another quirk that's been lurking in the DBGrid for a similar length ot time. To see
it:
Comment out all the references to the DBComboBox and reinstate dgMultiSelect amongst the grid options in the
line that sets them. Compile and run the app.
Click in the cell in the Name column for the first row, type something and save it.
Click the Next toolbutton once. The first row does not de-select itself as it should.
AFAICT (by displaying the DBGrid's count of Bookmarks on the form's caption) this is not
because it has saved a bookmark on the first row.
While I've been writing this, a possible work-around has occurred to me, which I'll updated
this to include if I can get it to work.
Code
type
TForm1 = class(TForm)
procedure FormCreate(Sender : TObject);
private
procedure SetUpDataSet;
procedure SetUpGUI;
protected
public
ClientDataSet1 : TClientDataSet;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
DBNavigator1: TDBNavigator;
DBComboBox1: TDBComboBox;
end;
[...]
procedure TForm1.SetUpGUI;
begin
ClientDataset1 := TClientDataSet.Create(Self);
DataSource1 := TDataSource.Create(Self);
DataSource1.DataSet := ClientDataSet1;
DBGrid1 := TDBGrid.Create(Self);
DBGrid1.Top := 8;
DBGrid1.Left := 8;
DBGrid1.Width := 425;
DBGrid1.Options := [dgEditing, dgTitles, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit{, dgMultiSelect}];
DBGrid1.DataSource := DataSource1;
DBGrid1.Parent := Self;
DBNavigator1 := TDBNavigator.Create(Self);
DBNavigator1.DataSource := DataSource1;
DBNavigator1.Top := 144;
DBNavigator1.Left := 16;
DBNavigator1.Parent := Self;
DBComboBox1 := TDBComboBox.Create(Self);
DBComboBox1.DataField := 'Name';
DBComboBox1.DataSource := DataSource1;
DBComboBox1.Top := 240;
DBComboBox1.Left := 16;
DBComboBox1.Parent := Self;
end;
procedure TForm1.SetUpDataSet;
var
Field : TField;
begin
// Create 2 fields in the CDS
Field := TIntegerField.Create(Self);
Field.FieldName := 'ID';
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
Field := TStringField.Create(Self);
Field.FieldName := 'Name';
Field.Size := 40;
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SetUpGUI;
SetUpDataSet;
// Set up DBComboBox
DBComboBox1.Style := csDropDown;
DBComboBox1.Items.Add('One');
DBComboBox1.Items.Add('Two');
DBComboBox1.Items.Add('Three');
// Next, set up the CDS
ClientDataSet1.CreateDataSet;
ClientDataSet1.InsertRecord([1, '']);
ClientDataSet1.InsertRecord([2, 'Two']);
ClientDataSet1.InsertRecord([3, '']);
ClientDataSet1.First;
end;
Possible work-around Add the following method to Form1:
procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet);
var
S : String;
begin
S := DataSet.FieldByName('Name').AsString;
if S <> DbComboBox1.Text then
DbComboBox1.Text := S;
Caption := IntToStr(DBGrid1.SelectedRows.Count);
end;
Then, in the SetUpGUI method, add the following immediately after the line where ClientDataSet1 is created:
ClientDataset1.AfterScroll := ClientDataSet1AfterScroll;
I have not tested this thoroughly, but it seems to work in the test conditions of the steps I've described above.
Hopefully I can articulate this properly. I have a TScrollBox on a form. I am adding instances of another form to a dynamically created panel that I am adding to the TScrollBox , here is the code I am using to add it.
procedure TSettings.AddWFOnclick(Sender: TObject);
var
dlg : TWFDetail;
panel: TPanel;
i : Integer;
begin
panel := TPanel.Create(self);
dlg := TWFDetail.Create(self);
panel.Parent := WFList;
panel.clientheight := dlg.height;
panel.align := alTop;
panel.Top := 330;
panel.Left := 0;
dlg.Parent := panel;
dlg.align := alClient;
dlg.visible := True;
dlg.Show;
end;
The above works beautifully to add my form and panel. As seen here:
The issue occurs when I try to close the dlg and remove the panel. I don't have any sample code for that. I have tried a dozen different things and can't seem to figure it out. I am closing the form with a close call on the click of the red X, then the panel remains. Seen here:
I need to be able to remove the blank panel and shift everything up. I just can't seem to wrap my head around it since the panel is being created dynamically.
The parenting structure is TScrollBox > TPanel > MyForm
Any help would be appreciated.
You can use an TNorifyEvent on TWFDetail form. Define it:
property OnCloseForm:TNotifyEvent read FOnCloseForm write FOnCloseForm;
When you close the form, fire the event if assigned:
Self.Close;
if Assigned(OnCloseForm) then
OnCloseForm(Self);
When you create the form, assign the OnCloseForm event:
...
dlg.OnCloseForm := CloseForm;
...
And define a simple CloseForm procedure to free the panel that you use to contain the form:
var
pnl:TPanel;
begin
if (Sender is TWFDetail) then begin
if TWFDetail(Sender).Parent is TPanel then begin
pnl := TPanel(TWFDetail(Sender).Parent);
pnl.Free;
end;
end;
end;
There are some other ways to to this, but this work fine.
I'm experimenting with a simple ClientDataSet project that uses a TADTField named Address which contains two subfields, Line1 and City, both of which are strings, size 20. THe CDS is connected to a DBGrid and DBNavigator.
If I set up the CDS using TFieldDefs in the Object Inspector and the ChildDefs property of the ADT field, the project compiles and executes fine.
However, if I try to set up the ATD field and its children in code, I get two problems:
Unlike when I use the FieldDefs method, the ATD "cell" doesn't appear in the DBGrid, so the Line1 and City sub-fields dont display inside it. Instead they appear as ordinary fields and they are duplicated. There is one "Line1" column, then a "City" one, then another "Line1" ...
When I close the form, I get a "double-free" AV inside a second (?) call to TFields.Destroy.
Obvioulsly I'm doing something wrong but I can't see what it is.
Here's my code :
procedure TForm1.FormCreate(Sender: TObject);
var
ADTField : TADTField;
Field : TField;
begin
// at this point, the clientDataSet has no TFields or TFieldDefs
Field := TIntegerField.Create(nil);
Field.FieldName := 'ID';
Field.DataSet := ClientDataset1;
ADTField := TADTField.Create(nil);
ADTField.FieldName := 'Address';
ADTField.DataSet := ClientDataset1;
Field := TStringField.Create(nil);
Field.FieldName := 'Line1';
Field.Size := 20;
Field.DataSet := ClientDataset1;
ADTField.Fields.Add(Field);
Field := TStringField.Create(nil);
Field.FieldName := 'City';
Field.Size := 20;
Field.DataSet := ClientDataset1;
ADTField.Fields.Add(Field);
ClientDataset1.CreateDataSet;
ClientDataset1.Insert;
ClientDataset1.FieldByName('ID').AsInteger := 1;
try
ADTField.Fields.FieldByName('Line1').AsString := '1, Railway Cuttings';
ADTField.Fields.FieldByName('City').AsString := 'London';
except
end;
ClientDataset1.Post;
end;
That's the entire code of the project. I'm using D7.
I remember being foxed by something similar when I first tried out ADT fields: although the TFieldDefs editor in the IDE has an obvious way to add child FieldDefs to an TADTField, there's no counterpart in the IDE's TFields editor.
Anyway, I think you're not quite "parenting" the two fields that you want to be children of the ADT one correctly. Instead of calling ADTField.Fields.Add, you need to do it via the field itself, by setting its ParentField property:
Field := TStringField.Create(ClientDataset1);
Field.FieldName := 'Line1';
Field.Size := 20;
Field.DataSet := ClientDataset1;
// ADTField.Fields.Add(Field);
Field.ParentField := ADTField;
And that btw is how you would do it in the IDE if you were setting up TFields instead of TFieldDefs. You'd create your Line1 and City fields in the ordinary way using the TFields editor, then select them in turn on the OI and set their ParentField property. I think you'll find that they and the Address field will then display in your grid correctly and the AV on shutdown will go away.
I using Delphi BDS 2006 and have a DevExpress cxGridDBColumn with properties set to DateEdit and was wondering whether it is possible to add a checkbox to the displayed date time picker popup?
I am not sure that I understand what you wish to achieve. Anyway, it is impossible without creating a custom cxEditor which supports this look&feel and desired functionality.
Here is a quick hack which should help you implement this feature. However, you should handle the checkBox yourself. I have done this for the standalone editor, however, the same approach will work with the inplace editor:
procedure TForm1.cxDateEdit1PropertiesPopup(Sender: TObject);
var
AEdit: TcxDateEdit;
ACalendar: TcxPopupCalendar;
ACheckBox: TcxCheckBox;
begin
AEdit := TcxDateEdit(Sender);
if AEdit.Tag <> 1 then
begin
AEdit.Tag := 1;
ACalendar := TcxPopupCalendar(AEdit.Properties.PopupControl);
ACheckBox := TcxCheckBox.Create(Self);
ACheckBox.Parent := ACalendar.Parent;
ACheckBox.Align := alBottom;
ACheckBox.Transparent := True;
ACalendar.Parent.Height := ACalendar.Parent.Height + ACheckBox.Height;
end;
end;