Chapter Three :- Common Widgets
KDevelop comes with a built in interface designer which would be better known to Windows programmer as a form designer. This is used for developing dialog boxes and small applications and is where we will be spending most of our time for the next few chapters. If you start the project chapterthreecommonwidgets in the same way as described in earlier chapters or open the sample project provided then open up the Automake Manager and click on the ui file, then the interface designer will open automatically and will look like,
In the centre of the interface designer you will see the form or dialog that we are working with. This at the start of the development will be the main form for your application, to the left you will see the Toolbox widget that lists all the available widgets that you can use in the application and to the right you will see three boxes containing information. The first box contains project information and for our purposes can just be closed to make room for the things that we are going to use. The second box contains information about the widgets or objects that are placed on the form, this has two tabs the first listing all the objects or widgets that are placed on the form
which for the standard default project is just a label and a button and the tab for members,
which lists all the members associated with the form. We can see from the image that there is one slot which responds to the button being clicked but you may notice that there is no corresponding signal. This is because the signal is sent by the button and not the form, the form merely receives the signal it doesn't send it.
The third box is the properties box which should be familiar to anyone who has used a form designer in any environment before. The main box lists all the publicly available options that the designer of the project can adjust on the form,
These properties are available for all widgets that appear on the form and the form itself. The Signal Handlers tab shows all the signals that the currently selected widget can output and what function if any we are using to handle the signal.
Once again I've selected the button on the form and we can see that we are handling the clicked() signal with the button_clicked() slot.
Layouts
The Qt library has a built in concept of Layouts, by this they mean the way that the widgets contained in a form are displayed when the size of the form is changed. If you build and run the default project created by KDevelop and then expand the window either by clicking on the resize button on the top border or by selecting and edge and expanding it manually then you will see that the widgets in the form maintain their relative positions automatically. This is because they are maintained in what is called a GridLayout. In a previous section I put off talking about layouts until they were necessary and now they are necessary. If you look in the generated header file for this ui file. ( Note. It wont be there if you are just looking at the demo code and haven't built the project. ) For the current project it is the chapterthreecommonwidgetswidgetsbase.h file in the debug/src folder off the project folder.
The header file contains the following forward class declarations,
class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QSpacerItem; class QPushButton; class QLabel;
We already know that the QPushButton class and the QLabel class are used in the project. The other forward declarations are for the layout of the form. QVBoxLayout is the class that controls the vertical layout, QHBoxLayout controls the horizontal layout while QGridLayout imposes a table style layout that controls both vertical and horizontal positions. With the QSpacerItem being included to allow the designer of the form to add spacings between widgets as they see fit.
The type of Layout class that we are using is declared in as,
protected: QGridLayout* chapterthreecommonwidgetswidgetbaseLayout;
and in the constructor we get
chapterthreecommonwidgetswidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, "chapterthreecommonwidgetswidgetbaseLayout"); button = new QPushButton( this, "button" ); chapterthreecommonwidgetswidgetbaseLayout->addWidget( button, 1, 0 ); label = new QLabel( this, "label" ); chapterthreecommonwidgetswidgetbaseLayout->addWidget( label, 0, 0 );
The first line creates the GridLayout object by passing it the required parameters. The first parameter being this which is the parent widget for the layout. You can also pass a QLayout pointer as the first parameter if you are using layers of layouts. The second and third parameters are the rows and columns, in that order, as the GridLayout class divides the form into the specified number of rows and columns. The fourth parameter is the margin between the widget and the sides of the parent or form and the fifth parameter is the spacing between the widgets.
The rest of the code in the example creates and places the widgets with the label being in the top left row 0 column 0 and the button being in the bottom left row 1 column 0. Of course to see how something works properly we are going to have to break it. Right click on the form,
and select Break Layout. You will now find that if you select the edge of the form and resize it then the controls do not automatically resize. If you decide to try the demo in this state you will find that the controls on the form don't respond to any size changes made to the form. In fact you are well on your way to creating your first completely unusable project. So we'd better have a look at fixing it. to start with you can give yourself all the space you need to develop your forms, so please design them with usability in mind and don't try to cram everything into an impossibly small area. You can have a form that looks like,
To correct this,
simply choose Adjust Size
and the form is automatically resized around the controls. the problem still remains however that there is no layout class controlling the form so things are still wrong. To add a layout we need to right click on a section of form and not on a control,
If we select Lay Out Horizontally we get,
which is just plain wrong, so we'll try vertical,
This does actually work although only because we are using such a simple dialog. If we wanted to half the size of the label and add another widget then things would fail in the same way they did with the horizontal layout although it is worth bearing in mind that if all your controls are all lined up either horizontally or vertically you can use the horizontal or vertical layout options safely but if things do start to break then go for the grid layout.
Problems Building Forms
When I moved on from showing how the layouts worked and started to look at how actual common widgets that this chapter is named for, I deleted the label and added two group boxes one for check boxes and one for radio buttons like so.
on saving the file and trying to build the project the compiler complained that it couldn't find the declarations for the new widgets, a little investigation revealed that the chapterthreecommonwidgetswidgetbase.h file contained.
public: chapterthreecommonwidgetsWidgetBase( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~chapterthreecommonwidgetsWidgetBase(); QLabel* label; QPushButton* button;
while the chapterthreecommonwidgetswidgetbase.h~ file contains,
public: chapterthreecommonwidgetsWidgetBase( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~chapterthreecommonwidgetsWidgetBase(); QPushButton* button; QButtonGroup* buttonGroup1; QCheckBox* checkBox1; QCheckBox* checkBox3; QCheckBox* checkBox2; QButtonGroup* buttonGroup2; QRadioButton* radioButton1; QRadioButton* radioButton2; QRadioButton* radioButton3;
There is obviously some confusion going on here. Fortunately there is an easy way to solve this go to the debug/src folder and delete all the files there. Then return to KDevelop and go to Build/Run automake & friends. When that has finished go to Build/Run Configure. If the program refuses to work open the widget.cpp file in your project in this case it's the chapterthreecommonwidgetswidget.cpp and remove the references to the label in the button_clicked function.
Also now is probably a good time for a reminder that building a project does not automatically save the ui file which means that if you have made any changes to the form they will not be reflected in the build until you save the form file.
Another problem that can arise is that you occasionally lose the sizing of the form so that it displays like this,
The way to fix this is to make sure that you have a layout on the form, if this alone doesn't clear the problem up then select Adjust Size from the right click menu and you should get this,
It should be noted that this doesn't work with frames. For some reason when you click on adjust size with frames it ignores the size of the frame so the best bet is to use a Group Box which works the same as a Button Group and just delete the title, same effect, easier life. But more on that when we get to containers.
Common Widgets
Now we'll take a look at the common widgets that are provided by the form designer, rather than try to fit everything on a single form I have arbitrarily grouped them into individual projects. The first project looks at the buttons and the button groups.
Buttons
The chapterthreecommonwidgetsbuttons project demonstrates the use of buttons and button groups, it is set up exactly the same as all the previous projects described earlier, being a Simple Designer Based KDE Application.
The above image is the creation phase of the form in the designer, the QButtonGroups are placed first and the QCheckBoxes and QRadioButtons are placed on top of them, with the details being edited in the properties box. Even if you have never seen a properties editor before the names should be pretty straight forward and if there is any doubt about them then you can do a search in the KDevelop documentation, though a quick hint is that most of them can be found in the QWidget documentation.
The application shows two QButtonGroup objects each containing three objects that represent options within the program. At the bottom of the form is the standard generated button that we have seen before. The Form is set to a grid layout with the QButtonGroups being set with a vertical layout. In the generated code this looks like,
chapterthreecommonwidgetsbuttonswidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, "chapterthreecommonwidgetsbuttonswidgetbaseLayout")
...
buttonGroup4Layout = new QVBoxLayout( buttonGroup4->layout() ); buttonGroup4Layout->setAlignment( Qt::AlignTop );
...
chapterthreecommonwidgetsbuttonswidgetbaseLayout->addWidget( buttonGroup4, 0, 0 );
...
buttonGroup3Layout = new QVBoxLayout( buttonGroup3->layout() ); buttonGroup3Layout->setAlignment( Qt::AlignTop );
...
chapterthreecommonwidgetsbuttonswidgetbaseLayout->addWidget( buttonGroup3, 0, 1 );
with each vertical layout being created to hold either the QCheckboxes or the QRadioButtons and then the QButtonGroup containing the vertical layout is added to the grid layout. The effect of this is if you create or just run the chapterthreecommonwidgetsbuttons application and then expand it to fill the screen the QCheckBoxes and the QRadioButtons maintain there relationship to each other rather than spacing out evenly across the larger surface area of the form.
This of course is just a demonstration to show that you can have multiple layers of layouts within the form with each controlling a seperate given area. In this case if you didn't add the extra layouts the code would respond in the same way. As the original code without the extra layers uses the setGeometry function to position the QCheckBoxes and QRadioButtons and these are not changed when the form is maximised.
buttonGroup3 = new QButtonGroup( this, "buttonGroup3" ); radioButton4 = new QRadioButton( buttonGroup3, "radioButton4" ); radioButton4->setGeometry( QRect( 11, 29, 145, 28 ) ); radioButton4->setChecked( TRUE ); radioButton5 = new QRadioButton( buttonGroup3, "radioButton5" ); radioButton5->setGeometry( QRect( 11, 63, 145, 28 ) ); radioButton6 = new QRadioButton( buttonGroup3, "radioButton6" ); radioButton6->setGeometry( QRect( 11, 97, 145, 28 ) ); chapterthreecommonwidgetsbuttonswidgetbaseLayout->addWidget( buttonGroup3, 0, 1 );
As you can see from the code above the QButtonGroup is created and then the QRadioButtons are created as members of the button group with their positioning being set by the setGeometry function. The QButtonGroup controls the layout of the buttons that are part of it's group. Setting the QButtonGroup to a vertical layout helps the designer of the form but they also have another advantage and that is if you run the program and then Option Two followed by Option Three in the QCheckBox Group and then Left Click Option Two and Option Three in the Radio Button Group, you'll get a form that looks like this.
QCheckBoxes work as either on or of options so when you click down the row all the boxes become checked whereas with the Radio Buttons only one button within a QButtonGroup will be checked at any one time so the selected option within the QRadioButton group moves down as you do with the mouse clicking them.
The usual way to check if either a Check Box or Radio Button option is selected is to check before the beginning of an operation by calling the isChecked function for both QCheckBox and QRadioButton as I have done in button_clicked function when you left click on the “Click Me!” button,
... QString strRadioButtonTwo = i18n( "Radio Button Option Two is " ); QString strRadioButtonThree = i18n( "Radio Button Option Three is " ); QString strTitle = i18n( "Buttons Demo" ); QString strChecked = i18n( "checked\n" ); QString strNotChecked = i18n( "not checked\n" ); ... strDisplay.append( strRadioButtonThree ); if( radioButtonOptionThree->isChecked() == true ) { strDisplay.append( strChecked ); } else strDisplay.append( strNotChecked ); QMessageBox::information( this, strTitle, strDisplay );
The code simply goes through all the Check Boxes and Radio Buttons calling the isChecked() function and then builds a string to report the results.
Of course if this was a real program the code would execute depending on what options were selected rather than just displaying a message box.
Tip Of The Day
When you are building the files and KDevelop generates the “projectname”widgetbase.h and the “projectname”widgetbase.cpp file along with the “projectname”widgetbase.moc files it includes all the header files that you need in your class that is derived from “projectname”widgetbase usually called “projectname”widget but it doesn't add the include files for the widgets in the “projectname”widget.cpp file so open the “projectname”widgetbase.cpp file and copy and paste the headers to the “projectname”widget.cpp file.
For example in the current project the generated file in debug/src called chapterthreecommonwidgetsbuttonswidgetbase.cpp has the includes,
#include <qvariant.h> #include <qpushbutton.h> #include <qbuttongroup.h> #include <qcheckbox.h> #include <qradiobutton.h> #include <qlayout.h> #include <qtooltip.h> #include <qwhatsthis.h>
while the chapterthreecommonwidgetsbuttonswidget.cpp file has the include files
#include <qlabel.h>
So the includes from the previous file should be copied to this file and everything should compile as expected.
Boxes
The ChapterThreeCommonWidgetsBoxes program looks at using the standard ComboBox and the standard ListBox provided by the Qt library as well as looking at the SpinBox.
As you can see from the designer shot above the idea is that we have a simple design that we have a QComboBox and a QListBox on the same form and we copy items between them using the buttons provided while the QSpinBox's provide limits to the number of items allowed in both the Listbox and the QComboBox. The idea being to show examples of how things actually work rather than talking about how they should work.
First of all we need to add some items to the QListBox and the QComboBox we do this in the designer by right clicking on the widget and selecting edit.
which brings up a dialog which we can enter data into.
As you can see we can add either Pixmap's which means any image type that the QPixmap class can open or simple text items. For this demonstration we will be sticking to simple text items, simply because it is 1, easier and 2, I have no artistic talent whatever. Adding items to the QComboBox is done through exactly the same dialog. So let's add a few items.
The items added are just some games that are usually lying around on my desk but for the purposes of this demo you could use any old text strings as we are only going to be moving them about it doesn't really matter what they say.
To get the program to work we are going to need to add a couple of signal handlers to the buttons.
This is done by opening up the Signal Handlers tab on the Property Editor and right clicking on the clicked() signal. When you select the New Signal Handler the Editor will automatically add a signal called “buttonname”_clicked()
If you hit return when you add the signal handler you should be given a class dialog
when you get this you have the option to create a new class to handle the implentation of the slot. You are free to do this if you can think of a good reason why you need to do it but for the most part you should use the class that ends with Widget as this is already derived from the generated class in you debug/src directory. Once you have set this up KDevelop will automatically add any new Signal Handlers to the class chosen. So with this project you will end up with a chapterthreecommonwidgetsboxeswidget.h that contains
public slots: /*$PUBLIC_SLOTS$*/ virtual void addToComboBoxButton_clicked(); virtual void addToListBoxButton_clicked(); virtual void numberOfItemsInListBox_valueChanged(int); virtual void numberOfItemsInComboBox_valueChanged(int);
with the corresponding function outlines in the .cpp file
void chapterthreecommonwidgetsboxesWidget::numberOfItemsInListBox_valueChanged(int) { }
Of course if while this code will compile perfectly, if you want to access the variables passed in the valueChanged functions you will need to add a variable name for the int to both the .h and the .cpp file.
virtual void numberOfItemsInComboBox_valueChanged(int value); void chapterthreecommonwidgetsboxesWidget::numberOfItemsInListBox_valueChanged(int value)
It is however very easy to miss or dismiss this dialog and you could find yourself having to implement the connections by hand. If dismiss the dialog and try to run the program what you get when you press the “Add To List Box” button is,
which is fair enough as we haven't done anything more than add the signal handlers at this moment in time. If you looked at the source code expectiong to find that the error message was generated by chapterthreecommonwidgetsboxeswidget class but it isn't because by missing the dialog we haven't set up the connection between the two. If we go to the debug/src folder for the project and look in the chapterthreecommonwidgetsboxeswidgetbase.h file we see,
public slots: virtual void button_clicked(); virtual void addToComboBoxButton_clicked(); virtual void addToListBoxButton_clicked();
but if we look at our chapterthreecommonwidgetsboxeswidget.h file,
public slots: /*$PUBLIC_SLOTS$*/ virtual void button_clicked();
which means that if we want to do anything with the slots that we have just added then we need to override them from the base class by hand.
public slots: /*$PUBLIC_SLOTS$*/ virtual void addToComboBoxButton_clicked(); virtual void addToListBoxButton_clicked();
While we are there we might as well remove the now useless button_clicked() slot. And in the cpp file, add our own qDebug messages to make sure that we are getting the results we expect.
void chapterthreecommonwidgetsboxesWidget::addToListBoxButton_clicked() { qDebug( "Widget Implementation Add To List Box Not Implemented Yet" ); } void chapterthreecommonwidgetsboxesWidget::addToComboBoxButton_clicked() { qDebug( "Widget Implementation Add To Combo Box Not Implemented Yet" ); }
which gives us,
Now we can add the code to move items between the two boxes
if( demoListBox->count() < numberOfItemsInListBox->value() ) { demoListBox->insertItem( demoComboBox->currentText() ); } else QMessageBox::information( this, "Boxes Demo", "Maximum Items Allowed in ListBox" );
As you can see the code is very simple when the slot for addToListBox is called the code checks the number of items in the QListBox against the number of items in the QSpinBox that sets how many items can be in the list box. If there are less items in the QListBox than the value in the SpinBox then the currently selected item text from the QComboBox is added to the list box. If not a dialog is displayed.
The code for the QSpinBox is equally simple,
if( demoListBox->count() > numberOfItemsInListBox->value() ) { demoListBox->removeItem( demoListBox->count()-1 ); }
as long as you take into account the 0 based reference for C++ arrays even though the count value starts at 1 the final item in the QListBox or QComboBox is count -1 Although if you add the name of the integer variable passed to your function you don't need this,
if( demoListBox->count() > value ) { demoListBox->removeItem( value ); }
Spacers
You can manually layout the form exactly the way that you want it through the use of spacers. which are the
Common Widgets tab. You insert the spacer between the two widgets that you want it to control.
You then select the group of items by drawing an outline around them with the mouse,
Once you let go of the mouse the entire group of widgets will be selected.
Now right click on the group and select the layout from the popup menu, in this case the horizontal layout and the group will be enclosed in a red square,
like so. You can also use then select groups and add layouts to them,
Right click on these and select a vertical layout.
If we go through the whole dialog we get something like this,
which will give us a running dialog of,
More On Layouts
It should be obvious from the layout of the chapterthreecommonwidgetboxes program that it does not conviently fall into any of the available layout patterns, which means that if you try imposing any of the layouts onto the form then it messes things up quite nastily, because the controls are not a uniform size. Spacers can be one solution to this but they can also be finicky and have a tendancy to not always do what you think they are going to the first time round, which means when using them you always keep one eye on the undo button.
There are a couple of ways to deal with this the first is with Containers which we haven't covered yet and the second is to manually take control of the dialog sizing.
You can do this by going to src and opening the .ui file for the project with KXML Editor,
At the very top of the file you can see the layout properties for the form you are working on. The rect property shows the x and y screen coordinates for where the form will be displayed when the program is started and the width and height of the form. If you copy these values to the form properties, minimum size,
then the form will not be resized so that you can't see anything when you run the program in fact if right click the form and select Adjust Size it will not resize the form below the minimum size that you have specified.
There is also the option to change the sizePolicy of the dialog in the properties which allows you to set a number of policies for the resizing of the dialog including a fixed size. This brings us to the universal problem with layouts on any system and that is simply put that by saying do it in any one particular way I would expect to be inundated with emails saying “I used the dialog layout method you said to use and my dialog looks rubbish.” Basically, on any system it is always going to be trial and error to see which technique suits both the user and their project.
Edits
Create a new Simple KDE Designer application as described in earlier chapters and then delete the label and the button and save the ui file. Open the chapterthreecommonwidgetsedits .h and .cpp files and remove the button_clicked slot. Save the .h and .cpp files, it might be a good idea to build the project here just to make sure you have gotten rid of all the references, as we don't want anything unexpected popping up later. Then add a QLineEdit, a QButton and a QTextEdit and arrange them something like this,
This project demonstrates the QLineEdit and the QTextEdit in a rather simple manner, as they behave in exactly the way you would expect them to in that the QLineEdit allows you to type in a single line of text and the QEditBox allows you to enter multiple lines of text.
The signal for the button is added through the properties tab Signal Handlers tab as described previously by selecting the button, then right clicking the clicked option and hitting return to bring up the the dialog that allows us to select the class to implement the slot. As recommended we are handling the slot in the projects widget class that is derived from the class generated by the Form Designer.
void chapterthreecommonwidgetseditsWidget::addToEditBox_clicked() { QString strText = lineEdit->text(); if( strText.isNull() == false && strText.isEmpty() == false ) { editBox->append( strText ); } }
The code isn't doing anything fancy at all. When the button is clicked we get the current text from the QLineEdit and test to see if it is valid as text. You could add a QValidator at this point if you required a specifc text format, for our purposes though as long as the QLineEdit contains any text we append it to the editBox.
The Q*Edit classes contain all the functionality that you would require from a standard edit box in a windowed environment by default,
As you can see all the copy, cut and past functionality you would expect is already built into the widget.
Tab Order
When using applications, especially ones where they are filling out forms people like to be able to use the Tab key to move around the form. You can specify the tab order of you application by clicking on the,
button, which should be on the KDevelop tool bar, Alternatively it is shown on the Tools menu and is accessible by pressing F4.
The widgets that you can tab to will be given a blue circled number as shown above and if you click on the circled one and then on the circled two the numbers and the tab order will be changed.
Summary
In this chapter we have looked at the common widgets provided by the KDevelop environment most of which will form the basic makeup of any application that runs in a windowed environment. We can now put together a basic application and lay it out the way we want it using a number of different layout methods and we have seen how to make the widgets on the form interact with each other through the use of the signals and slots mechanism.