TabPages with close buttons on the tab? Anyone?

  • Thread starter Thread starter Uri Dor
  • Start date Start date
U

Uri Dor

Hi,

Maybe it's just me, but I think my application needs TabPages which can
each be closed by clicking a "X" (close) button on the tab itself. The
thing is, I couldn't find such a TabPage - not by tweaking the tabpage
styles and not by looking for a 3rd part library.
Does anyone know of such a component? Or have an idea on how to write
one (extending TabControl?)

thx
Uri
 
Uri,
Do you mean you have a tab control with 5 pages. There is a button on each
page that when clicked that page closes (disappears)?

I would inherit from TabPage and put a button on it. The click event of the
button would remove the tab page from the parents (tabcontrol) tabpages
collection, as the TabPage.Visible property doesn't actually do anything.

Hope this helps
Jay
 
Uri,
I should add, that you may need to derive from TabControl, to "shadow" the
TabPages collection to give a custom CollectionEditor to support adding your
new version of tab control to the collection via the designer...

Hope this helps
Jay
 
Thanks for the input, Jay.
I can't seem to put a button on the tab - I mean the actual tab, not the
page attached to it. To make myself clear: If there are 5 pages on a tab
control, I want to see 5 close buttons.
I'm currently toying with code like this, although I really don't want
to (for obvious reasons):

public class Class1 : TabPage
{
protected override void InitLayout()
{
base.InitLayout ();
Button b = new Button();
b.Text = "a";
b.Location = new Point(15,15);
this.Parent.Parent.Controls.Add(b);
b.BringToFront();
}
}

I also tried handling OnClick, assuming I could somehow draw over the
tab (GDI+), but that event is only triggered when the page, not the tab,
is clicked.
 
Uri,
If you want the button on the tab itself, then you probably will need to use
the 'owner draw' facility. Override the OnDrawItem method or handle the
DrawItem event.

You can use the System.Windows.Forms.ControlPaint to actually draw a button,
there are different methods to draw different kinds of buttons.

However I do not have a clear example of using Owner Draw with a tab
control.

Hope this helps
Jay
 
I've played around with OwnerDrawFixed and it works. The problem is I
want my code to run after the original DrawItem, not instead of it.
Otherwise I have to deal with text alignment, rotation, yuck.

Below is what I have so far. I still need to rotate the text on
left-aligned tabs by 180 degrees and don't know how to do that.


public class myTC: TabControl
{
public myTC()
{
DrawMode = TabDrawMode.OwnerDrawFixed;
DrawItem += new DrawItemEventHandler(myTC_DrawItem);
MouseUp += new MouseEventHandler(myTC_MouseUp);
Padding += new Size(10,0);
}

private Rectangle buttonRect(Rectangle r)
{
switch(Alignment)
{
case(TabAlignment.Top):
case(TabAlignment.Bottom):
return new Rectangle(r.X + r.Width-12, r.Y + 5, 10, 10);
case(TabAlignment.Right):
case(TabAlignment.Left):
return new Rectangle(r.X + 5, r.Y + r.Height-12, 10, 10);
default:
System.Diagnostics.Debug.Fail("bad TabAlignment");
return new Rectangle(-1,-1,-1,-1);
}
}

private void myTC_DrawItem(object sender, DrawItemEventArgs e)
{
int index = e.Index;
TabPage tp = TabPages[index];
Rectangle r = GetTabRect(index);
StringFormat sf = StringFormat.GenericDefault;
if(Alignment == TabAlignment.Right || Alignment == TabAlignment.Left)
sf.FormatFlags |= StringFormatFlags.DirectionVertical;
e.Graphics.DrawString(tp.Text, Font, new SolidBrush(tp.ForeColor), r,
sf);
ControlPaint.DrawButton(e.Graphics, buttonRect(r), ButtonState.Pushed);
}

private void myTC_MouseUp(object sender, MouseEventArgs e)
{
for(int p=0; p<TabPages.Count; p++)
{
Rectangle r = GetTabRect(p);
if(buttonRect(r).Contains(e.X, e.Y))
TabPages.RemoveAt(p);
}
}
}
 
Uri,
I've played around with OwnerDrawFixed and it works. The problem is I
want my code to run after the original DrawItem, not instead of it.
Otherwise I have to deal with text alignment, rotation, yuck.
Unfortunately that is a side effect of owner draw. Its all or nothing.
Below is what I have so far. I still need to rotate the text on
left-aligned tabs by 180 degrees and don't know how to do that.
180 degrees? that will make the text upside down? Did you mean 90 degrees?

To rotate the text you need to use the Graphics.RotateTransform method (on
the e.Graphics object). You will also need to use the
Graphics.TranslateTransform to move the origin as rotating will rotate the
drawn text around the origin. In other words with 180 degree rotation what
was drawn down & right of the origin will now be up & left of the origin,
seeing as the origin is in the upper left the text will 'disappear'. This is
compounded by the fact you are not drawing at the origin (r.Position is not
(0,0))

