If I am using Delphi at design time, then when I copy/paste a group of components at design time, the pasted components are slightly offset from their originals.
However, I am developing a GUI which allows users to develop a GUI for another app. When I cut/paste the pasted components lie directly over the originals. Is there are way to offset them slightly?
Since you have the basic copy/paste working, the next bit is a fairly simple extension to what you already have.
Pasting does of course - presumably - paste the copied controls with their copied property values, so yes they will over-lay on top of the original controls from which they were copied.
To get off-setting behaviour, you will have to do some "nudging" of the pasted controls after they have been pasted. If you cannot get a list of the pasted controls more directly, then one way to derive such a list would be to take a copy of the Components collection of the target form before pasting, then after pasting iterate over the Components collection once again - any item now in the collection and not in the "original" list must have been pasted and you can apply your Left/Top nudges to those as required.
list := TList.Create;
try
for i := 0 to Pred(dest.ComponentCount) do
list.Add(dest.Components[i]);
// Do your pasting
for i := 0 to Pred(dest.ComponentCount) do
if list.IndexOf(dest.Components[i]) = -1 then
// Nudge dest.Components[i]
finally
list.Free;
end;
That should get you at least heading down the right direction I think.
NOTE: The above code assumes you are dealing (potentially) with TComponent derived classes (i.e. non-visual components). If you are dealing only with TControl descendants then you could optimise by working with the ControlCount and Controls of the destination container control (not necessarily the form).
Also, the Left/Top position of a TComponent is stored in the lo/hi word of the public DesignInfo property, so your nudge code will need to take that into account and deal with TComponent derived controls differently from TControl if you are working with non-visual components as well as visual controls.
Related
I have a TStackPanel which I want to add a number of frames into at runtime. The number may vary each time the form is opened.
There seems to be limited information about TStackPanel around, and the examples I can find are in languages other than Delphi.
I have a loop that creates the frame, gives it a unique name and then adds it to the TStackPanel:
for i := 0 to 10 do
begin
mfSubFrame[i] := TMyFrame.Create(Application);
mfSubFrame.Name := name_array[i];
StackPanel1.InsertComponent(mfSubFrame[i]);
end;
This does not put anything in the stack panel. If I change the SP line to:
StackPanel1.InsertControl(mfSubFrame[i]);
then I do get a frame in the SP. It is the last one of the loop as I can tell by the name, the others may be hidden behind it but I can't tell. They are certainly not stacked horizontally like they should.
I have tried various other things like setting the parent of the frames to be the SP, and had a look at things like:
StackPanel1.Components.InsertComponent(mfSubFrame[i]);
and other sub-methods, but had no luck so far.
I suspect it may require a combination of statements, like add a control item as well as the actual component, but as I am working on the basis of trial and error it could be a long time before I stumble on the right combination.
I have never used the TStackPanel before, but it seems like you can add controls to it exactly the same way you add controls to any other windowed control: just create the control and assign its Parent.
For example,
for var i := 1 to 10 do
begin
var Memo := TMemo.Create(Self);
Memo.Parent := StackPanel1;
end;
will add ten memo controls (all owned by Self) to StackPanel1. There is no need to name the controls; referring to components by string name at runtime is an antipattern. (So is using FindComponent.)
InsertComponent() changes a component's Owner, which has no effect on visual display. You are creating each frame with Application as its Owner, and then changing its Owner to StackPanel1. You should assign the desired Owner when calling the component's constructor.
InsertControl() changes a control's Parent, which does affect visual display. You are creating each frame without a Parent, and then changing its Parent to be StackPanel1. You should be using the actual Parent property, not calling InsertControl() directly.
That being said, TStackPanel has a ControlCollection property that you are not doing anything with. That collection manages the actual stacking.
If needed 1, for each frame, try calling StackPanel1.ControlCollection.Add(), and then assigning the frame to the TStackPanelCollectionItem.Control property.
1: I don't have the source code for TStackPanel to look at, but I suspect TStackPanel probably handles this automatically for UI controls dropped onto it at design-time, but you might need to perform it manually for controls that you create dynamically at runtime. I'm not sure.
I have a project where I try to share form files between Delphi and Lazarus. This actually works well excep with GroupBox there "top" values is caluculated against two different "tops". This menas that in Lazarus the controls are moved about 20-30 pixels too far down the TGroupBox.
Anyhow, assuming I design in Delphi, and I make a descendant TGroupBox. IO could Add a property called "fixiffromdelphi" (or something like that) and then add some code which ensures the child controls get placed correctly.
Ideally, this should happen under streamning, but I am not sure how to go about it. Is there anyway the parent (GroupBox) can access the subcontrols while the form streams in the dfm/lfm? And through that override e.g. top value?
My goal is to create a simple forms editor like the one that we find on Delphi IDE.
Right now the user can select and add the components making it parent of a TPanel that is the holder of the form. For simplicity, please consider also TPanel as the visual components added to the form.
I have 2 missing parts I want to find out ideas/code to help complete:
1 - how to move the created visual component? The same effect that in IDE for moving the visual component, for example Tpanel, around, chaning its top and left position
2 - how to draw that hooks for the component with focus on the form editor
3 - how to resize using the hooks
I only want the part related to handle the visual part. I am not generating DFM or anything like that.
Simply put your moving code needs to do this:
When the mouse goes down, check if the mouse position is over a control that can be dragged. If so, then set a variable named FDragControl to refer to that control. This code lives in an OnMouseDown event handler.
When the mouse moves, if FDragControl is not nil, move the control. This code lives in an OnMouseMove event handler.
When the mouse goes up, set FDragControl to nil.
That's pretty much all there is to it. The main nuance is that you must also remember the X, Y values of the mouse when the drag commenced. So in your OnMouseDown handler you write:
FStartMousePos := Point(X, Y);
FStartDragControlPos := Point(FDragControl.Left, FDragControl.Top);
And then in the OnMouseMove your position code reads:
FDragControl.Left := FStartDragControlPos.X + (X-FStartX);
FDragControl.Top := FStartDragControlPos.Y + (Y-FStartY);
You will also need to capture the mouse when you start dragging.
The resizing code is similar. Again, you need to decide in the OnMouseDown that you are resizing rather than dragging, but the code still involves handling mouse down, move and up events.
As for painting, you need to force a repaint whenever one of your event handlers changes a property that will influence the visual appearance of your form. You can use the value of FDragControl to decide whether or not to use special drawing of your control and indicate that it is being dragged. And likewise for resizing.
I've not coded up a full working implementation since your question is high level and conceptual. The implementation is down to you.
// I have made this an answer as I have just read your latest update which really should have been made as an edit to your original question but, anyway.
You can download the Cindy Components Pack and use the cyResizer Component which will do pretty much everything you need and is very customisable as well.
You can download it from here: http://sourceforge.net/projects/tcycomponents/
Searching more for an answer I could find these articles:
How to Move and Resize Controls at Run Time
http://delphi.about.com/library/weekly/aa102505a.htm
How to Add Size Handles to Controls being Resized at Run-Time
http://delphi.about.com/library/weekly/aa110105a.htm
Pretty much with all the information to complete this task with source code example.
These articles show how to implement and use a TMover class. I have done it and work correctly.
I have also downloaded the TcyComponents Pack and used the TcyResizer. It is a full featured form editor with pretty much everything that is required for a Delphi like forms editor. I recommend. It comes with source code and works fine with XE2 version.
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;
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;