Double buffering problem of a derived owner-drawn TabControl? - c#

I have derived a TabControl with the express purpose of enabling double buffering, except nothing is working as expected. Here is the TabControl code:
class DoubleBufferedTabControl : TabControl
{
public DoubleBufferedTabControl() : base()
{
this.DoubleBuffered = true;
this.SetStyle
(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.SupportsTransparentBackColor,
false
);
}
}
This Tabcontrol is then set with it's draw mode as 'OwnerDrawnFixed' so i can changed the colours. Here is the custom drawing method:
private void Navigation_PageContent_DrawItem(object sender, DrawItemEventArgs e)
{
//Structure.
Graphics g = e.Graphics;
TabControl t = (TabControl)sender;
TabPage CurrentPage = t.TabPages[e.Index];
//Get the current tab
Rectangle CurrentTabRect = t.GetTabRect(e.Index);
//Get the last tab.
Rectangle LastTab = t.GetTabRect(t.TabPages.Count - 1);
//Main background rectangle.
Rectangle BackgroundRect = new Rectangle(LastTab.Width, t.Bounds.Y - 4, t.Width - (LastTab.Width), t.Height);
//Tab background rectangle.
Rectangle TabBackgroundRect = new Rectangle(0, LastTab.Y + LastTab.Height, LastTab.Width, t.Bounds.Height - (LastTab.Y + LastTab.Height));
//Set anitialiasing for the text.
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
//String format for the text.
StringFormat StringFormat = new StringFormat();
StringFormat.Alignment = StringAlignment.Center;
StringFormat.LineAlignment = StringAlignment.Center;
//Fill the background.
g.FillRectangle(Brushes.LightGray, BackgroundRect);
g.FillRectangle(Brushes.Bisque, TabBackgroundRect);
//Draw the selected tab.
if(e.State == DrawItemState.Selected)
{
g.FillRectangle(Brushes.White, e.Bounds);
Rectangle SelectedTabOutline = new Rectangle(e.Bounds.X + 2, e.Bounds.Y + 2, e.Bounds.Width, e.Bounds.Height - 4);
g.DrawRectangle(new Pen(Brushes.LightGray, 4f), SelectedTabOutline);
g.DrawString(CurrentPage.Text, new Font("Arial", 12f, FontStyle.Bold, GraphicsUnit.Point), new SolidBrush(Color.FromArgb(70, 70, 70)), CurrentTabRect, StringFormat);
}
else
{
g.FillRectangle(new SolidBrush(Color.FromArgb(230, 230, 230)), e.Bounds);
g.DrawString(CurrentPage.Text, new Font("Arial", 12f, FontStyle.Regular, GraphicsUnit.Point), Brushes.Gray, CurrentTabRect, StringFormat);
}
}
All to no avail however, as this control is not double buffered and still flickers when resized.
Any ideas?

If you read the documentation, it says, "This member is not meaningful for this control." If you want the control to be drawn utilizing double-buffering, you'll have to implement it yourself. Besides the fact that if you owner-draw the control, you would have to implement double-buffering yourself anyhow.

