Removing the padding around a Tab control in WinForms [duplicate] - c#

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I remove the border padding on container controls in WinForms?
I've developed a Winforms application in Visual studio 2008. On the main form I have a Tab control. Now I am trying to use a background image to the tab page. The problem that I am running into is that the tab control seems to have a thick border around it. Also the tab control does not cover the entire form leaving a line of space over the top between the form and tab page. (I have the tab pages alignment set at bottom). So a border around the tab control and line of space at the top making my page look ugly. I tried to give the same image as the background to form, but the tab control padding playing the spoilsport.
Any ideas to make my design better would be appreciated.

I agree with the majority of comments made here. The standard TabControl is drawn very poorly on Microsoft's Part...even in Windows Vista / 7 it doesn't look great! You would be better to write your own custom implementation by inheriting the TabControl and then drawing the additional stuff you want.
You might consider using this as a template for your new control. You just need to add some cool design / drawing work to the OnPaint and OnPaintBackground methods.
namespace CustomControls
{
#region USING
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
#endregion
public class CustomTabControl : TabControl
{
#region VARIABLES
private int hotTrackTab = -1;
#endregion
#region INSTANCE CONSTRUCTORS
public CustomTabControl() : base()
{
this.InitializeComponent();
}
#endregion
#region INSTANCE METHODS
private void InitializeComponent()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
}
private int GetTabUnderCursor()
{
Point cursor = this.PointToClient(Cursor.Position);
for (int index = 0; index < this.TabPages.Count; index++)
{
if (this.GetTabRect(index).Contains(cursor))
{
return index;
}
}
return -1;
}
private void UpdateHotTrack()
{
int hot = GetTabUnderCursor();
if (hot != this.hotTrackTab)
{
if (this.hotTrackTab != -1)
{
this.Invalidate(this.GetTabRect(this.hotTrackTab));
}
this.hotTrackTab = hot;
if (this.hotTrackTab != -1)
{
this.Invalidate(this.GetTabRect(this.hotTrackTab));
}
this.Update();
}
}
#endregion
#region OVERRIDE METHODS
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.UpdateHotTrack();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.UpdateHotTrack();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.UpdateHotTrack();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
switch (this.Alignment)
{
case TabAlignment.Bottom:
case TabAlignment.Left:
case TabAlignment.Right:
case TabAlignment.Top:
default:
throw new NotImplementedException();
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground(pevent);
}
#endregion
}
}
Bear in mind that the code above draw an absolutely blank TabControl that only displays the DisplayRectangle. Everything else including the tabs you will need to do yourself!
Additionally, to draw backgrounds for individual TabPages, you may need to override and custom implement TabPage as well, however you might be able to achieve the result you are looking for with just a custom tab control.
Check this out
http://www.codeproject.com/Articles/42046/Customized-TabControl-by-Repainting-Microsoft-s-Pa
IMHO This is better...
http://www.codeproject.com/Articles/38014/KRBTabControl
IMHO this is better still...
http://www.codeproject.com/Articles/91387/Painting-Your-Own-Tabs-Second-Edition
also look on VB Forums...I know I've seem some awesome custom tab controls on there!

Related

Updating TabControl.DisplayRectangle manually

