I am trying to add close button or 'X' on tabpage panel in tabcontrol and I have successfully done this by reading this stackoverflow question.
The problem is the tabbed page title and X sign is merged together. I discovered that the tabpage title panel is not resizing according to title text.
Here is the code:
//This code will render a "x" mark at the end of the Tab caption.
e.Graphics.DrawString("X", e.Font, Brushes.Black, e.Bounds.Right + 15, e.Bounds.Top + 4);
e.Graphics.DrawString(this.tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left+5, e.Bounds.Top + 4);
e.DrawFocusRectangle();
The result which comes is here I have changed e.bounds.right value but still it's not working.
To fix merged tabPage.Text with additionally drawed "X" just add:
tabControl.Padding = new System.Drawing.Point(21, 3);
It will add some extra space to the end of every tabPage
Make sure you set the DrawMode property of the Tab Control to OwnerDrawFixed. This property is decides whether system or developer painting the captions.
Here is code example that uses TabSizeMode = Fixed is setting the tab size:
public partial class Form1 : Form
{
const int LEADING_SPACE = 12;
const int CLOSE_SPACE = 15;
const int CLOSE_AREA = 15;
public Form1()
{
InitializeComponent();
}
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
//This code will render a "x" mark at the end of the Tab caption.
e.Graphics.DrawString("x", e.Font, Brushes.Black, e.Bounds.Right - CLOSE_AREA, e.Bounds.Top + 4);
e.Graphics.DrawString(this.tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + LEADING_SPACE, e.Bounds.Top + 4);
e.DrawFocusRectangle();
}
private void Form1_Load(object sender, EventArgs e)
{
// get the inital length
int tabLength = tabControl1.ItemSize.Width;
// measure the text in each tab and make adjustment to the size
for (int i = 0; i < this.tabControl1.TabPages.Count; i++)
{
TabPage currentPage = tabControl1.TabPages[i];
int currentTabLength = TextRenderer.MeasureText(currentPage.Text, tabControl1.Font).Width;
// adjust the length for what text is written
currentTabLength += LEADING_SPACE + CLOSE_SPACE + CLOSE_AREA;
if (currentTabLength > tabLength)
{
tabLength = currentTabLength;
}
}
// create the new size
Size newTabSize = new Size(tabLength, tabControl1.ItemSize.Height);
tabControl1.ItemSize = newTabSize;
}
}
Screen shot of sample code from above:
Related
Just a silly display question, but how do I edit the shape of my checkbox in WinForms?
To be specific, instead of the checkmark when I click on the 3 state checkbox, I would like a square. I saw this in a homework assignment and it's purely display, but I just can't find where to edit it.
I'm using Visual Studio C# for Windows Forms btw.
http://imgur.com/a/SkHW9
This is what the "Big" checkbox should look like
You can try this code:
private void checkBox1_Paint(object sender, PaintEventArgs e)
{
CheckState cs = checkBox1.CheckState;
if (cs == CheckState.Indeterminate)
{
using (SolidBrush brush = new SolidBrush(checkBox2.BackColor))
e.Graphics.FillRectangle(brush, 0, 1, 14, 14);
e.Graphics.FillRectangle(Brushes.Green, 3, 4, 8, 8);
e.Graphics.DrawRectangle(Pens.Black, 0, 1, 13, 13);
}
}
This should be easy to modify if you want something else..
Note that you may need to adapt it when changing fonts and surely will have to modify it when changing the alignments..! Also when changing DPI. Or themes. Or Windows versions. Or half a dozen other things. So this is more an example than a recommendation!
You may also read the interesting comments here.. and this example of more involved checkbox drawing..
In order to modify shape any control you need to use Paint event. For example if you add two radio buttons at form, and for each Paint event bind following code:
private void radioButton_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
graphics.Clear(BackColor);
int offset = 2;
SizeF stringMeasure = graphics.MeasureString(radioButton1.Name, Font);
// calculate offsets
int leftOffset = offset + Padding.Left;
int topOffset = (int)(ClientRectangle.Height - stringMeasure.Height) / 2;
if (topOffset < 0)
{
topOffset = offset + Padding.Top;
}
else
{
topOffset += Padding.Top;
}
graphics.FillRectangle(new SolidBrush(Color.AliceBlue), 0, 0, leftOffset + 10, topOffset + 10);
graphics.DrawRectangle(new Pen(Color.Green), new Rectangle(0, 0, leftOffset + 10, leftOffset + 10));
graphics.DrawString(radioButton1.Text, (sender as RadioButton).Font, new SolidBrush(Color.IndianRed), 15, 0);
if( (sender as RadioButton).Checked)
{
graphics.FillRectangle(new SolidBrush(Color.Yellow), 1, 1, leftOffset + 8, 10);
}
}
you'll see following picture:
You have to play with the CheckState property of the checkbox
using the Checked, Unchecked or Indeterminate state
a pretty str.forw example:
private void AdjustMyCheckBoxProperties()
{
// Change the ThreeState and CheckAlign properties on every other click.
if (!checkBox1.ThreeState)
{
checkBox1.ThreeState = true;
checkBox1.CheckAlign = ContentAlignment.MiddleRight;
}
else
{
checkBox1.ThreeState = false;
checkBox1.CheckAlign = ContentAlignment.MiddleLeft;
}
// Concatenate the property values together on three lines.
label1.Text = "ThreeState: " + checkBox1.ThreeState.ToString() + "\n" +
"Checked: " + checkBox1.Checked.ToString() + "\n" +
"CheckState: " + checkBox1.CheckState.ToString();
}
In winforms tabcontrols i have default tab with name start. This tab is always opened and cant be closed! Another tabs has X draw string for closing tab.
In my case all tabas including Start tab has X drawstring for closing.
I want to remove X just on Start tab.
Here is my code
private void mainTabControl_DrawItem(object sender, DrawItemEventArgs e)
{
//This code will render a "x" mark at the end of the Tab caption.
e.Graphics.DrawString("x", e.Font, Brushes.Black, e.Bounds.Right - CLOSE_AREA, e.Bounds.Top + 4);
e.Graphics.DrawString(this.mainTabControl.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + LEADING_SPACE, e.Bounds.Top + 4);
e.DrawFocusRectangle();
mainTabControl.Padding = new Point(21, 3);
}
I have a project in which you can add and remove tabs (like a web browser). So far I have this:
//Button to add a new tab page
private void cb_addPage_Click(object sender, EventArgs e)
{
string title = "TabPage " + (tabControl1.TabCount + 1).ToString() + " ";
TabPage myTabPage = new TabPage(title);
tabControl1.TabPages.Add(myTabPage);
tabControl1.SelectedTab = myTabPage;
}
//Form1_Load
private void Form1_Load(object sender, EventArgs e)
{
tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;
cb_addPage.Top = tabControl1.Top;
cb_addPage.Left = tabControl1.Right - cb_addPage.Width;
foreach (TabPage tp in tabControl1.TabPages) tp.Text += " ";
}
Rectangle closeX = Rectangle.Empty;
//Sets background and places the X button on each tab
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
Size xSize = new Size(15, 15);
TabPage tp = tabControl1.TabPages[e.Index];
e.DrawBackground();
using (SolidBrush brush = new SolidBrush(e.ForeColor))
e.Graphics.DrawString(tp.Text + " ", e.Font, brush,
e.Bounds.X + 3, e.Bounds.Y + 4);
if (e.State == DrawItemState.Selected)
{
closeX = new Rectangle(e.Bounds.Right - xSize.Width - 3,
e.Bounds.Top + 5, xSize.Width, xSize.Height);
e.Graphics.DrawImage(imageList1.Images[0], closeX,
new Rectangle(0,0,16,16), GraphicsUnit.Pixel );
}
}
//Removes current tab (from X button)
private void tabControl1_MouseClick(object sender, MouseEventArgs e)
{
if (closeX.Contains(e.Location))
tabControl1.TabPages.Remove(tabControl1.SelectedTab);
}
So all this does is let you add tabs with a button and on each individual tab there is an X button to delete the tab.
I have used Graphics.DrawImage to display the custom X button (that are in an imageList). However how will I go about making custom tabs using Graphics.DrawImage.
To sum up I want tabs, but I want them to be custom images that I have made so it looks better. - Thanks
Your question is not very clear. Probably you want to display a different text on each tab. You can use the TextRenderer to do so:
const TextFormatFlags flags = TextFormatFlags.PreserveGraphicsClipping |
TextFormatFlags.VerticalCenter;
TextRenderer.DrawText(e.Graphics, tp.Text, tp.Font, e.Bounds, tp.ForeColor, flags);
Either prepend some spaces to the text in order to leave space for the X or define new coordiantes for the text
const int XCrossWidth = 20;
Rectangle textRect = new Rectangle(e.Bounds.Left + XCrossWidth, e.Bounds.Top,
e.Width - XCrossWidth, e.Height);
and substitue this for e.Bounds in TextRenderer.DrawText(...).
UPDATE
So you want to display custom images on your tabs. I assume that you have placed these images in the imageList1. How do you know which of them to display on which TabPage?
You could create your own tab page class and use this one instead of TabPage.
public TabPageEx : TabPage
{
public int ImageIndex { get; set }
}
Now set the property to the appropriate image index.
TabPageEx myTabPage = new TabPageEx(title);
myTabPage.ImageIndex = 3; // As an example.
tabControl1.TabPages.Add(myTabPage);
Then you can draw the image with
TabPageEx tp = (TabPageEx)tabControl1.TabPages[e.Index];
...
Rectangle imageRect = new Rectangle(e.Bounds.Left + 20, 0, 16, 16);
e.Graphics.DrawImage(imageList1.Images[tp.ImageIndex], imageRect);
If you want to display Images in addition to the text like a webbrowsers favicon you can use this modified code:
private void tabControl3_DrawItem(object sender, DrawItemEventArgs e)
{
Size xSize = new Size(16,16);
Point imgLoc = new Point(e.Bounds.X + 4, e.Bounds.Y + 4);
TabPage tp = tabControl3.TabPages[e.Index];
e.DrawBackground();
e.Graphics.DrawImage(imageList1.Images[tp.ImageIndex], new Rectangle(imgLoc, xSize),
new Rectangle(Point.Empty, xSize), GraphicsUnit.Pixel);
using (SolidBrush brush = new SolidBrush(e.ForeColor))
{
e.Graphics.DrawString(tp.Text + " ", e.Font, brush,
e.Bounds.X + 23, e.Bounds.Y + 4);
if (e.State == DrawItemState.Selected)
{
closeX = new Rectangle(e.Bounds.Right - xSize.Width - 3,
e.Bounds.Top + 5, xSize.Width, xSize.Height);
e.Graphics.DrawImage(imageList1.Images[0], closeX,
new Rectangle(Point.Empty, xSize), GraphicsUnit.Pixel);
}
}
}
You need to make sure that:
You have those images you want to show in each tab in your imagelist.
and you know the index of ech one.
Youi must set it when you create the page but you can always change it later.
It is a good idea to have a default icon at index 0 and to set the imageindex to 0 for those you don't know the right index.
Usually you would also have to make your Tab control point to the Imagelist. But since we are drawing it all by ourselves, this is not important.
You can use the more complex format of DrawString, that is used for drawing the close button. Here you don't just use a point to determine the location where to draw; instead this format uses two rectangles to determine the source and the target. This effectively leads to th option of scaling the image to a new size.
But you'll get the best quality if your Images have the right size to begin with. Please note that the text of each TabPage determines the width of the tab; to make it higher you need to chose a larger Fontsize.
I found an article that does exactly what I need. It draws multiple colors on the same line on a text box. But the problem is that it was written in VB.NET and I'm writing my program in C#. Any good soul can convert this to C# if it is possible and if it isn't can you give me other options? Thanks.
This is the article: http://www.vbrad.com/article.aspx?id=34.
here is the conversion of what you posted Nicolas
if you need to change / get anything else working you will need to test it on your end..
Happy coding
private void MeasureItemHandler(object sender, MeasureItemEventArgs e)
{
Graphics g = Graphics.FromHwnd(lstColor.Handle);
StringFormat sf = new StringFormat(StringFormat.GenericTypographic);
SizeF size = default(SizeF);
float height = 0;
Font oFont = new Font("Arial", 10);
//measure the height of what you are about to draw
//and let the listbox know this
size = g.MeasureString(data(e.Index), oFont, 500, sf);
height = size.Height + 5;
e.ItemHeight = height;
}
private void DrawItemHandler(object sender, DrawItemEventArgs e)
{
Graphics g = Graphics.FromHwnd(lstColor.Handle);
StringFormat sf = new StringFormat(StringFormat.GenericTypographic);
SizeF size = default(SizeF);
float width = 0;
Font oFont = new Font("Arial", 10);
//get the width of the string you are about to write
//this info is needed so that we can offset the next
//string that will be drawn in a different color.
size = g.MeasureString(data(e.Index), oFont, 500, sf);
width = size.Width + 16;
//prepare the list for drawing
e.DrawBackground();
e.DrawFocusRectangle();
//draw the first string in a certain color
e.Graphics.DrawString(data(e.Index), oFont, new SolidBrush(color(e.Index)), e.Bounds.X, e.Bounds.Y);
//draw the second string in a different color
e.Graphics.DrawString(data(data.Length - 1 - e.Index), oFont, new SolidBrush(color(color.Length - 1 - e.Index)), width, e.Bounds.Y);
}
First of all, they aren't using a Textbox in this article, they are using a Listbox but what follows is a conversion of the code from VB.Net to C# like you asked. It needs tidied up a bit but it does the job.
Just create a new Windows Form, place a Listbox called lstColor onto this form, change the DrawMode property to OwnerDrawFixed inside the properties window, then add event handlers for DrawItem and MeasureItem (you can add event handlers by clicking on the lightning bolt in the Properties window, and double clicking the whitespace beside these two words in the list).
In the DrawItem event handler, add the following:
private void lstColor_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
var size = g.MeasureString(data[e.Index], oFont, 500, sf);
var width = size.Width + 16;
e.DrawBackground();
e.DrawFocusRectangle();
e.Graphics.DrawString(data[e.Index], oFont, new SolidBrush(color[e.Index]), e.Bounds.X, e.Bounds.Y);
e.Graphics.DrawString(data[data.Length - 1 - e.Index], oFont, new SolidBrush(color[color.Length - 1 - e.Index]), width, e.Bounds.Y);
}
In the MeasureItem event handler, add this:
private void lstColor_MeasureItem(object sender, MeasureItemEventArgs e)
{
var size = g.MeasureString(data[e.Index], oFont, 500, sf);
var height = size.Height;
e.ItemHeight = Convert.ToInt32(height);
}
Add five private fields outside the scope of any methods but inside your Form1 (or whatever you've called your form) class like so:
private string[] data;
private Color[] color;
private Font oFont;
private Graphics g;
private StringFormat sf;
Put the following three lines inside your Form1_Load event:
private void Form1_Load(object sender, EventArgs e)
{
oFont = new Font("Arial", 10);
data = new string[] { "This is Red", "This is Blue", "This is Green", "This is Yellow", "This is Black", "This is Aqua", "This is Brown", "This is Cyan", "This is Gray", "This is Pink" };
color = new Color[] {Color.Red, Color.Blue, Color.Green, Color.Yellow, Color.Black, Color.Aqua, Color.Brown, Color.Cyan, Color.Gray,Color.Pink};
lstColor.DataSource = data;
g = Graphics.FromHwnd(lstColor.Handle);
sf = new StringFormat(StringFormat.GenericTypographic);
}
And you are all set.
Hope this helps
Check out http://converter.telerik.com/ It converts code from VB.NET to C# and C# to VB.NET. It wont work on complex code, but could prove useful to you.
is there anyone can tell me how to add close button in each tab in using tabControl in C#?
i plan to use button pic for replacing [x] in my tab..
thank you
Without deriving a class, here is a neat snippet:
http://www.dotnetthoughts.net/implementing-close-button-in-tab-pages/
Set the DrawMode property of the Tab Control to OwnerDrawFixed. This property decides whether system or developer can paint the captions.
Add the code in the DrawItem event of the Tab Control – This event will be invoked for painting each Tab Page.
//This code will render a "x" mark at the end of the Tab caption.
e.Graphics.DrawString("x", e.Font, Brushes.Black, e.Bounds.Right - 15, e.Bounds.Top + 4);
e.Graphics.DrawString(this.tabControl1.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + 12, e.Bounds.Top + 4);
e.DrawFocusRectangle();
Now for close button action, we need to add the following code to the MouseDown event of the Tab Control.
//Looping through the controls.
for (int i = 0; i < this.tabControl1.TabPages.Count; i++)
{
Rectangle r = tabControl1.GetTabRect(i);
//Getting the position of the "x" mark.
Rectangle closeButton = new Rectangle(r.Right - 15, r.Top + 4, 9, 7);
if (closeButton.Contains(e.Location))
{
if (MessageBox.Show("Would you like to Close this Tab?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.tabControl1.TabPages.RemoveAt(i);
break;
}
}
}
Adding to the other answers... why iterating through all the tabs on mouse click event when we can just detect the current tab with .SelectedIndex and .SelectedTab ?
Like so:
private void tabControl1_MouseDown(object sender, MouseEventArgs e)
{
Rectangle r = tabControl1.GetTabRect(this.tabControl1.SelectedIndex);
Rectangle closeButton = new Rectangle(r.Right - 15, r.Top + 4, 9, 7);
if (closeButton.Contains(e.Location))
{
this.tabControl1.TabPages.Remove(this.tabControl1.SelectedTab);
}
}
What seems to happen is, the moment you click on a tabPage for closing it, it also gets selected, thus allowing the close button to close the right tabPage.
To me it works but please take this with extra care though as I am not completely sure of possible drawbacks (my initial sentence wasn't a completely rhetorical question since I'm kinda new to .Net...).
Try this code:
private Point _imageLocation = new Point(13, 5);
private Point _imgHitArea = new Point(13, 2);
private void Form1_Load(object sender, EventArgs e)
{
tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;
tabControl1.DrawItem += tabControl1_DrawItem;
CloseImage = WindowsFormsApplication3.Properties.Resources.closeR;
tabControl1.Padding = new Point(10, 3);
}
private void TabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
try
{
Image img = new Bitmap(CloseImage);
Rectangle r = e.Bounds;
r = this.tabControl1.GetTabRect(e.Index);
r.Offset(2, 2);
Brush TitleBrush = new SolidBrush(Color.Black);
Font f = this.Font;
string title = this.tabControl1.TabPages[e.Index].Text;
e.Graphics.DrawString(title, f, TitleBrush, new PointF(r.X, r.Y));
if (tabControl1.SelectedIndex >= 1)
{
e.Graphics.DrawImage(img, new Point(r.X + (this.tabControl1.GetTabRect(e.Index).Width - _imageLocation.X), _imageLocation.Y));
}
}
catch (Exception) { }
}
private void TabControl1_Mouse_Click(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabControl tc = (TabControl)sender;
Point p = e.Location;
int _tabWidth = 0;
_tabWidth = this.tabControl1.GetTabRect(tc.SelectedIndex).Width - (_imgHitArea.X);
Rectangle r = this.tabControl1.GetTabRect(tc.SelectedIndex);
r.Offset(_tabWidth, _imgHitArea.Y);
r.Width = 16;
r.Height = 16;
if (tabControl1.SelectedIndex >= 1)
{
if (r.Contains(p))
{
TabPage TabP = (TabPage)tc.TabPages[tc.SelectedIndex];
tc.TabPages.Remove(TabP);
}
}
}
Look at this code snippet
I ran into this same issue, however, I also wanted to have an image in front of the caption on the tab. Here is the code that will give you an image in front, a caption and a close button. Also, this code includes the ability to use the middle mouse click on the tab to close it.
First under the TabControl's properties change DrawMode from Normal to OwnerDrawFixed. Next, you will need to tweak the X dimension for the TabControl's padding also found under properties.
In this example, my TabControl is called "TabManager" so rename that to the name of your control.
Go under the envents (the little lightning bolt near the top of the properties box) and double click in the white box next to "DrawItem", this will create a trigger. In that method use this code with your adjustments.
private void TabManager_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage thisTab = TabManager.TabPages[e.Index];
string tabTitle = thisTab.Text;
Image icon = imageList1.Images[thisTab.ImageIndex];
//Draw Close button
Point closeLoc = new Point(15, 5);
e.Graphics.DrawRectangle(Pens.Black, e.Bounds.Right - closeLoc.X, e.Bounds.Top + closeLoc.Y, 10, 12);
e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds.Right - closeLoc.X, e.Bounds.Top + closeLoc.Y, 10, 12);
e.Graphics.DrawString("x", e.Font, Brushes.Black, e.Bounds.Right - (closeLoc.X), e.Bounds.Top + closeLoc.Y-2);
// Draw String of Caption
e.Graphics.DrawString(tabTitle, e.Font, Brushes.Black, e.Bounds.Left + 28, e.Bounds.Top + 4);
// Add Icon to Front
e.Graphics.DrawImage(icon, e.Bounds.Left + 6, e.Bounds.Top + 3);
e.DrawFocusRectangle();
}
Next go back to the events and double click on the white box next to MouseDown. Then use this code:
private void TabManager_MouseDown(object sender, MouseEventArgs e)
{
Point closeLoc = new Point(15, 5);
Rectangle r = TabManager.GetTabRect(TabManager.SelectedIndex);
Rectangle closeButton = new Rectangle(r.Right - closeLoc.X, r.Top + closeLoc.Y, 10, 12);
if (closeButton.Contains(e.Location))
{
TabManager.TabPages.Remove(TabManager.SelectedTab);
return; // Don't keep running logic in method
}
for (int i = 0; i < TabManager.TabCount; i++)
{
r = TabManager.GetTabRect(i);
if (r.Contains(e.Location) && e.Button == MouseButtons.Middle)
{
TabManager.TabPages.RemoveAt(i);
}
}
}