|
So how do
I put controls in my main window? I want an Edit Box on my window.
First off, you will need to #include <edit.h>
. There are several varieties of edit box. A single-line editbox
is a TLineEdit. A multi-line editbox is derived from
TLineEdit, and is a TEdit. So say you want
a nice TLineEdit, a single-line editbox. So, in our
class definition:
|
|
class TMyWin : public TWin
{
public:
TLineEdit MyEdit;
TMyWin( TWin *PParent, const TWinAttr &Changes );
};
|
And now you have a line-edit in your window. You'll need to
set its attributes, of course:
|
|
[this is my constructor...]
TMyWin::TMyWin( TWin *PParent, const TWinAttr &Changes )
: TWin( PParent, Changes ),
MyEdit( this, TWinAttr().X(100).Y(100).W(300).H(50).Text("Hi!") )
{
}
|
So there we go. This will make an edit box at 100,100 with
the text "Hi!" in it. What's that you ask? The syntax
of that thing up there? The colon followed by a comma-separated
list? Well, see, a TLineEdit doesn't have a default
constructor, so it must be explicitly constructed by its owner.
This is normal C++ behavior, but since it seems to confuse people
so much I will try to go over it some. Remember, though, this
is not EWin specific -- this is an intermediate-level C++ technique.
The colon-separated list has two purposes. First, it is used
to call the earlier class'es constructor. You only need to call
the base class constructor if it does not have a default constructor
(or you don't want to use it). Thus:
|
|
class Foo
{
public:
Foo() { }
};
class Bar : public Foo
{
public:
Bar() { }
};
|
This is fine; it doesn't have to call Foo(). It *could*,
if it wanted:
|
|
class Bar : public Foo
{
public:
Bar() : Foo() { }
};
|
This is exactly equivalent. The point is that since the base
class Foo has a default constructor (aka a constructor
without parameters), it gets called by default (automatically
by the compiler).
Now, you do not normally use the TWin's default constructor,
because you usually want to at least tell it who its parent is
and what its initial attributes are when it is constructed. So
you need to use the colon-list to call the constructor of the
class you derived from. Even if you don't care about the parameters,
you still should explicitly call your base class.
|
|
class TMyWin : public TWin
{
public:
TMyWin() : TWin(NULL, TWinAttr()) { }
};
|
Now, if you were derived from two things, you would put both
of those constructor calls after the colon, separated by a comma:
|
|
class TMyWin : public TWin, public TNostrilFlarer
{
public:
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes), TNostrilFlarer(6)
{
}
};
|
Ok? That's the first use of the colon-separated list. But
there is another use of this list: it can also be used to call
the constructor of a class'es MEMBER VARIABLES. Thus:
|
|
class Foo
{
public:
int n;
Foo(int init) : n(init) { }
};
|
Here I use the colon list to initialize n to 'init'.
This often results in slightly faster code than using the obvious
method:
|
|
class Foo
{
public:
int n;
Foo(int init) { n = init; }
};
|
But, as you can see, it was completely optional that I used
the colon-constructor syntax. This is because an int has a default
constructor that takes no parameters. By default it is initialized
to an arbitrary integer. By the same token:
|
|
class Thingy
{
public:
int a;
int b;
int c;
Thingy() { a = 1; b = 2; c = 3; }
};
class TMyWin : public TWin
{
public:
Thingy MyThingy;
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes) { }
};
|
I didn't have to call Thingy() in my colon list.
But if there wasn't a default constructor for Thingy
[a default constructor is fancy-talk for a constructor without
parameters, remember], then I would have to call it. And if Thingy
had several constructors, then I would have to explicitly call
one if I didn't want to get the default automatically.
|
|
class Thingy
{
public:
int a;
int b;
int c;
Thingy(int init) { a = init; b = init+1; c = init+2; }
};
class TMyWin : public TWin
{
public:
Thingy MyThingy;
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes), MyThingy(1) { }
};
|
Here, I had to call MyThingy(1) in my colon-separated
list, because the Thingy doesn't have a default constructor.
So you can imagine that if your class contained instances
of a bunch of other classes, and you didn't want to use their
default constructors, you would have a very long colon-separated
list. This is relatively normal in EWin programming, so don't
freak out if it happens. You will often have a TMyWin
or somesuch that owns a TLineEdit, two TStatics,
and a TBtn, and none of these objects has a default
constructor, so your colon-separated list will get rather large.
It's more efficient this way, though. I mean, I could add a default
constructor and then require that you call some new function
like TLineEdit.Init() before you actually get started
using the object, but it is slightly less efficient, and more
importantly, you would probably forget to call Init()
sometimes, and I want the library to be as foolproof as possible.
This way, the compiler will bitch if you forget to put the class
in the colon-separated list.
A few more tricky bits I should mention about the colon-separated
list. First, just because you have a list doesn't mean it's an
*ordered* list. Sometimes, it matters what order your objects
get constructed in. It's unfortunate, but it sometimes crops
up in real-world programs. Well, if you have some objects called
Thing1, Thing2, and Thing3:
|
|
class ThingHolder
{
Thing1 c;
Thing2 a;
Thing3 b;
ThingHolder() : a(5), c(4), b(c) { }
};
|
You might intuitively assume that a will get constructed,
then c, then b. But no, they will get constructed
in the order c, a, b, because THAT
IS THE ORDER IN THE CLASS DECLARATION. It uses the class declaration,
NOT the order in your constructor list. This is unfortunate,
and I wish it wasn't so, but it's just a fact of C++.
Also, this is a handy technique that lets you pass references
to classes. Say you have a reference to an int. If you change
this reference, the original int gets changed, too. Say you want
to pass this reference to your Thingy object, so that
the Thingy can change the original int also. So you
have
|
|
class Thingy
{
public:
int &referenced_int;
Thingy(int &r) : referenced_int(r) { }
};
int my_lucky_int;
Thingy(my_lucky_int); //now, if Thingy changes referenced_int,
//my_lucky_int will get changed
|
The only way to pass a reference to a class is via the colon-separated
list. This makes sense if you think about it; if in your Thingy
constructor you said
|
|
class Thingy
{
public:
int &referenced_int;
Thingy(int &r) { referenced_int = r; }
};
|
Then this would tell the compiler to assign a value to whatever
referenced_int is referring to, and not tell it to initialize
r. This is because the = sign is used to assign a value
to the reference, NOT to tell it what memory address to reference.
Having said that, I have to confess that you can create temporary
references in functions like this:
And the compiler will figure out that you don't mean an =
sign there, but instead you are trying to make ref refer to original_int.
The reason it can do that is because of all references have a
copy constructor. Hmm, never mind. Just know that you can do
this when you have references in a code body, but you can't construct
references this way if the reference is a member variable of
a class.
To repeat: if you want one of your class'es member variables
to be a reference, you must tell it what it refers to in the
class'es constructor, and you must do so using the colon list
syntax.
There, we're done. Oh damn, I was supposed to be explaining
something else, wasn't I? Let's see... oh yes, you want a TLineEdit
in your TWin. The code above should now make a bit more
sense -- in the colon-list you are calling the TLineEdit's
constructor and passing it some configuration info. Now the object
will get created and all will be well.
Now I want to create a button.
Same deal as a TLineEdit, but with different particulars:
|
|
#include <btn.h>
class TMyWin : public TWin
{
public:
TBtn Button;
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes),
Button(this, TWinAttr().X(50).Y(50).W(100).H(50).Text("Buttonical"))
{
}
};
|
You don't have to do it this way. You can also just pass the
smallest possible parameters to the Button constructor, then
manually set the stuff:
|
|
class TMyWin : public TWin
{
public:
TBtn Button;
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes),
Button(this, TWinAttr())
{
Button.Text("Buttonical");
Button.X(50);
Button.Y(50);
Button.Width(100);
Button.Height(50);
}
};
|
How
do I get a checkbox?
Well, in Windows, a checkbox is a fancy kind of button. In
order to tell it that it's a checkbox, you give it the BS_AUTOCHECKBOX
or the BS_CHECKBOX style. So, you could do this:
|
|
class TMyWin : public TWin
{
public:
TBtn Button;
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes),
Button(this, TWinAttr())
{
Button.Style(WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX);
Button.Text("Checkboxical");
Button.X(50);
Button.Y(50);
Button.Width(50);
Button.Height(50);
}
};
|
But it's kind of nasty that a button is also a checkbox, isn't
it? Similarly, a radio button and a group box are also fancy
buttons, determined by what kind of button styles the button
has. You can either just give TBtn objects the style
you want, or you can use pre-made classes that hide the fact
that checkboxes, etc., are buttons: TCheckBox and TRadioBtn.
To get or set the check in a checkbox or radio button, use
TBtn::bChecked() and TBtn::SetCheck().
(go on to the next
page in the tutorial...) |