I am writing a custom drawn TabControl class in C#.
My InitializeComponent method (which is called by the constructor) behaves like so, in order to custom draw the control:
private void InitializeComponent()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.UpdateStyles();
this.DrawMode = TabDrawMode.OwnerDrawFixed;
}
The TabControl uses two rectangle surfaces;
The ClientRectangle which contains the entire control
The DisplayRectangle which is the portion of the control to display TabPage content
As I want to adjust the DisplayRectangle, I have overridden its property (which is get only):
public override Rectangle DisplayRectangle
{
get
{
return this.displayRectangle; // points to a local rectangle, rather than base.DisplayRectangle
}
}
Then I've overridden OnSizeChanged in order to update the display rectangle size:
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
this.displayRectangle = this.ClientRectangle;
this.displayRectangle.Y += this.ItemSize.Height + 4;
this.displayRectangle.Height -= this.ItemSize.Height + 4;
}
The problem I'm encountering is, when the TabControl is re-sized, the DisplayRectangle works and re-sizes accordingly, but when the parent form is maximized/minimized (thus changing the control size), the display rectangle does not update.
How should I go about fixing this? Are there any guidelines for managing the display rectangle manually?
I have discovered a couple of things which help to fix this issue. They are; to resize the display rectangle using an override of the OnResize method, and to set the bounds for each tab in the tab control when you have finished resizing the display rectangle...e.g:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.displayRectangle = this.ClientRectangle;
this.displayRectangle.Y += this.ItemSize.Height + 4;
this.displayRectangle.Height -= this.ItemSize.Height + 4;
foreach(TabPage page in this.TabPages)
{
page.SetBounds(
this.DisplayRectangle.X,
this.DisplayRectangle.Y,
this.DisplayRectangle.Width,
this.DisplayRectangle.Height,
SpecifiedBounds.All
);
}
this.Refresh(); // Can optimize this!
}

Focus on scroll

I have a user control with a scrollbar (scrollbar appears as a contained user control, which inherits from Panel, is too large). When using the mouse to scroll all is well, but trying to scroll with the mousewheel dont work.
My solution here is to set focus to my child-control in an eventhandler for Scroll. This works. Now the question; Will this result in a lot of unecessary calls to childControl.Focus()? Is there a more neat way of doing this?
Edit: I think I was a bit unclear with my question so Rephrasing the question:
is
private void ChildControl_OnScroll(object sender, ScrollEventArgs scrollEventArgs)
{
this.childControl.Focus();
}
a bad way of setting the focus? I.e. will the focus be set mutliple times each time I scroll? or rather, will this cause (tiny) performance issues.
Here's another approach that gives focus when the scrollbar area of panel1 inside SomeUserControl is clicked. It uses NativeWindow so you don't have to change the panel in your UserControl. This way Focus() will only be called once, when the mouse goes down in the scrollbar area:
public partial class SomeUserControl : UserControl
{
private TrapMouseDownOnScrollArea trapScroll = null;
public SomeUserControl()
{
InitializeComponent();
this.VisibleChanged += new EventHandler(SomeUserControl_VisibleChanged);
}
void SomeUserControl_VisibleChanged(object sender, EventArgs e)
{
if (this.Visible && trapScroll == null)
{
trapScroll = new TrapMouseDownOnScrollArea(this.panel1);
}
}
private class TrapMouseDownOnScrollArea : NativeWindow
{
private Control control = null;
private const int WM_NCLBUTTONDOWN = 0xA1;
public TrapMouseDownOnScrollArea(Control ctl)
{
if (ctl != null && ctl.IsHandleCreated)
{
this.control = ctl;
this.AssignHandle(ctl.Handle);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCLBUTTONDOWN:
if (this.control != null)
{
Rectangle screenBounds = control.RectangleToScreen(new Rectangle(0, 0, control.Width, control.Height));
if (screenBounds.Contains(Cursor.Position))
{
control.Focus();
}
}
break;
}
base.WndProc(ref m);
}
}
}
This might be overkill for your scenario, but it demonstrates one way to trap lower level messages. As said before, you could also derive from Panel to achieve the same affect. You could also trap messages at the application level with IMessageFilter.
The MouseWheel event is an event that "bubbles". Windows sends it to the control that has the focus, regardless of where the mouse cursor is located. The most typical problem is that you have a control that cannot receive the focus. A Panel for example.
This changes when you put a control on the panel. Now that control can get the focus and gets the MouseWheel message. It won't have any use for it so the message passes to its parent. Which does have a use for it, the panel scrolls as expected.
You can get a focusable panel control from this answer. A generic "make it work like a browser or Office program" solution from this question
If childControl has a MouseEnter() event then use that instead:
private void childControl_MouseEnter(object sender, EventArgs e)
{
childControl.Focus();
}
Then the mouse wheel events should be direct to childControl.

