I have met a bug in applying of StyleLookup in TTabControl nested in FMX Frame. The appearance of the control does not accept the style if the frame with the control is nested in a HorzScrollBox and at form creation the position of the frame is outside the visible area of the form. In other words if the control is visible when the form is shown the control is painted with necessary style appearance otherwise - not.
To reproduce the bug:
Create a new grid metropolis application.
Create a Frame and put a TTabControl on it. Set Align of the TabControl to alClient. Add, say, 4 TTabItems.
Open detail form, and set its width to a value > your Screen.Width (for me 2500 was enough).
Copy-paste any of the Columns (Layouts), say, 2 times and use the most right layout to nest a frame in it. You will see normal presentation of tabitems at designtime. Set style settings. You will get the following picture:
Done. Run the project. You will get the bug.
At runtime the appearance of TabItem is like this:
If you set the StyleLookup manually at runtime the appearance might either not change either be set to every other item (for example 1st, 3rd and not set to 2nd and 4th though you assign the same StyleLookup to all TabItems). The other interesting finding. If you have other forms containing TabControls with TabItems (nested without frames and closer to left border of the form) for example Form2, you get the following. If you show the faulty Form1 at runtime first you will see the bug, but if you close this form and show Form2 you see proper TabControl. Closing Form2 and showing Form1 (faulty) afterwards will give you a proper appearance of TabControl in a faulty form.
It has something in common with the bug reported by me earlier: Incorrect selection of items in an FMX TListbox (Grid Metropolis UI). It is still not fully solved.
There was also a question yesterday but it is about an exact problem with VCL Frames and the solution is not suitable for FMX.
Appended.
The way is to partially set the style is to set OnPainting event handler for TTabControl in the parent form unit (it does not work being set in frame unit) and write something like this:
procedure TPatientsScrollF.HMDiagnosisFr1TabControl2Painting(Sender: TObject;
Canvas: TCanvas; const ARect: TRectF);
var
i: byte;
begin
(Sender as TTabControl).StyleLookup := 'tabcontrolstyle';
for i := 0 to (Sender as TTabControl).TabCount-1 do
(Sender as TTabControl).Tabs[i].StyleLookup := 'tabitemstyle';
end;
But than you still get a problem - the Tabs are not drawn properly - see the lower edge of inactive Tabs.
Or even like this:
Appended 2
I have just met the bug appearing even at design-time after placing a frame in a form and assigning style to TabItems. The TabControl looks like in fig. 3.
I am trying to implement a Firemonkey TListbox that has a number of TListBoxItems. On one of the TListBoxItems I have placed a number of TRadioButtons by simply dropping them on the TListBoxItem at design time. When the application is run the visual appearance of the buttons is erratic when the TListBoxItem is scrolled off the screen and back on.
Another TListBox question here at StackOverflow makes the point that a TListBox does not host any item other than a TListBoxItem. That being said, there seems to be no such restriction that I can find about a TListBoxItem. I have routinely placed TEdit, TLabel and TComboBox components in this manner without difficulty.
To duplicate the condition start a new Firemonkey desktop application; drop a TListBox on the form and set it to alClient; populate the TListBox with a number of TListBoxItems; set the heights of the TListBoxItems or Form so that when run, you are able to scroll the TListBox; drop three TRadioButtons on the topmost TListBoxItem; set the GroupName for all the TRadioButtons to the same name; run the application; select a TRadioButton so that it appears selected; scroll the TListBox so that the TListBoxItem containing the buttons scrolls off the form; scroll back; continue selecting different TRadioButtons and scrolling until anomalies are observed.
Questions: First and probably most important, am I permitted to simply drop components on a TListBoxItem at design time? Has my previous success with this technique been simply accidental?
Second and also of importance for an application I'm working on, if this is NOT a viable approach to my UI, can anyone suggest better approaches?
TIA
I entered a Report at Embarcadero's Quality Central and after a few exchanges with Tomohiro-san along with Marcus Adams suggestion that the problem is related to scrolling came up with the following:
Tracking through the various cases mentioned it appears that there are a number of other cases that are related to this one because of the use of scrolling. When an item is scrolled off the screen and then scrolled back the system apparently reapplies the styles to the items being returned to visibility. At least, in the few cases I have traced that seems to be the case. It is on this reapplication of style and rendering that the problem seems to occur. In my own examples I have added a button that displays the IsChecked property for the controls and the rendering does not reflect the property correctly in all cases, suggesting that it is the style/rendering that is causing the issues.
The related issues I was able to find are:
QC 120593
QC 117381
QC 119638
QC 117658
I'm waiting for a resolution.
I ran into the same problem. I found no solution, but as a work-around, I ended up just replacing the radio buttons with check boxes and adding code so that they behaved in a similar manner to Radio buttons. E.g., only allowing one of a group of check boxes to be selected...
In my current project I have a TTbxDock with toolbars and panels (TTbxDockablePanel). After moving TBX-->SpTBX I can't place a panel (TSpTbxDockablePanel) on a TSpTbxDock. I see a runtime error message saying I can't place a panel on a dock.
Do I need to use a TSpTbxMultiDock instead for panels? Why?
Can I place toolbars on a TSpTbxMultiDock instead of a TSpTBXDock?
i.e. why such code is written:
procedure TSpTBXCustomMultiDock.ValidateInsert(AComponent: TComponent);
begin
inherited;
if not (AComponent is TSpTBXCustomDockablePanel) then
raise EInvalidOperation.CreateFmt('Cannot insert %s into MultiDock', [AComponent.ClassName]);
end;
procedure TSpTBXCustomDockablePanel.ValidateContainer(AComponent: TComponent);
begin
inherited;
if (AComponent is TTBDock) and not (AComponent is TSpTBXCustomMultiDock) then
raise EInvalidOperation.CreateFmt('Cannot insert %s into %s. Place it on a MultiDock instead', [Self.ClassName, AComponent.ClassName]);
end;
Why can't I just use one type of dock?
"I need to use TSpTbxMultiDock instead for panels? Why?"
The short answer is, because the TSpTBXMultiDock component is written to handle and dock only TSpTBXDockablePanels.
The long answer is that the TSpTBXMultiDock component is written to handle and dock only TSpTBXDockablePanels because the docking code relies on specific methods / properties in the controls it is docking. See TSpTBXCustomDockablePanel.SetParent, which looks after this: it accesses the properties and methods TSpTBXCustomMultiDock.
UpdateDockablePanelsDockPos
ClientAreaWidth
ClientAreaHeight
In other words, the docking and layout code needs to know certain information and ask the docked controls to do certain things. The easiest way to do this is to constrain the type of the controls it can dock to descendants of a specific class which declares and implements the required interface (using interface loosely, there is no (class) interface declared, it's just an informal set of methods / properties.)
From scanning the code quickly, I think only the first of these, UpdateDockablePanelsDockPos, is really essential. I may have missed something. But this method gets a list of all docked panels and updates each one's DockPos, which is the one-dimensional position of the dock in the panel. That is, for a horizontal panel it is the left / horizontal start, and for a vertical panel it is the top / vertical start. It also updates the total so the panel knows how big is has to be, or can be if it chooses.
I'm not quite sure I've answered your question. I feel I've given a correct answer but not a helpful answer (the above are technical reasons, not conceptual reasons), which requires insight into what you're doing. My guess is you are asking this because you're struggling with a TBX -> SpTBX migration and you want docking areas which can handle both toolbars and dockable panels. Luckily, both these lead to your second question...
"Can I place toolbars on a TSpTbxMultiDock?"
I'm stepping into remembered territory here and I can't guarantee that this part of the answer is correct, because I converted our app from TBX to SpTBX a long, long time ago.
First of all, no, you cannot place a TSpTBXToolbar on a dockable panel. Nor can you dock a TSpTBXDockablePanel on a TSpTBXDock. A toolbar can dock into a dock, and a dockable panel can dock into a multidock.
The reason for this is guesswork, but I would guess it's because of the different behaviour of the two docked controls and their purpose.
Toolbars:
Are meant to contain SpTBX items (buttons, dropdown, etc, not any normal VCL controls, buttons, etc) and can dynamically change from being toolbars to menus etc, eg as the form is resized and the toolbar shrinks.
Stack: as well as having any amount of space between them (unlike docked panels, which always sit adjacent to each other) toolbars can have space between them; they sit whereever the user put them. They also often sit in several rows, and the one dock expands and contains several rows.
Are designed to shrink: a form can get too small or a user can drag a toolbar over another, and a toolbar will show only the items it can, and either hide the others or show a chevron (a tiny >> button to open a menu containing the other items.)
Dockable panels:
Are meant to hold complex controls, including standard VCL controls - they're meant as a toolbar and docking system-compatible way to build complex docked forms. (We design ours as frames and client-align a frame to the dockable panel.)
Rarely are on several rows. This is up to you as the UI implementer, but many panels docked next to each other could rapidly fill up form space. We keep LimitToOneRow turned on for dockable panels, so they they can only fill up one dimension (ie, on the left and right side of the form, dockable panels can only sit above and below each other, not to the left and right of each other.)
Resizing behaviour is different: they are always adjacent with no gap in between (toolbars can be positioned anywhere) and usually stretch one or more of the panel to fill the whole size of the dock (toolbars don't, unless MenuBar is set (see below)) and in fact toolbars are designed to shrink, not grow, and show a chevron and a drop-down menu. Panels don't have that behaviour.
After that diversion, back to your question. When we were converting our app I remember wondering what would happen if a user wanted to create a layout such that there were toolbars, then a dockable panel, and then toolbars again, in order to get as close as possible to mixing the two types. Doing so would require a dock, then a multidock, and then a dock again, but in practice this actually leads to users being able to create quite complex and confusing layouts. One problem with dockable UIs is that many users find them confusing (honestly, you'd be surprised), and can accidentally move or dock items where they don't want them. (It's good practice to contrain the menu bar to the top, for example - don't allow that one to be moved. It's just confusing.)
Our current design is that each side of our main window has two docks: one normal dock and then next to that that (closer to the center of the form) a multidock. The resulting behaviour is that all toolbars, such as menus and normal toolbars, can only dock next to the edge of the window. Dockable panels are then closer to the inside of the form than that. Keeping pairs of docks and multidocks next to each other on each form edge lets both types of controls be docked at any side of the form, but constrains toolbars to always be on the very edge of the form. This makes UI sense, actually: dockable panels tend to contain more complex things inside them than a toolbar, and it follows that they are more central controls. Of course, if one particular edge of the form has only a dockable panel on it, not a dockable toolbar, then the toolbar dock is invisible (width or height of 0) and so the panel sits next to the edge of the window too.
Written as text, it's hard to explain the previous two paragraphs (how this scheme works, and why this is okay / good.) You need to try it in practice and have a sense for user's behaviour. But basically, I recommend having both types of docks on your form, and keep the TSpTBXMultiDocks aligned 'inside' or 'inner to' the normal toolbar docks. Hopefully this image helps.
Other
You might be interested in the following properties:
DockableTo (toolbar and dockable panel): constrains which sides (top, bottom, left, right) a toolbar or panel can dock. Use this to make some controls only able to be docked in certain specific docks - a main menu to only dock on the top, for example.
DefaultDock (again, both): when users double-click to dock, rather than drag-docking, where does it go?
CurrentDock: I think you've already found this, but set it in the designer to move a toolbar or panel between docks, and read at runtime (or, I suppose, set at runtime if you programatically want to move things to specific docks.)
DockMode: controls whether a toolbar or panel can float (be undocked and be its own window), or not, including whether it can change docks. An example of using this is the main-menu constraint again.
FixedDockSize (panels only): stops the panel being resizable, either by the user or by the docking code referred to above.
MenuBar (toolbars only): gives the toolbar slightly different behaviour, I think that it always takes up the full width of the dock and doesn't allow other toolbars to be docked next to it. It may also change how items render slightly.
How I can create a custom shape changing dialog form in Firemonkey using Delphi XE3.
I want create two layout with some components(buttons,edits) inside any one of them, and one button in the first layout with name "SHOW/HIDE MORE DETAILS ",
So this button will do:
layout2.visible:=false
With this layout2 component hide but not auto align and auto size form.
How i can doit?
Here image example about what i want to do
Look at the following code:
procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.Height:=40;
end;
Using this, when you click More (in this case the button is called Button1) the form changes its height. I set 40, but of course you can use another integer value.
Simply control the Forms Height Property to expand or contract the form. Any control that is not being painted in the client area still remains in the same position regardless.
Make sure that you disable your none-visible components when contracting the form, despite not being drawn they can still receive focus.
Please help me with my question.
I have TreeView and Frames, how can I shift them if I click on an item of TreeView?
Is it better to use PageControl (PageControl1.Pages[i].TabVisible := false;) instead of Frames or Frames fit better?
Thank you very much!
To answer your first question "how to ... using a TreeView?" : Implement the OnChange event of the TreeView. The node parameter refers to the newly selected item.
About your second question "Should I use Frames or a PageControl?" : Well, one does not exclude the other and you perfectly can use both. Indeed, I advice to do so when you use the contents of such a TabPage multiple times. In those cases, place the Frame with Align = alClient on your TabPage.
Frames are usefull to design an arbitrary reusable container. For instance: you could set the same FrameType on every Page of the PageControl, assuming they all look the same but each working with different data.
Another possible minor advantage of using frames is not to get confused about all the controls on the TabPages.
But if every TabPage is unique in terms of visual style or control layout, then it's perfectly ok to not use frames and design the pages on the PageControl directly.
And about the shifting part: I don't exactly understand what you want to accomplish by setting the visibility of a tab, but shifting to another page (depending entirely on your implementation) based on the node could be as simple as:
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
PageControl1.ActivePageIndex := Node.Index;
end;