First of all, you can get rid of your TabControl code—you turn on buffering, and then immediately turn it off, so it's not actually doing anything useful.
Part of your problem is that you're trying to paint just part of the TabControl.
The easiest solution that gives about a 90% solution (it's still possible to get a flicker) is to add this to your form class:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
If you want to be very sure of getting no flicker, you'll need to draw the entire TabControl yourself, and make sure to ignore background painting requests.
Edit: Note that this will only work in XP and later.

I've had problems with double buffering on controls in the past and the only way to stop the flicker was to ensure the inherited OnPaintBackground method was not being called. (See code below) You will also need to ensure the entire backgound is painted during your paint call.
protected override void OnPaintBackground( PaintEventArgs pevent )
{
//do not call base - I don't want the background re-painted!
}

Not sure, but you might try double-buffering the control that contains the tab control.

I looked around quite a bit, tried your code and whatever else I could think of, but I don't see a way to get rid of the flicker. Unfortunately, in my tests even a regular (non-owner-drawn) tab control flickers during resizing.
For what it's worth, turning off "Show window contents while dragging" will fix it, but I realize that may not be helpful.

I think it doesn't work because you are disabling double buffering!
All this.DoubleBuffered = true does is set ControlStyles.OptimizedDoubleBuffer to true. Since you are disabling that flag in the next line of your program, you are really doing nothing. Remove ControlStyles.OptimizedDoubleBuffer (and perhaps ControlStyles.AllPaintingInWmPaint) and it should work for you.

Related

how to avoid blank area treeview node clicking?

I am working on a regular WinForm Outlook Addin and I have created a Treeview using DrawNode event, The tree is working as expected but there is a glitch in node clicking, only the green region is clickable and the half node gets non responsive.
When I use the MouseDown event the whole area gets clickable including the blank space right next to the node. But to restrict this blank clicking I am using a logic with the help of TreeViewHitTestLocations I am checking if the clicked location is the RightOfLabel then don't do anything but unfortunately this doesn't give me a precise result, it somehow gets confuse and takes right half of the label(Node) as the blank space and doesn't get clicked.
Note: I think this all happened because I played with DrawNode method and while keeping the distance between label and workspace icon the application underneath assumes that the label gets finish within the green portion so the red portion gets left as a blank space. This is just my assumption based on all the naïve things I have done with the method.
Need help to resolve this issue if someone can guide me to a fix. Thanks
void treeview_mousedown(object sender, MouseEventArgs e)
{
TreeNode nodeClicked;
// if arrow up/down will be excluded from the mousedown event
var hitTest = this.HitTest(e.Location);
if (hitTest.Location == TreeViewHitTestLocations.PlusMinus)
return;
if (hitTest.Location == TreeViewHitTestLocations.RightOfLabel)
return;
// Get the node clicked on
nodeClicked = this.GetNodeAt(e.X, e.Y);
// Was the node clicked on?
if (!(nodeClicked == null))
this.SelectedNode = nodeClicked;
}
Below is the treeview drawnode method I am using:
void treeview_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
Rectangle nodeRect = e.Node.Bounds;
// below location is the expand and collapse icon location
Point ptExpand = new Point(nodeRect.Location.X - 7, nodeRect.Location.Y + 5);
Image expandImg = null;
// check the below condition for nodes with child nodes and nodes without child nodes
if ( e.Node.Nodes.Count < 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.search;
else if (e.Node.IsExpanded && e.Node.Nodes.Count > 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.down_arrow_icon;
else
expandImg = global::myresource.OfficeAddin.Controls.Resource.right_arrow_icon;
Graphics g = Graphics.FromImage(expandImg);
IntPtr imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(expandImg, ptExpand);
// draw node icon
Point ptNodeIcon = new Point(nodeRect.Location.X - 4, nodeRect.Location.Y + 2);
Image nodeImg = global::myresource.OfficeAddin.Controls.Resource.folder_icon_16px;
g = Graphics.FromImage(nodeImg);
imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(nodeImg, ptNodeIcon);
// draw node text
Font nodeFont = e.Node.NodeFont;
if (e.Node.NodeFont != null)
{
nodeFont = e.Node.NodeFont;
} else {
nodeFont = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
}
// set the forecolor
Color forecolor = e.Node.ForeColor;
// color same as the font color
string strSelectedColor = #"#505050";
Color selectedColor = System.Drawing.ColorTranslator.FromHtml(strSelectedColor);
SolidBrush selectedTreeBrush = new SolidBrush(selectedColor);
//Inflate to not be cut
Rectangle textRect = nodeRect;
//below value controls the width of the text if given less then, long texts will come in multiple lines
textRect.Width += 150;
// below value controls the over all width of the node, if given less all the things will get sqeeze
e.Graphics.DrawString(e.Node.Text, nodeFont, selectedTreeBrush , Rectangle.Inflate(textRect, -20, 0));
}
Your assumption is right, you can check it by marking the bounds at drawing, for example:
TreeNodeStates state = e.State;
bool isFocused = (state & TreeNodeStates.Focused) == TreeNodeStates.Focused;
if (isFocused)
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, backColor);
Your code has more problems:
Issue 1: Images
If you use icons for nodes use the TreeView.ImageList and TreeNode.ImageKey properties. Otherwise, no space will be allocated for the image at drawing. In this case you can use TreeViewDrawMode.OwnerDrawText DrawMode.
Issue 2: Fonts
Do not use other font than the font of the node or the tree because it can have unexpected size. If this SegoeUI font is a default one use it at your TreeView instance instead. Then you can obtain the required font like this:
Font font = e.Node.NodeFont ?? e.Node.TreeView.Font;
Issue 3: Texts
You use Graphics.DrawString for drawing texts, which uses GDI+. However, starting with .NET 2.0 the default text rendering method is by GDI, unless CompatibleTextRendering property of a control is true. They produce slightly different text sizes.
To use GDI, for which the size of a label is calculated use the TextRenderer class instead:
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, e.Bounds, foreColor, TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix);

Howto fix Backgroundcolor bleeding in bordered ToolStripStatusLabel