Windows Forms Remove Close Button

I'm working on a Windows Forms app and I'm wanting to remove the close button from the top. I'm aware of the ControlBox option, but I'm wanting to provide a help button. Is there a way to have the Close button not visible while maintaining the help button?
Your best bet may be to subcribe to the FormClosing event of the form like so and cancel the closing action:
// In your code somewhere subscribe to this event
Form1.FormClosing += Form1_FormClosing;
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
The benefit of doing this is that it prevents the user from closing the application from the close button and the taskbar.
Obviously you don't want to ALWAYS cancel the form from closing. So you will want to set some type of boolean flag that you will check in the event listener as to whether you want the form to be allowed to close or not. Example:
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (BlockClosing)
e.Cancel = true;
}
EDIT: If you don't want to approach the problem that way, and you really do intend to completely remove the close button, then your best bet is to create your own custom title bar. In that case, you set the form's FormBorderStyle property to None. And you then dock your custom title bar to the top of the form. Here is some sample code from one I made a while back:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace Spectrum.UI
{
public partial class TitleBar : UserControl
{
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler MinButtonClick;
public event EventHandler MaxButtonClick;
public event EventHandler CloseButtonClick;
#region Properties
[Category("Appearance")]
public string Title
{
get { return TitleLabel.Text; }
set { TitleLabel.Text = value; }
}
[Category("Appearance")]
public bool MinimizeEnabled
{
get
{
return minButton.Visible;
}
set
{
minButton.Visible = value;
}
}
[Category("Appearance")]
public bool MaximizeEnabled
{
get
{
return maxButton.Visible;
}
set
{
maxButton.Visible = value;
}
}
#endregion
public TitleBar()
{
InitializeComponent();
ShowTitleBarImage = false;
}
#region Mouse Events
private void TitleBar_MouseDown(object sender, MouseEventArgs e)
{
this.OnMouseDown(e);
}
private void TitleBar_MouseUp(object sender, MouseEventArgs e)
{
this.OnMouseUp(e);
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
this.OnMouseMove(e);
}
#endregion
#region Button Click Events
private void minButton_Click(object sender, EventArgs e)
{
if (MinButtonClick != null)
this.MinButtonClick.Invoke(this, e);
}
private void maxButton_Click(object sender, EventArgs e)
{
if (MaxButtonClick != null)
this.MaxButtonClick.Invoke(this, e);
}
private void closeButton_Click(object sender, EventArgs e)
{
if (CloseButtonClick != null)
this.CloseButtonClick.Invoke(this, e);
}
#endregion
}
}
As you can see from the image, I also added a background image to the control. Depending on your patience and your requirements, you can use images and PictureBox controls to make this look as much like a standard title bar as you need.
In the above example I placed three buttons on the control with images I found online to represent minimize, maximize, and close. in your case you would simply exclude a close button. I also placed a string on the control with an appropriate font to serve as the title of the window.
Adding the custom title bar to your form is easy.
public TitleBar titleBar = new TitleBar();
titleBar.Dock = DockStyle.Top;
titleBar.MaximizeEnabled = true;
titleBar.MinimizeEnabled = true;
titleBar.Size = new System.Drawing.Size(10, 40); // Width doesn't matter - I wanted it 40 pixels tall
titleBar.Title = "Title Example";
titleBar.MinButtonClick += titleBar_MinButtonClick;
titleBar.Max ButtonClick += titleBar_MaxButtonClick;
this.Controls.Add(this.TitleBar);
And then last step is to set up your event listeners for the min and max button clicks:
private void titleBar_MinButtonClick(object sender, EventArgs e)
{
WindowState = FormWindowState.Minimized;
}
private void titleBar_MaxButtonClick(object sender, EventArgs e)
{
WindowState = FormWindowState.Maximized;
}
You may also note that I included events for mouse down, up and move in my title bar. This was so that I could create listeners in my form to move the form when the user clicked and dragged the title bar. This is optional and depends on if you need the user to be able to move your application window.
The added benefit of doing this is that can use the title bar for additional controls. For example, my application was custom written for use on a toughbook style tablet computer with a small touchscreen display. In my application, utilization of the limited space was extremely important. I was able to further modify what I've described here to also include menu bar style control directly on the title bar. In addition, I added more buttons to the left of the stand minimize, maximize, and close buttons. Really helped me utilize every square inch of the screen in my application. Couldn't have done it with the standard title bar.
Can you simply use Form.ControlBox = false (or via the designer as you point out rather negatively in your comment) and then add a custom help button on the form?
EDIT: A colleague of mine wrote an Excel add in and had a requirement to remove the X from certain forms (e.g. a Progress Bar that shouldn't be closed). He found a function written by Stephen Bullen that did just that. I've only seen this function used in VB, but perhaps you can get some ideas or direction out of his approach of using Windows API to solve your issue.
This code will disable the Close button. I am not sure if you can actually make it invisible. http://www.codeproject.com/Articles/20379/Disabling-Close-Button-on-Forms
//
// source code
// Code Snippet
private const int CP_NOCLOSE_BUTTON = 0x200;
protected override CreateParams CreateParams
{
get
{
CreateParams myCp = base.CreateParams;
myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON ;
return myCp;
}
}
Good luck!
Please try this.ControlBox = false.

Disable focus cues on a SplitContainer

How can I disable the focus cues on a SplitContainer?
I ask because I'd rather draw them myself using OnPaint in order to make it look somewhat smoother.
I tried this:
protected override bool ShowFocusCues
{
get
{
return false;
}
}
And this is my control:
public class cSplitContainer : SplitContainer
{
private bool IsDragging;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (!IsSplitterFixed) IsDragging = true;
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (IsDragging)
{
IsDragging = false;
IsSplitterFixed = false;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (IsDragging)
{
IsSplitterFixed = true;
if (e.Button == MouseButtons.Left)
{
if (Orientation == Orientation.Vertical)
{
if (e.X > 0 && e.X < Width) SplitterDistance = e.X;
}
else
{
if (e.Y > 0 && e.Y < Height) SplitterDistance = e.Y;
}
}
else
{
IsDragging = false;
IsSplitterFixed = false;
}
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
if (IsDragging)
{
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(127, 0, 0, 0)), Orientation == Orientation.Horizontal ? new Rectangle(0, SplitterDistance, Width, SplitterWidth) : new Rectangle(SplitterDistance, 0, SplitterWidth, Height));
}
}
}
but it didn't work. I also tried some other methods mentioned before, but I'm still getting focus cues.
I don't think what you are seeing is the FocusCue so much as a floating window that is used to move the slider.
If keyboard access isn't important, you can try making it unselectable:
public class MySplit : SplitContainer {
public MySplit() {
this.SetStyle(ControlStyles.Selectable, false);
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(Color.Red);
}
}
This prevents the SplitContainer from getting focus, but your mouse can still interact with it.
The code of SplitContainer is like:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (Focused) {
DrawFocus(e.Graphics,SplitterRectangle);
}
}
DrawFocus is not virtual. So you can't override it.
Focused is virtual. Maybe you can set it to false while calling base.OnPaint(...) in your OnPaint override.
So you could add following code (I did not tested if it works):
private bool _painting;
public override bool Focused
{
get { return _painting ? false : base.Focused; }
}
protected override void OnPaint(PaintEventArgs e)
{
_painting = true;
try
{
base.OnPaint(e);
}
finally
{
_painting = false;
}
}
That is more a hack than a clean solution.
I was googling for this issue and this question came up on the top.
There is a solution and interesting discussion on a Microsoft forum regarding the splitter stealing focus for no good reason. The following comment is spot on:
The focus issue you mentioned is by design, however to get the performance you want, you can use the following workaround: ....
It may be "by design", but it is not a very good one. What spitters have you ever seen in any Microsoft production application that even temporarily take the focus from the panes they split? I also added the code you suggest, and it does keep me from permanently losing the focus to the splitter, but I still don't like the fact that my panes hide and show their selections during splitter manipulation.
This distracting selection flash just is not present in most professional applications. It is just good enough that it probably won't be worth my time to fix for a while, but not what most people really want. If you respected the TabStop property or even added a AcceptsFocus property, most people would want this off. I think you should add this option to the design in a future version.
--Brendan
Simple solution: give away focus immediately when receiving it!
Three steps:
Create a GotFocus handler for the SplitContainer
Forward the focus to another control with AnotherControl.Focus().
Set TabStop to False
That's all. The ugly focus cue is never shown.
Now, one subtlety: Which other control to give the focus to? It's up to you. Just take the first control by tab order, or a upper-left focusable control in the right pane of the SplitContainer (TextBox in the ASCII diagram below). The perfect solution would be the previous control which had focus, but sadly this is not easy to find out: Find out the control with last focus, but IMHO the upper-left focusable control is a very good response.
left pane right pane
------------------------------------------------
: :: :
: :: [TextBox] [Button] :
: :: :
: :: [Combobox V] :
: :: :
------------------------------------------------
it is a kind a similar question being asked on stackoveflow one solution is sugested as being used by you also along with overriding showfocuscues property you need to override paint method as well.

