![]() |
Languages »
C# »
Windows Forms
Intermediate
Build a Qt Like layout to automatically get controls arranged on panel resizingBy rmortega77Adding designing capabilities and visual feedback by creating a container component that behaves like Qt Layouts. Automatically adjusting size and position of contained components as the container resizes. |
C#, Windows, .NETVS.NET2003, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
While experimenting with Linux programming and the Qt designer, we found it interesting to port to C# the QLayout component which allows to create a layout container that automatically arranges its components on resizing. It is not in a stage where it can be used, and perhaps you will find it a bit difficult, but we still wanted to post it in order to get feedback and to show how to add components, design time behavior and functionality. Let us cross the Rubicon.
First, we will create our QLayout and QSpacer components. The QLayout is the container component, while the QSpacer is just a contained component. When the QSpacer appears among the QLayout contained components, it will absorb the extra space on resizing. Our intention is only to create some similar behavior rather than cloning the QLayout component.
Change the inheritance to System.Windows.Forms.Panel
public class QLayout : System.Windows.Forms.Panel
But in order to inherit from Forms.Panel we need to add System.Windows.Forms to our references:
QtLikeLayout References -> Add reference. To the using directives on top of the QLayout.cs file, add the following code:
using System.Windows.Forms;
In order to display the Horizontal and Vertical layout behavior of the QLayout component, we need to add a CmpLayout property exposed in designing mode, in the property editor. Given below is an explanation of how this can be done.
First we add a QLayoutProperty enumeration, just before the QLayout class definition:
public enum QLayoutProperty
{
Horizontal,
Vertical
}
We need a private property pHLayout to hold the value, and a public method CmpLayout to expose it to the designer property editor, so we add this code to the QLayout class properties and methods:
private QLayoutProperty pHLayout = QLayoutProperty.Horizontal;
[
Category("Layout"),
Description("Controls layout arrangement."),
DefaultValue(QLayoutProperty.Horizontal)
]
public QLayoutProperty CmpLayout
{
get
{
return pHLayout;
}
set
{
pHLayout = value;
// Rearrange components
this.OnLayout(new LayoutEventArgs(this,""));
}
}
When we add a custom property to a Control, it appears on the property editor at design time. But in the Miscellaneous section, we indicate a Category, Description and Default value Attribute to specify the category in which the property or event will be displayed in the visual designer, and the description and the default value it will get. Note that when we specify a default value, the designer does not add code to initialize the control. So we must be sure to initialize it in the code, either in the constructor or by assigning it a value in the declaration as we have done, and ensure that this value is the same we put in the DefaultValue attribute. In order to set the way controls are justified, we need to add a CtrlsDock property exposed in designing mode, in the property editor. Here is how this can be done.
Again we add a QDockProperty enumeration, just before the QLayout class definition:
public enum QDockProperty
{
Fill, // Default, the widgets fill upto container walls
Side, // Right or Top justification
Center, // Center justification
UpSide // Left or botton justifcation
}
But we also need a private property pDock to hold the value, and a public method CtrlsDock to expose it to the designer property editor, so we add this code to the QLayout class properties and methods:
private QDockProperty pDock = QDockProperty.Center;
[
Category("Layout"),
Description("Controls justification."),
DefaultValue(QDockProperty.Center)
]
public QDockProperty CtrlsDock
{
get
{
return pDock;
}
set
{
pDock = value;
// Rearrange components
this.OnLayout(new LayoutEventArgs(this,""));
}
}
Now, let us program the main behavior. We do the Layout arrangement by overriding the OnLayout Panel inherited component method:
protected override void OnLayout(LayoutEventArgs levent)
{
// We need to handle the Docking and layout changes performed
// by the base clase. Maybe we should inherit from a more generic
// control.
base.OnLayout (levent);
this.SuspendLayout();
if (pHLayout == QLayoutProperty.Horizontal)
{
// controls are horizontally arranged
int mHeight = this.Height / 2;
int ctrlCount = this.Controls.Count;
int mWidth = (ctrlCount != 0) ? this.Width / ctrlCount : 0;
int pLeft = 0;
int cntSpace = 0;
int ctrlWidths = 0;
foreach (Control ctrl in this.Controls)
{
if (ctrl.GetType().Name == "QSpacer")
cntSpace += 1;
else
ctrlWidths += ctrl.Width;
}
foreach (Control ctrl in this.Controls)
{
// there is no spacer
if (cntSpace == 0)
{
ctrl.Width = mWidth;
ctrl.Left = pLeft;
ctrlCount --;
// if ctrl does not allow resizing
pLeft += ctrl.Width;
}
else
{
if (ctrl.GetType().Name == "QSpacer")
{
ctrl.Width = (this.Width - ctrlWidths) / cntSpace;
((QSpacer)ctrl).CmpLayout = QLayoutProperty.Horizontal;
}
ctrl.Left = pLeft;
pLeft += ctrl.Width;
}
switch (this.pDock)
{
case QDockProperty.Fill:
ctrl.Top = 0;
ctrl.Height = this.Height;
break;
case QDockProperty.Side:
ctrl.Top = 0;
break;
case QDockProperty.Center:
ctrl.Top = mHeight - ctrl.Height / 2;
break;
case QDockProperty.UpSide:
ctrl.Top = this.Height - 1 - ctrl.Height;
break;
}
}
}
else
{
int ctrlCount = this.Controls.Count;
int mHeight = this.Height / (ctrlCount + 1);
int mWidth = this.Width / 2;
int pTop = 0;
int ctrlsHeight = 0;
int cntSpace = 0;
int ctrlHeights = 0;
// count spacers and meassure
foreach (Control ctrl in this.Controls)
{
if (ctrl.GetType().Name == "QSpacer")
cntSpace += 1;
else
ctrlHeights += ctrl.Height;
ctrlsHeight += ctrl.Height + 1;
}
// dump spacer high;
int sHeight = (this.Height - ctrlHeights) /
(this.Controls.Count + 1);
foreach (Control ctrl in this.Controls)
{
// there is at least one Spacer
if (cntSpace !=0)
{
if (ctrl.GetType().Name == "QSpacer")
{
// the spacer fill the space betwen
ctrl.Height = (this.Height - ctrlHeights) / cntSpace;
((QSpacer)ctrl).CmpLayout = QLayoutProperty.Vertical;
}
}
// is it just like there is one spacer between every control
else
pTop += sHeight;
switch (this.pDock)
{
case QDockProperty.Fill:
ctrl.Left = 0;
ctrl.Width = this.Width;
break;
case QDockProperty.Side:
ctrl.Left = 0;
break;
case QDockProperty.Center:
ctrl.Left = mWidth - ctrl.Width / 2;
break;
case QDockProperty.UpSide:
ctrl.Left = this.Width - 1 - ctrl.Width;
break;
}
ctrl.Top = pTop;
pTop += ctrl.Height;
}
}
this.ResumeLayout();
}
This method generates the automatic arrangement of the contained controls. In the case of Horizontal Layout, we get all components laid out horizontally one after another, and all of them absorb the width of the QLayout panel by resizing. If one or more QSpacers are found, they absorb the extra size and the components are left at the size that they are. The controls are aligned at the center or top or bottom according to the CtrlsDock justification. In the case of Vertical Layout, we get all components laid out vertically one above the other. Here, their distance is proportional to the space left, but they retain the original width. If one or more QSpacers are found, the components are aligned one above the other without any space between them, but the spaces absorb it by resizing. Now lets create the QSpacer control:
System.Windows.Forms.Control
public class QSpacer : System.Windows.Forms.Control
Add a pHLayout private property and a CmpLayout method, to the QSpacer class methods and properties:
private QLayoutProperty pHLayout = QLayoutProperty.Horizontal;
[
Category("Layout"),
Description("Spacer layout arrangement."),
DefaultValue(QLayoutProperty.Horizontal)
]
public QLayoutProperty CmpLayout
{
get
{
return pHLayout;
}
set
{
if ((pHLayout != value) && (DesignMode))
{
pHLayout = value;
//
if (pHLayout != QLayoutProperty.Horizontal)
this.Width = 23;
else
this.Height = 23;
//
this.Invalidate();
}
}
}
The controls are usable as they are, but we want to go a step further and give the user feedback as well as more posibilities at design time.
QSpacerComponentDesigner Control Designer for extending the design mode behavior of our QSpacer control. We will use it to draw it like a spring just as the Qt Designer does at design time.
Change the inheritance to System.Windows.Forms.Panel:
public class QSpacerComponentDesigner : System.Windows.Forms.Design.ControlDesigner
We need to add System.Design.dll and System.Drawing.dll to our references:
QtLikeLayout References -> Add reference. To the using directives on top of the QSpacerComponentDesigner.cs file, add the following code:
using System.Windows.Forms;
using System.Drawing;
Now we will override the WndProc for handling the resizing and OnPaintAdornments method in order to draw a spring like appearance on designing. Double click the startButton button. Replace the startButton_Click method with:
// repaint control on Resize
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
// see Winuser.h for other message const
// int WM_MOVE = 0x0003;
const int WM_SIZE = 0x0005;
//
if (m.Msg == WM_SIZE)
this.Control.Invalidate();
}
// Occurs after the designed Control has painted itself
protected override void OnPaintAdornments(PaintEventArgs pe)
{
base.OnPaintAdornments (pe);
int bias = 3;
Point[] lines = new Point[10];
if (((QSpacer)this.Control).CmpLayout == QLayoutProperty.Horizontal)
{
int wInc = this.Control.Width / (lines.Length-1);
int wX = 0;
for(int i=0; i<lines.Length; i++)
{
lines[i].X = wX;
wX += wInc;
lines[i].Y = (i % 2 == 0 ? -1 : 1) *
(this.Control.Height / bias) + (this.Control.Height / 2);
}
}
else
{
int hInc = this.Control.Height / (lines.Length-1);
int hY = 0;
for(int i=0; i<lines.Length; i++)
{
lines[i].Y = hY;
hY += hInc;
lines[i].X = (i % 2 == 0 ? -1 : 1) *
(this.Control.Width / bias) + (this.Control.Width / 2);
}
}
pe.Graphics.DrawLines(Pens.Blue,lines);
}
}
Now add the Designer Attribute on top of the QSpacer class decaration:
// Associates the designer class QSpacerComponentDesigner
// with QSpacer control.
[DesignerAttribute(typeof(QSpacerComponentDesigner), typeof(IDesigner))]
public class QSpacer : System.Windows.Forms.Control
Also add the System.ComponentModel.Design namespace to the using directives of QSpacer.cs file:
using System.ComponentModel.Design;
Push (F7)
Let's test the newly created QLayout and QSpacer controls.
QLayout control into the form. Right click the qlayout1 control -> Properties. Set the Dock property to Bottom. Toolbox, drag another QLayout control into the form. Right click the qlayout2 control -> Properties. Set the CmpLayout property to Vertical; the CtrlsDock to Fill, and the Dock property to Left. QLayout control into the form. Right click the qlayout3 control -> Properties. Set the CmpLayout property to Vertical; the CtrlsDock to Fill, and the Dock property to Fill. QSpacer and five label controls into the qLayout1 layout (left). QSpacer and five text boxes into the qLayout2 layout (right). qLayout2 (Left) layout from the controls drop down list. Set With property to 90. QLayouts and controls. You can arrange the controls using the bring to front, and set to back verbs of controls. In later article updates and hopefully with your feedback, we will add a QLayoutComponentDesigner control designer that will be more helpful at design time.
| You must Sign In to use this message board. | ||||||||
|
||||||||
|
||||||||
|
||||||||
|
||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 10 Mar 2007 Editor: Deeksha Shenoy |
Copyright 2007 by rmortega77 Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |