Make/reuse a custom component in delphi with fixed layout properties (design) - delphi

Is it possible to create a custom component, not only by adding extra functionality like methods and events, but also design the custom component (so that color, font, ... shouldn't be set when adding that component).
I would like to use this to construct a custom TDBGrid which I reuse and can just add to a form properly designed.
Thanks in advance!

Everything you can do in the designer you can "code into your component". Generally you just redeclare the new default values for properties and set / initialize them in the overriden constructor. Ie to create custom panel with red color as default you'd do
type
TMyPanel = class(TPanel)
public
constructor Create(AOwner: TComponent); override;
published
property Color default clRed;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited;
Color := clRed;
end;

Component templates should serve the task.
After you're created and installed the component, drop it on the form, adjust every property you want to, go Component-Create Component Template, choose name and palette page. Starting this moment you can select and drop your customized variant on the form
Since the copied component is a simple text inside clipboard, you can also copy-paste your customized component to a text file and when you need this copy, just select this fragment and copy it as a simple text, Delphi form will accept this object when you paste it. You even can organize a little storage for your saved component after the "end." in the module or inside comments, both will be safely bypassed by the compiler.

Related

Set DevExpress Grid Tableview Editable property and determine its parent via RTTI

I have a project with multiple TcxGrids on. The class hierarchy structure of the grids in my project are as follows:
TForm->TPanel->TcxPageControl->TcxTabSheet(subclassed)->TcxGrid->TcxGridLevel->TcxGridDBBandedTableView
In my subclassed TcxTabSheet I have added a new property "ReadOnly" that when set loops over the tabsheets controls and sets them to enabled/disabled respectively.
When a TcxGrid controls enabled property is set to true, the user can no longer interact with the grid at all (including navigating).
It seems I need to set the OptionsData.Editing property on the TcxGridDBBandedTableView to achieve a readonly but still navigable grid control.
Simple enough until you factor in that I want to do this in a loose coupled manner which I think leaves me the option of RTTI.
I have written the following code that loops over the forms controls (looping over the tabs controls or components doesn't give me access to the TcxGridDBBandedTableView). Once the control is found I can set its editing property via RTTI. I just don't seem to able to determine if that TcxGridDBBandedTableView belongs to the TabSheet it sits on.
var
compIdx: Integer;
begin
for compIdx := 0 to Pred(ComponentCount) do
if (Components[compIdx].ClassNameIs('TcxGridDBBandedTableView')) then
SetOrdProp(GetObjectProp(Components[compIdx], 'OptionsData'), 'Editing', Ord(not FReadOnly));
end;
TL;DR
How can I determine what pagectrl tab a cxgrid is on and set its TableView.OptionsData.Editable property without adding any devexpress units to the uses clause of the unit.
You are iterating over the components owned by the form. I think that's the wrong approach. You should be looking at the parent/child relationship rather than ownership. Not least because it's perfectly possible for a form to contain controls that it does not own. So, your approach can fail to find controls, particularly dynamically created controls.
So, if you have a tabsheet (or indeed any windowed control), you can iterate over its children like this:
for i := 0 to TabSheet.ControlCount-1 do
DoSomething(TabSheet.Controls[i]);
If your target grid control is a direct descendent of the tabsheet then this will be enough. If it is more than one level deep in the hierarchy then you will need a recursive solution. I will leave that recursive solution as an exercise for you.
Suppose that you have a control and want to find the tabsheet that it sits inside, then you need to walk up the parent chain. Like this:
function GetParentOfClass(Control: TControl; AClass: TWinControlClass): TWinControl;
var
Control: TWinControl;
begin
while Assigned(Control) and not (Control is AClass) do
Control := Control.Parent;
Result := TWinControl(Control);
end;

Detecting insertion of new components at design-time in Delphi

How can my Delphi component detect at design time if any other component is being dropped on the form?
You should override Notification method of your component; something like that:
type
TMyComponent = class(TComponent)
//..
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
end;
procedure TMyComponent.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opInsert) and (csDesigning in ComponentState) then begin
// AComponent was dropped on the form
end;
end;
If you mean controls being dropped instead of components, and if you mean dropping on your component rather than dropping on the form, then:
Add an CM_CONTROLLISTCHANGE message handler to track controls before they are inserted, or
Add an CM_CONTROLCHANGE message handler to track controls after they are inserted (WParam points to the control),
Don't do this until csLoading is out of ComponentState to prevent tracking during form creation by the IDE.
Just a tip, if it helps... i am having a similar issue:
I want to hide non-visual component icon (for my TMyLabel=class(Classes.TComponent) component) when i drop it from IDE inside a Form, Panel, etc ...
I have oveloaded: Loaded and ReadState to get such icon out of sight (at design-time) ... on Loaded & ReadState i put DesignInfo to point to (-100,-100), so icon is not shown
I have oveloaded: WriteState to avoid Left & Top be saved to the .dfm (at design-time) ... on WriteState i put it to point (0,0), so it is not saved inside the .dfm
Note: I use same technique/trick to not save properties i do not want, etc ... i really only let Caption to be saved inside the .dfm
The question / tip is:
When i drop a new "control" (of my component) into the form, such icon is visible just where i drop it... how to hide it?
Maybe user1580348 is trying something similar ... or something related to auto-align such non-visual "controls" / "components".
In other words:
How can we control the icon position when dropping a new (our component) control on a form, panel, etc...
I know my problem is much easier, but i did not yet solve it... i want just to hide that icon IDE shows for non-visual components (only for controls that are of my component)... but maybe knowing how to it will also helps user1580348.
As i said, it is just a tip/clue.

Using the style of the inherited control

My first firemonkey component is inherited from a TSpinBox but I cannot figure out how to get it to use the same style as the base component.
At design time in my app I can set the StyleLookup to "spinboxstyle" and get the correct style but if I try to do that in the constructor for the new component it ignores it.
What is the correct procedure for this?
The constructor is the correct place.
You have to use FStyleLookup := 'spinboxstyle'; (Note the F)
Not StyleLookup := 'spinboxstyle';

Adding Custom control to another custom control

What i am trying to accomplish is to create new touchkeyboard.
First i created buttons which i derive from speed buttons.
That done, now i need to create multiple buttons and layout them somewhere. This is were i get stuck.
I created a new component which i derive from TGraphicControl (this should be my new touchkeyboard), but i don't know how to add components to canvas. I actually don't know whether i'm supposed to add them to canvas or to some other component (eg. panel)!?!
Is my approach OK?
Thanks in advance.
If you're creating a custom visual control, you need to create the buttons and position them manually. For example:
TOnScreenKeyboard = class(TWinControl)
public
constructor Create(AOwner: TComponent);
end;
[...]
constructor TOnScreenKeyboard.Create(AOwner : TComponent)
var
TempButton : TSpeedButton;
begin
inherited;
TempButton := TSpeedButton.Create(self);
TempButton.Parent := self;
TempButton.Top := 10;
TempButton.Left := 15;
TempButton.Caption := 'A';
end;
You can put the button creation into a loop and position each one according to where it should be.
(I wrote this off the top of my head, and I don't write a lot of Pascal anymore, so there may be some minor mistakes! But it should get you started.)
Because of your wording and confusion between Panel, Canvas and custom controls in general, I assume you're a Delphi beginner. You need to learn about frames: embarcadero docwiki link on frames
Frames allow you to create re-usable portions of GUI. You use the IDE to "draw" the frame, you can then place that composite control (the frame) onto forms or other frames. It's a very powerful feature and it's conceptually very close to what other languages call "custom controls" (very close to what asp.net or WPF consider a custom control to be).
In the Delphi world, when you say "custom control", people would normally expect you to want to create an reusable control that's placed in a package and it's installed in the IDE. It's an fairly advanced subject. If that's what you want then I misunderstood the question, sorry.

Making a TPageControl flat in Delphi 7

I don't know whether this question can be answered here, but I hope it will.
I wrote a simple text editor in Delphi 7 that serves as my primary IDE for writing C code under Windows. I run Windows in a VM and I needed something light.
In any case, it uses a TpageControl that gets a new tab whenever you open or create a new file. Pretty standard.
Now, the TPageControl under Delphi has no flat property.
NO I don't mean setting the tab style to tsButtons or tsFlatButtons
the borders cannot be set to "none" and it looks pretty bad when you add a text editor into the tab control.
Is there any way to make a TpageControl flat?
EDIT:
On an open source PageControl that supports flat here's what I found:
procedure TCustomTabExtControl.WndProc(var Message: TMessage);
begin
if(Message.Msg=TCM_ADJUSTRECT) and (FFlat) then
begin
Inherited WndProc(Message);
Case TAbPosition of
tpTop : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Top:=PRect(Message.LParam)^.Top-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpLeft : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Left:=PRect(Message.LParam)^.Left-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpBottom : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Bottom:=PRect(Message.LParam)^.Bottom-4;
PRect(Message.LParam)^.Top:=0;
end;
tpRight : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=PRect(Message.LParam)^.Right-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
end;
end else Inherited WndProc(Message);
end;
The thing is when I tried something similar on the main application it won't work. It won't even compile.
When the tabs are drawn as buttons, no border is drawn around the display area, so set the Style property to tsButtons or tsFlatButtons. (For non-VCL programmers, this is equivalent to including the tcs_Buttons window style on the tab control.)
An alternative is to use a TNotebook. It holds pages, but it doesn't do any painting at all. You'd have to provide the tabs yourself, such as by setting the tab control's height equal to the height of the tabs, or by using a TTabSet. (TTabSet is available in Delphi 2005; I'm not sure about Delphi 7.)
Regarding the code you found, it would be helpful if you indicated why it doesn't compile, or if you gave a link to where you found it, since I suppose the compilation error was because it refers to fields or properties of the custom class rather than the stock one. Here's what you can try to put it in your own code, without having to write a custom control.
Make two new declarations in your form like this:
FOldTabProc: TWndMethod;
procedure TabWndProc(var Msg: TMessage);
In the form's OnCreate event handler, assign that method to the page control's WindowProc property:
FOldTabProc := PageControl1.WindowProc;
PageControl1.WindowProc := TabWndProc;
Now implement that method and handle the tcm_AdjustRect messsage:
procedure TForm1.TabWndProc(var Msg: TMessage);
begin
FOldTabProc(Msg);
if Msg.Msg = tcm_AdjustRect then begin
case PageControl1.TabPosition of
tpTop: begin
PRect(Msg.LParam)^.Left := 0;
PRect(Msg.LParam)^.Right := PageControl1.ClientWidth;
Dec(PRect(Msg.LParam)^.Top, 4);
PRect(Msg.LParam)^.Bottom := PageControl1.ClientHeight;
end;
end;
end;
end;
You can fill in the other three cases if you need them. Tcm_AdjustRect is a message identifier declared in the CommCtrl unit. If you don't have that message in that unit, declare it yourself; its value is 4904.
I suspect this doesn't stop the control from drawing its borders. Rather, it causes the contained TTabSheet to grow a little bigger and cover up the borders.
I'm using Delphi XE8 and the following seems to do the trick:
ATabControl.Tabs.Clear;
ATabControl.Style := TTabStyle.tsFlatButtons;
ATabControl.Brush.Color := clWhite;
You could always use a commercial solution. I would strongly recommend Raize components, which support flat TPageControls with tabs. The component set is very easy to work with, and supports numerous visual enhancements which in my opinion give a better feel to any application.
(source: raize.com)
Drop two TPageControls, one with tabs as Tabs, with a global height equal to the tabs, and one with flatbuttons and Tabvisible properties set to false, which would be aligned under the first one. Then make sure the tab change on the first TPagecontrol makes the tabs also change in the second one.

Resources