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.
Related
In Delphi XE4 if you set HideSelection to true and use an explorer style TListView (when the selection rectangle has a gradient background like Windows Explorer) clicking on another control will not hide the selection rectangle. It will stay there as if nothing has happened - it will not even turn into a gray rectangle like normally when the Listview doesn't have focus.
Is this a Delphi bug or a "feature" of the MS Listview control? Are there any known workarounds or fixes for this? It's really annoying...
This is a feature of the underlying control. The delphi code does nothing with the property beyond passing on the LVS_SHOWSELALWAYS list view style to the underlying control.
Initially I was surprised by your question. I've never seen the behaviour that you describe. Upon closer inspection I realise that is because all my list views are virtual. That is they set OwnerData to True and supply content in response to OnData events. Doing that is the only workaround that I know of.
This "feature" is explained by David, and here is a workaround.
By utilizing the OnExit event to save the selection and set selection to nil, you would mimic the wanted behavior. When the ListView is focused, restore the selection.
To make it react on the mouse, make the ListView focused in the OnMouseEnter event.
Type
TForm1 = class(TForm)
...
private
FSelected: TListItem;
...
end;
procedure TForm1.ListView1Enter(Sender: TObject);
begin
if (ListView1.SelCount = 0) and Assigned(FSelected) then
ListView1.Selected := FSelected;
end;
procedure TForm1.ListView1Exit(Sender: TObject);
begin
FSelected := ListView1.Selected;
if Assigned(FSelected) then ListView1.Selected := Nil;
end;
procedure TForm1.ListView1MouseEnter(Sender: TObject);
begin
ListView1.SetFocus;
end;
Having mentioned this solution, why not go for the simple one, set HideSelection = false, and the selected item will turn gray when unfocused, just like Sertac mentioned in a comment.
Delphi controls have AutoSize property which is exposed e.g. in TPanel. It adjusts the width/height of the panel depending on the content.
Apparently it does nothing when the panel is invisible, and does no realigning later when it's set to visible. So if I put some controls into it and then make it visible, the size is not adjusted.
I can trigger adjusting size by setting height to any value in FormShow:
procedure TForm1.FormShow(Sender: TObject);
begin
Panel1.Height := Panel1.Height + 1; //triggers auto-resize
end;
But I have to do this manually for every control which has AutoSize on. I'm bound to forget something.
Are there better ways to fix this, preferably once and for all?
I don't think there's much that you can do. A better way to for the re-sizing is to add a call to the Realign method of the panel immediately after you make it visible.
You could hook into the CM_VISIBLECHANGED message and force the matter there, for auto-sized controls. For instance, using an interceptor:
type
TPanel = class(Vcl.ExtCtrls.TPanel)
protected
procedure CMVisibleChanged(var Message: TMessage); message CM_VISIBLECHANGED;
end;
procedure TPanel.CMVisibleChanged(var Message: TMessage);
begin
inherited;
if Visible and AutoSize then
Realign;
end;
Its been a while since i used delphi, but one thing i remember is that the controls did play a bit of mind games on me, most of the time it was because the rendering engine did not refresh the form and the controls.
If you have the control set to auto-resize i would suggest checking if form1.refresh or panel1.refresh since its been a few years since i played with it ( delphi 7 ) i might confuse refresh with repaint. which some controls had, which initiated the size calculation before repainting the control. since delphi controls are open source you can go in to the apropriate pas file to find the control and look at refresh/repaint to see if you can persist the auto-resizing.
hope that helped.
Delphi v7
I have yet another remedial question.
Using a TColorBox I would like to change the font color in each of 4 RichEdit controls. I am using an OnClick event of the color box.
This procedure works fine for one rich edit control.
procedure TForm1.cmbFColorClick(Sender: TObject);
begin
reProc.SelAttributes.Color := cmbFColor.Selected;
end;
If I try to write the same code for each of the richedit controls it will change the font color in all of the richedit control at the same time.
Example: I select and change the text color on one richedit control, then I change the text color on a different control the text color on both richedit controls is changed at the same time.
Example
procedure TForm1.cmbFColorClick(Sender: TObject);
begin
reProc.SelAttributes.Color := cmbFColor.Selected;
reApp.SelAttributes.Color := cmbFColor.Selected;
reServ.SelAttributes.Color := cmbFColor.Selected;
end;
This procedure does not work at all
procedure TForm1.cmbFColorClick(Sender: TObject);
begin
if ActiveControl is TDBRichEdit then
with ActiveControl as TDBRichEdit do
SelAttributes.Color := cmbFColor.Selected;
end;
Is there a way I can change the text color on all of the richedit controls without affecting any of the other controls?
i think the active control is your TColorBox not the richeditboxes, because only one control can be the active control. If i remember right, this control which have the focus.
So you have to implement a procedure like this.
and you have remember by code, which was the last, active richedit.
procedure changeColor(edit : Trichedit) ;
begin
procedure changeColor(edit:Trichedit);
begin
edit.SelAttributes.Color := cmbFColor.Selected;
end;
Kind Regards
Problem solved. In a PageControl OnChange event I set the RichEdit SelLength to "0" for each rich edit control.
Thank you for your help. It gave me the idea.
I'm piecing things together from this question, your last question, the comments to those questions, and your answers to those questions.
What you are trying to do is modify SelAttributes.Color for a single rich edit control. The problem is working out which rich edit control to operate on.
Let us suppose you had the following function available:
function ActiveRichEdit: TRichEdit;
Then you could simply write:
ActiveRichEdit.SelAttributes.Color := NewColor;
Or, if there was a possibility that there was no rich edit control active:
if ActiveRichEdit<>nil then
ActiveRichEdit.SelAttributes.Color := NewColor;
So, how do we implement ActiveRichEdit? Well, it seems that you have a control with multiple pages, each containing a distinct rich edit. That sounds very much like a page control to me.
I'm going to assume that your page control is called PageControl, and the tab sheets called TabSheet1, TabSheet2 etc., and rich edit controls are named RichEdit1, RichEdit2 etc. But if your names are different then you'll need to adapt this code.
function TForm1.ActiveRichEdit: TRichEdit;
begin
if PageControl.ActivePage=TabSheet1 then
Result := RichEdit1
else if PageControl.ActivePage=TabSheet2 then
Result := RichEdit2
else if PageControl.ActivePage=TabSheet3 then
Result := RichEdit3
// etc. etc.
else
Result := nil;
end;
Now, there are other ways to do this. You could make an array of rich edit references that could be indexed by PageControl.ActivePageIndex. And there are indeed yet more possible solutions.
But the key is to use the ActivePage or ActivePageIndex properties of the page control to work out which rich edit control to operate on.
When I use SetWindowLong command to change direction of treeview, popupmenu on its node dose not show. Full Code is here :
Procedure SetWinControlBiDi(Control: TTreeView);
var
ExStyle: Longint;
begin
ExStyle := GetWindowLong(Control.Handle, GWL_EXSTYLE);
SetWindowLong(Control.Handle, GWL_EXSTYLE, ExStyle or WS_EX_RTLREADING or WS_EX_RIGHT or WS_EX_LAYOUTRTL or WS_EX_NOINHERITLAYOUT );
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
SetWinControlBiDi(TreeView1);
end;
The standard way to do this is to use the Delphi BiDiMode property. It's best to do it this way so that the VCL is aware that you want right-to-left. You need to change the BiDiMode property on the popup menu too.
Now, the correct way to do this is not to change the properties on the individual components. Doing it that way is laborious and very error prone. Set Application.BiDiMode somewhere in your application's initialization and the change will propagate through to all your components.
For example you can make the change in your application's .dpr file:
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.BiDiMode := bdRightToLeft;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
You need to make sure that you have not modified any component's BiDiMode or ParentBiDiMode in any .dfm file. If you have simply remove those lines from your .dfm file and that will allow the single application wide Application.BiDiMode setting to control everything.
Your approach of setting GWL_EXSTYLE is problematic. The VCL is in control of that setting and if you do need to change it, doing so in TForm.OnShow will lead to strange bugs. Sometimes windows need to be re-created and when this happens your code to set GWL_EXSTYLE will not run and your tree view will revert to left-to-right. If you do need to modify the window styles then you need to override TWinControl.CreateParams for the component. However, in this case the VCL has direct support for BiDi and that is the best solution.
This is an alternative solution to show TPopupMenu In this case
1- Use OnMouseDown Event
2- Write this code to show a TPopupMenu when you click the right mouse button
var
pt : TPoint;
begin
pt := Mouse.CursorPos;
if Button = mbRight then
APopupMenu.Popup(pt.X, pt.Y);
Good luck !
When Delphi (2006) goes quantum: I've got "something" that appears to be both a TToolBar and a TPanel, depending on how you observe it. I'd like to understand what's going on.
Here is how to create it and what happens:
in the DFM
add a TToolBar named bar;
in that TToolBar, put a TPanel.
in the code and at runtime:
the panel appears in the list of buttons bar.Buttons[], let's say at index i
bar.Buttons[i], from the compiler point of view, is a TToolButton
bar.Buttons[i].ClassName = 'TPanel'
(bar.Buttons[i] is TToolButton) = true, but that's the compiler optimising the call to 'is' out;
indeed IsBarButton(bar.Buttons[i]) is false for function IsBarButton (defined below);
bar.Buttons[i].Name is the name I gave the TPanel in the DFM
inspecting the value bar.Buttons[i] in the debugging:
it has a property 'Caption' the real TToolButton's don't have
strangely, it has all properties TToolButton's have, like TToolButton.Indeterminate (=true).
IsToolButton:
function IsToolButton(X : TObject) : boolean;
begin
Result := X is TToolButton;
end;
So bar.Buttons[i] both is and is not a TToolButton... what's up ?
(Bottom story is I'd like to distinguish my TPanel from the genuine TToolButton's. This I can do in more or less hackish ways. My goal by asking this question here, is to get a fuller understanding of what's really happening here.)
Question: what is happening ?
Sub-question: is it legitimate to add a TPanel to a TToolBar ?
The only thing the OS allows to be added to a tool bar is a tool button. To add anything else, you technically need to create a button and then put your other things on top of it. The button that gets added is literally a placeholder. It's there to take up space so the next thing you add gets positioned properly.
You can see this sometimes if the non-tool-button control you add is transparent. Then you can see the tool bar's separator underneath, so it looks like there's a vertical line running through the middle of your control.
When you add a non-tool-button control to the tool bar, the Buttons property indeed lies about the type of the control. You'll notice throughout ComCtrls.pas that TToolBar itself always casts the buttons to TControl and then checks whether they really descend from TToolButton. It's completely legitimate to add non-buttons to a tool bar; that's why the Form Designer allows it in the first place.
I suggest you use the Form Designer to create your tool bar. That way, the IDE will maintain an identifier for you in your form, so you'll always have a direct reference to your panel. You won't have to go hunting for it in the tool bar. Even if you're creating the tool bar manually, it's a good idea to make an extra field to refer to the panel. Even if you move the panel around within the tool bar, it will still be the same object the whole time, so you needn't worry about dangling references.
When you put a couple of buttons and a panel on a toolbar, and a Memo somewhere, then run this code in the form's onCreate:
procedure TForm1.FormCreate(Sender: TObject);
function _IsToolButton(const aObject: TObject): Boolean;
begin
Result := aObject is TToolButton;
end;
function _IsPanel(const aObject: TObject): Boolean;
begin
Result := aObject is TPanel;
end;
var
i: Integer;
begin
for i := 0 to bar.ButtonCount - 1 do begin
Memo.Lines.Add(Format('bar.Buttons[%d].Name: %s', [i, bar.Buttons[i].Name]));
Memo.Lines.Add(Format('bar.Buttons[%d].ClassName: %s', [i, bar.Buttons[i].ClassName]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TToolButton: %s', [i, BoolToStr(_IsToolButton(bar.Buttons[i]), True)]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TPanel: %s', [i, BoolToStr(_IsPanel(bar.Buttons[i]), True)]));
// Memo.Lines.Add(Format('bar.Buttons[%d] has Caption property: %s', [i, 'dunno yet']));
Memo.Lines.Add('');
end;
end;
you'll see that the panel is not a TooButton and most definitely a TPanel.
The debugger showing properties of a ToolButton for the panel, is simply the debugger casting each and every bar.Buttons[i] to a TToolButton. When you right-click on the "Data" tab of the Debug inspector, you can Type Cast it to a TPanel and you will get the correct information.
'is it legitimate?' - well, you are definitely using the toolbar in a way that the creator of the toolbar did not ment it to be used. Will it blow up in your face? Who knows. I guess you could walk through the sourcecode for the toolbar and check if it is safe or not, but what about possible third party tools or components, expecting to find buttons in a toolbar?
I would see if I could find another way of solving my problem. Clever hacks have a tendency to turn out not so clever after all, and it will surely higten the wtf-rate of your code.
Do you have to use a toolbar? What about a flowpanel with buttons and panels instead? Or a panel with a toolbar and a panel?