in winforms, can i drag a control with a bunch of controls (like listboxes) in it into another control and resize / move them from there?

I'm running into some situations where it has been nice to make a control (Control A) with a bunch of things in it like buttons, listboxes, datagridviews etc so I can drag it into other controls (Control B) whenever I like, so I don't have to write the logic for the stuff within Control A over and over again.
The only annoying thing with this approach has been that I can't seem to find a way in the designer to move/resize the buttons, listboxes, datagridviews etc of Control A around from the designer of Control B. All it seems to let me do is resize the entirety of Control A.
Does anyone know how to make these custom controls containing multiple controls in such a way that they support design time resizing/moving?
Thanks
Isaac
There is no built in way to do this. You would essentially have to implement event handlers in order to handle the resizing of individual components within your control. One alternative for you is to expose the Size and Location properties of each individual component to the control's clients. For example, within the Control class you could do something like this:
public Size Component1Size
{
get { return component1.Size; }
set { component1.Size = value; }
}
public Point Component1Location
{
get { return component1.Location; }
set { component1.Location = value; }
}
and do this for each component of your control. I think this would be your best option, even though the user won't be able to physically click/drag the components to move and resize them.
Yes you can my friend ,you need to create a SizeAble and DragAndDrop pannel where you can Insert Controls and by Moving That Pannel you can reach that .And for the Resizing Issue you can play with Anchor of Control's that you already added .
using System;
using System.Drawing;
using System.Windows.Forms;
public class SizeablePanel : Panel {
private const int cGripSize = 20;
private bool mDragging;
private Point mDragPos;
public SizeablePanel() {
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.BackColor = Color.White;
}
protected override void OnPaint(PaintEventArgs e) {
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor,
new Rectangle(this.ClientSize.Width - cGripSize, this.ClientSize.Height - cGripSize, cGripSize, cGripSize));
base.OnPaint(e);
}
private bool IsOnGrip(Point pos) {
return pos.X >= this.ClientSize.Width - cGripSize &&
pos.Y >= this.ClientSize.Height - cGripSize;
}
protected override void OnMouseDown(MouseEventArgs e) {
mDragging = IsOnGrip(e.Location);
mDragPos = e.Location;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e) {
mDragging = false;
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e) {
if (mDragging) {
this.Size = new Size(this.Width + e.X - mDragPos.X,
this.Height + e.Y - mDragPos.Y);
mDragPos = e.Location;
}
else if (IsOnGrip(e.Location)) this.Cursor = Cursors.SizeNWSE;
else this.Cursor = Cursors.Default;
base.OnMouseMove(e);
}
}

Categories