I have a problem with a ToolStripStatusLabel which occurs when the BorderSides is set to All and I set a Background Color different to the owning StatusStrip Background Color: The ToolStripStatusLabels Backgroundcolor bleeds outside the border - which looks pretty ugly. I tried to set the BorderStyle property to other settings than Flat without success.
In the screenshot added below, you see the issue - the example in teal is with BorderStyle = Adjust to get the border drawn outside the rectangle. But unfortunately the border dissappears completely.
What I would like to get is no bleeding at all like in this hand-drawn example.
Is this possible to do by a setting or by inheriting or overriding a specific method of the ToolStripStatusLabel? I'm open to programmatical solutions, but I don't know where to start from, so any hints would be welcome.
Implemented Solution by combining x4rf41 and TaWs answers below
Since I made use of multiple answers which led me on the right track, I added the final solution to the question.
I extended the ToolStripStatusLabel class and overrode the OnPaint method. This gave me the possibility to make use of the classes properties and draw it as it would draw itself normally but without the bleeding.
public partial class ToolStripStatusLabelWithoutColorBleeding : ToolStripStatusLabel
{
/// <summary>
/// Bugfix to prevent bleeding of background colors outside the borders.
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
Rectangle borderRectangle = new Rectangle(0, 0, Width - 1, Height - 1);
// Background
e.Graphics.FillRectangle(new SolidBrush(BackColor), borderRectangle);
// Border (if required)
if (BorderSides != ToolStripStatusLabelBorderSides.None)
ControlPaint.DrawBorder3D(e.Graphics, borderRectangle, BorderStyle, (Border3DSide)BorderSides);
// Draw Text if you need it
e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), 0,0);
}
}
I don't think that your problem can be solved by setting the labels properties. You have to do some custom drawing.
I don't know exactly what you are trying to do with your labels but the easiest way for custom drawing is to use the paint event of the label:
private void toolStripStatusLabel1_Paint(object sender, PaintEventArgs e)
{
// Use the sender, so that you can use the same event handler for every label
ToolStripStatusLabel label = (ToolStripStatusLabel)sender;
// Background
e.Graphics.FillRectangle(new SolidBrush(label.BackColor), e.ClipRectangle);
// Border
e.Graphics.DrawRectangle(
new Pen(label.ForeColor), // use any Color here for the border
new Rectangle(e.ClipRectangle.Location,new Size(e.ClipRectangle.Width-1,e.ClipRectangle.Height-1))
);
// Draw Text if you need it
e.Graphics.DrawString(label.Text, label.Font, new SolidBrush(label.ForeColor), e.ClipRectangle.Location);
}
this will give you your hand-drawn example if you set the BackColor of the label to magenta and the ForeColor to the right gray.
You can also extend the ToolStripStatusLabel class and override the onPaint method. The code would be pretty much the same, but you have more options in the custom class, like adding a BorderColor property or something like that.
I played around a little using ControlPaint.DrawBorder3D and found that it too has the BackColor showing as a bottom and right line.
So, similar to xfr41's answer, I tried to do owner-drawing. My idea was to use the system's routines, but to enlarge the drawing rectangle over the clipping area; this way the wrong stripes are lost altogether..
private void toolStripStatusLabel1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = e.ClipRectangle;
Rectangle r2 = new Rectangle(r.X, r.Y, r.Width + 1, r.Height + 1);
ControlPaint.DrawBorder3D(e.Graphics, r2 , Border3DStyle.SunkenInner);
}

Recoloring TabControl

I have a tab control which I want to customize. To be more specific, I want to change the color of the tab page header, as well as the color of that white line around the tab page (check first picture).
I thought of using a custom renderer to do this (similar to recoloring a menu strip, for example), but I'm not sure how to do this. I've also read that setting the DrawMode to OwnerDrawFixed may do this, but using this option makes the the tab control look as if my program was made in the '90s (check second picture).
What I really want to do is to keep the tabs simple and flat and change their color. Check the way tabs are in Visual Studio as an example (check third picture).
Any ideas?
Edit: Another picture of the tab page so that it's more clear what this "white line" is.
When you use OwnerDrawFixed it means you will supply the drawing code. If you did not hook up and use the DrawItem event, nothing gets drawn. This will look much the same as yours at design time, because the event is not firing. For design time painting, you'd have to subclass the control and use OnDrawItem.
// colors to use
private Color[] TColors = {Color.Salmon, Color.White, Color.LightBlue};
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
// get ref to this page
TabPage tp = ((TabControl)sender).TabPages[e.Index];
using (Brush br = new SolidBrush(TColors[e.Index]))
{
Rectangle rect = e.Bounds;
e.Graphics.FillRectangle(br, e.Bounds);
rect.Offset(1, 1);
TextRenderer.DrawText(e.Graphics, tp.Text,
tp.Font, rect, tp.ForeColor);
// draw the border
rect = e.Bounds;
rect.Offset(0, 1);
rect.Inflate(0, -1);
// ControlDark looks right for the border
using (Pen p = new Pen(SystemColors.ControlDark))
{
e.Graphics.DrawRectangle(p, rect);
}
if (e.State == DrawItemState.Selected) e.DrawFocusRectangle();
}
}
basic result:
The tab thumb looks a bit cramped to me and not as tall as the default. So, I added a TFontSize to draw the text at a different size than the Font.
Set the TabControl.Font to 10 (which seems to be plenty), so that Windows draws a slightly larger thumb/header. If you still draw the text at the default 8.25, there is more room:
private float TFontSize = 8.25F; // font drawing size
...
using (Font f = new Font(tp.Font.FontFamily,TFontSize))
{
// shift for a gutter/padding
rect.Offset(1, 1);
TextRenderer.DrawText(e.Graphics, tp.Text,
f, rect, tp.ForeColor);
}
One thing you will loose this way is the VisualStyles effect, but they would seem to clash with colored tabs anyway.