The problem is going to be getting the correct rotates & translates to get
your text to appear. I do not have a good example of doing this.

For 180. Try translating transforming your origin so it is at the bottom
right corner of your r, something like:

e.Graphics.TranslateTransform(r.Right, r.Bottom);
e.Graphics.RotateTransform(180);

For 90, try something like:

e.Graphics.TranslateTransform(r.X + r.Height, r.Y);
e.Graphics.RotateTransform(90);

Then when you draw the text use an origin of (0,0).

r.X = 0;
r.Y = 0;
// may need to exchange r.Width with r.Height
e.Graphics.DrawString(tp.Text, Font,
new SolidBrush(tp.ForeColor), r, sf);

The above is 'from memory' I did not try it to verify the numbers.

Charles Petzold's book "Programming Microsoft Windows with Microsoft Visual
Basic .NET" from MS Press has examples & explanations of using
RotateTransform & TranslateTransform.

I'm not sure if this GDI+ FAQ would help at all:
http://www.bobpowell.net/gdiplus_faq.htm

He has an article on drawing rotated & inverted text.

Hope this helps
Jay


Uri Dor said:
I've played around with OwnerDrawFixed and it works. The problem is I
want my code to run after the original DrawItem, not instead of it.
Otherwise I have to deal with text alignment, rotation, yuck.

Below is what I have so far. I still need to rotate the text on
left-aligned tabs by 180 degrees and don't know how to do that.


public class myTC: TabControl
{
public myTC()
{
DrawMode = TabDrawMode.OwnerDrawFixed;
DrawItem += new DrawItemEventHandler(myTC_DrawItem);
MouseUp += new MouseEventHandler(myTC_MouseUp);
Padding += new Size(10,0);
}

private Rectangle buttonRect(Rectangle r)
{
switch(Alignment)
{
case(TabAlignment.Top):
case(TabAlignment.Bottom):
return new Rectangle(r.X + r.Width-12, r.Y + 5, 10, 10);
case(TabAlignment.Right):
case(TabAlignment.Left):
return new Rectangle(r.X + 5, r.Y + r.Height-12, 10, 10);
default:
System.Diagnostics.Debug.Fail("bad TabAlignment");
return new Rectangle(-1,-1,-1,-1);
}
}

private void myTC_DrawItem(object sender, DrawItemEventArgs e)
{
int index = e.Index;
TabPage tp = TabPages[index];
Rectangle r = GetTabRect(index);
StringFormat sf = StringFormat.GenericDefault;
if(Alignment == TabAlignment.Right || Alignment == TabAlignment.Left)
sf.FormatFlags |= StringFormatFlags.DirectionVertical;
e.Graphics.DrawString(tp.Text, Font, new SolidBrush(tp.ForeColor), r,
sf);
ControlPaint.DrawButton(e.Graphics, buttonRect(r), ButtonState.Pushed);
}

private void myTC_MouseUp(object sender, MouseEventArgs e)
{
for(int p=0; p<TabPages.Count; p++)
{
Rectangle r = GetTabRect(p);
if(buttonRect(r).Contains(e.X, e.Y))
TabPages.RemoveAt(p);
}
}
}

Uri,
If you want the button on the tab itself, then you probably will need to use
the 'owner draw' facility. Override the OnDrawItem method or handle the
DrawItem event.

You can use the System.Windows.Forms.ControlPaint to actually draw a button,
there are different methods to draw different kinds of buttons.

However I do not have a clear example of using Owner Draw with a tab
control.

Hope this helps
Jay

Thanks for the input, Jay.
I can't seem to put a button on the tab - I mean the actual tab, not the
page attached to it. To make myself clear: If there are 5 pages on a tab
control, I want to see 5 close buttons.
I'm currently toying with code like this, although I really don't want
to (for obvious reasons):

public class Class1 : TabPage
{
protected override void InitLayout()
{
base.InitLayout ();
Button b = new Button();
b.Text = "a";
b.Location = new Point(15,15);
this.Parent.Parent.Controls.Add(b);
b.BringToFront();
}
}

I also tried handling OnClick, assuming I could somehow draw over the
tab (GDI+), but that event is only triggered when the page, not the tab,
is clicked.


Jay B. Harlow [MVP - Outlook] wrote:

Uri,
Do you mean you have a tab control with 5 pages. There is a button on
each

page that when clicked that page closes (disappears)?

I would inherit from TabPage and put a button on it. The click event of
the

button would remove the tab page from the parents (tabcontrol) tabpages
collection, as the TabPage.Visible property doesn't actually do
anything.

Hope this helps
Jay



Hi,

Maybe it's just me, but I think my application needs TabPages which can
each be closed by clicking a "X" (close) button on the tab itself. The
thing is, I couldn't find such a TabPage - not by tweaking the tabpage
styles and not by looking for a 3rd part library.
Does anyone know of such a component? Or have an idea on how to write
one (extending TabControl?)

thx
Uri
 
Back
Top