Clear background of transparent user control

I'm working on ImageButton, in which I paint every state(i've got several images for each state) of this button (like mouseOver, mouseDown etc.).
I've made control transparent using this code:
public ImageButton()
{
InitializeComponent();
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20;
return parms;
}
}
But there's a problem, after few switches of state, corners becoming sharp and ugly, to solve this problem I need to clear background, but if my control is transparent then it's impossible.
I've tried this solution: Clearing the graphics of a transparent panel C#
but it's slow and makes control flickering.
Do you have any ideas how to clear this background and keep transparency of control?
Ok, I've solved this problem.
I've worked around it by setting control as not transparent and I draw canvas which is under my control, as background of my ImageButton.
Solution (in Paint event):
//gets position of button and transforms it to point on whole screen
//(because in next step we'll get screenshot of whole window [with borders etc])
Point btnpos = this.Parent.PointToScreen(new Point(Location.X, Location.Y));
//now our point will be relative to the edges of form
//[including borders, which we'll have on our bitmap]
if (this.Parent is Form)
{
btnpos.X -= this.Parent.Left;
btnpos.Y -= this.Parent.Top;
}
else
{
btnpos.X = this.Left;
btnpos.Y = this.Top;
}
//gets screenshot of whole form
Bitmap b = new Bitmap(this.Parent.Width, this.Parent.Height);
this.Parent.DrawToBitmap(b, new Rectangle(new Point(0, 0), this.Parent.Size));
//draws background (which simulates transparency)
e.Graphics.DrawImage(b,
new Rectangle(new Point(0, 0), this.Size),
new Rectangle(btnpos, this.Size),
GraphicsUnit.Pixel);
//do whatever you want to draw your stuff
PS. It doesn't work in designtime.

Manual Painting Issue

I'm having some strange drawing artefacts that I'm hoping someone might be able to help me pin down.
Basically I have a left docked panel that is supposed to have a gradient background that hasn't been working right. I've changed the colours for debugging
screenshot http://img509.imageshack.us/img509/5650/18740614.png
The panel is docked left, and has a transparent control on it (hence you can see small stubs of red along the edges where the transparent control has correctly been painted, picking up the background colour).
When I resize the panel slowly however, I get red lines left at the bottom, which I would expected to be covered up by yellow fills. The code I'm using is as follows:
// Construction
sidePanel.Paint += new PaintEventHandler(OnPaint);
private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Panel pb = sender as Panel;
PaintControl(pb.Width, pb.Height, sender, e, true);
}
private static void PaintControl(int width, int height, object sender, PaintEventArgs e, bool sidebar)
{
Rectangle baseRectangle = new Rectangle(0, 0, width -1, height-1);
using (LinearGradientBrush gradientBrush = new LinearGradientBrush(baseRectangle, WizardBaseForm.StartColour, WizardBaseForm.EndColour, 90))
{
e.Graphics.Clear(Color.Yellow);
e.Graphics.DrawRectangle(Pens.Red, baseRectangle);
e.Graphics.FillRectangle(Brushes.Yellow, baseRectangle);
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}
The overall effect of this is I'm not getting either a nice solid colour, or a nice gradient fill when I resize.
I would suggest creating your own panel, derive it from panel and place the following in the constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint
| ControlStyles.OptimizedDoubleBuffer
| ControlStyles.ResizeRedraw
| ControlStyles.DoubleBuffer
| ControlStyles.UserPaint
, true);
Add following in New (after InitializeComponents):
this.SetStyle(ControlStyles.ResizeRedraw, true);
This is to ensure painting on resize.
There are few additional styles that you may try (AllPaintingInWmPaint, UserPaint...) but whether they should be set or not depends on your particular case and what exactly you want to do. In case that all painting is done by you in Paint event, I would set them.

Categories

Resources