Stretch tab headers instead of tab pages in tab control when resizing - c#

I am owner-drawing a left-oriented tabcontrol in winforms.
Each tabpage has a fixed size, so when the UI is stretched wide, I would like the ItemSize width of the tab headers to increase correspondingly.
private void tbcTests_Resize( object sender, EventArgs e )
{
tbcTests.ItemSize = new Size(
tbcTests.Width - tbcTests.TabPages[0].Controls[0].Width - tbcTests.Padding.X,
tbcTests.ItemSize.Height );
}
This code results in a stack overflow. I suspect this is because the resize is done using incorrect dimensions, forcing the control to continuously redraw. However, I am unsure how to fix it. Am I not accounting for excess space correctly?
How should I resize the tab headers and using what dimensions?

Changing the ItemSize property cause the Resize event to fire again. You'll need a helper variable to suppress the nested event. Like this:
private bool busySizing;
private void tbcTests_Resize( object sender, EventArgs e )
{
if (busySizing) return;
busySizing = true;
try {
tbcTests.ItemSize = new Size(
tbcTests.Width - tbcTests.TabPages[0].Controls[0].Width - tbcTests.Padding.X,
tbcTests.ItemSize.Height );
}
finally {
busySizing = false;
}
}

Related

Zoom effect on buttons inside a FlowLayoutPanel

I have some buttons inside a FLP and I want when the mouse is over a button, to increase the size of the button a little, like a zoom effect. The problem is that when the button size increases, all buttons next to it slide to the right and down.. Probably the issue is caused by the space added by the FLP between items, but how can I prevent this, so when the size is increasing to go over that space not to add more..?
My ZOOM class:
class zoom
{
public zoom(Control cst)
{
cst.MouseEnter += delegate(object sender, EventArgs e)
{
cst.Size = new Size(70, 75);
cst.Font = new Font(cst.Font.FontFamily, 9);
};
cst.MouseLeave += delegate(object sender, EventArgs e)
{
cst.Size = new Size(68, 73);
cst.Font = new Font(cst.Font.FontFamily, 8);
};
}
}
It's the way the FlowLayoutPanel works. It moves the rest of the controls to fit them all following the flow.
If you have some space between the buttons it's because of the Margin property, you could reduce the margin of the button as you zoom it.
This won't work if you want the button to show over the other ones. In that case, probably the only way to go is to use a simple Panel instead of the FlowLayoutPanel and do a BringToFront() with the Zoom.

Paint event of panel not called when shrinking Form from right to left?

I have a Form which has got a parent panel and it had got a child panel where I am drawing items using the drawing mechanism it works good as expected, but when I shrink my form from right to left it doesn't call child panels paint event while if I shrink a little from left to right and again spread it then it calls the paint event, how should I fix it?
Below is my code.
private void canvas_Paint(object sender, PaintEventArgs e)
{
drawString(e);
this.Invalidate();
//this.Refresh();
//this.Update();
}
private void drawString(PaintEventArgs e)
{
System.Drawing.Drawing2D.LinearGradientBrush myBrush = new System.Drawing.Drawing2D.LinearGradientBrush(ClientRectangle, Color.Red, Color.Yellow, System.Drawing.Drawing2D.LinearGradientMode.Horizontal);
cBasketItemHelper objHelper = new cBasketItemHelper() { CanvasWidth = this.canvas.Width, CanvasHeight = this.canvas.Height, X = 3, Y = 3 };
objHelper.myBrush = myBrush;
objHelper.currOrder = Program.currOrder;
objHelper.g = e.Graphics;//this.canvas.();//this.canvas.Graphics;
objHelper.DrawBasketItems();
e.Dispose();
}
The Panel class was designed to be just a container for other controls, it is not expected to do any painting of its own beyond drawing the background. Somewhat heavy-handedly it optimizes the painting, a resize only paints the parts that were revealed, not the entire client area.
You however want OnPaint to always run when the size changes, even when you make it smaller. Derive your own class from Panel and set the ResizeRedraw property to true in the constructor:
class Canvas {
public Canvas() {
this.ResizeRedraw = true;
this.DoubleBuffered = true; // extra goodie
}
}
Build. Drop the new Canvas control from the top of the toolbox, replacing your existing panel control. If you don't need the scrolling support that Panel provides then using a PictureBox gets you both without needing to derive.

C# Resize textbox to fit content

I'm writing a program where the user should be able to write text in a TextBox. I'd like the TextBox to resize itself, so it fits to the content.
I've tried the following:
private void textBoxTitle_TextChanged(object sender, TextChangedEventArgs e)
{
System.Drawing.Font myFont = new System.Drawing.Font("Verdana", 8);
System.Drawing.SizeF mySize = e.Graphics.MeasureString("This is a test", myFont);
this.textBoxTitle.Width = (int)Math.Round(mySize.Width, 0);
}
I get an error saying that Graphics doesn't work for TextChangedEventArgs. Is there another way I can resize the TextBox?
You should try a code something like below. It has worked for me well.
private void textBox1_TextChanged(object sender, EventArgs e)
{
Size size = TextRenderer.MeasureText(textBox1.Text, textBox1.Font);
textBox1.Width = size.Width;
textBox1.Height = size.Height;
}
For more information refer to TextRenderer.MeasureText()
I am adding this answer as I do not see the fixed width aspect of a textbox being discussed in any of the other. If you have a fixed width for your textbox, and you want to adjust only its height you can do something like the following:
Something like this gives the height of the text as how it is drawn in the multiline wordwrapped textbox itself:
SizeF MessageSize = MyTextBoxControl.CreateGraphics()
.MeasureString(MyTextBoxControl.Text,
MyTextBoxControl.Font,
MyTextBoxControl.Width,
new StringFormat(0));
I am not sure what StringFormat should be but the values StringFormatFlags do not seem to apply to a default TextBox make up.
Now with MessageSize.Height you know the height of the text in the textbox.
I had the same problem and I solved it in a simpler way.
I used the AutoSize property of a Label control.. I added an invisible label to my form, set its AutoSize property True. When the I need to change the size of my TextBox I use this code:
MyLabel.Text = MyTextBox.Text;
MyTextBox.Size = MyLabel.Size;
I set the Maximum and Minimum Size of the label for better results.
Have Fun
Your binding to the wrong event, and you cannot use the graphics object in the TextChangedEventArgs object.
Try using the TextChanged event. The following snippet is working:
public Form1()
{
InitializeComponent();
this.textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
}
void textBox1_TextChanged(object sender, EventArgs e)
{
System.Drawing.SizeF mySize = new System.Drawing.SizeF();
// Use the textbox font
System.Drawing.Font myFont = textBox1.Font;
using (Graphics g = this.CreateGraphics())
{
// Get the size given the string and the font
mySize = g.MeasureString(textBox1.Text, myFont);
}
// Resize the textbox
this.textBox1.Width = (int)Math.Round(mySize.Width, 0);
}
}
first, create method to Make the TextBox fit its contents.
private void AutoSizeTextBox(TextBox txt)
{
const int x_margin = 0;
const int y_margin = 2;
Size size = TextRenderer.MeasureText(txt.Text, txt.Font);
txt.ClientSize =
new Size(size.Width + x_margin, size.Height + y_margin);
}
then with the TextChanged event handler calls AutoSizeTextBox() function to make the TextBox fit its text when the text changes.
private void txtContents_TextChanged(object sender, EventArgs e)
{
AutoSizeTextBox(sender as TextBox);
}
That’s all, for more info:
resize-a-textbox-to-fit-its-text
You will need to use the CreateGraphics() method of the form to create the Graphics instance to measure the string on.
The TextChangedEventArgs class does not have a Graphics property, that is a property of the PaintEventArgs class passed in to the Paint event handler
Try this:
using System.Drawing;
...
private void textBoxTitle_TextChanged(object sender, TextChangedEventArgs e)
{
// Determine the correct size for the text box based on its text length
// get the current text box safely
TextBox tb = sender as TextBox;
if (tb == null) return;
SizeF stringSize;
// create a graphics object for this form
using(Graphics gfx = this.CreateGraphics())
{
// Get the size given the string and the font
stringSize = gfx.MeasureString(tb.Text, tb.Font);
}
// Resize the textbox
tb.Width = (int)Math.Round(stringSize.Width, 0);
}
Essentially you create your own Graphics object for the form, then measure it based on the text and font of the TextBox. The using will properly dispose the Graphics object - your previous code would have leaked horribly!
Whatever the aim is.
If the size of the textbox should be dynamically set up based on the string, which should be the text inside this box, there is no nice option.
Reasons : MeasureString uses usual string formatters as delimiters for its own width and height.
Means, carriage return and line feed are parsed, too. Resulting in a sizeF.Width and sizeF.Height.
Depending on the string( and its font and number of lines ) these both variables can carry values, which are sometimes useless to be used as width/height values of a textbox ( because they can be bigger than the parentform's values and this would resize the textbox to a size, with left and bottom borders beyond those of the parent form).
Some solutions are still available, depending on the aim, one would like to achieve.
One idea would be :
Create a textbox in designer, size = 100 X 100. enable word-wrapping.
In the OnTextChanged event handler of the textbox, we just resize the textbox's width to a value, defined by ourself (e.g. parentform.Width or another hard value ).
This would cause the word wrap to recalculate the string in the textbox and this would rearrange all the characters inside the textbox, because word wrap is enabled.
The height of the textbox could can be set hard to parentform.Height, for example.
BUT,
better : set the height dynamically,based on the Y value of the ReturnValue (Point) of the method texbox.GetPositionFromCharIndex(textbox.TextLength -1 ).
Then, with Math.Min() determine, which is smaller ( either parentform.Height or Point.Y ) , and reset the textbox size to new Size(previousDeterminedWidth, nowDeterminedHeight).
Please keep in mind ( if scrollbars are enabled ) to add about 17 pixs to Your width calculation.
Best regards
Did you try to set yourTextBox.AutoSize = true;?
This property may be hidden in the GUI designer, but you can set it in the form constructor right after InitializeComponent(); call.
Graphics.Measure string you can do o PaintEventArgs, not on TextChangedEventArgs
What I think you want is this
System.Drawing.Font myFont = new System.Drawing.Font("Verdana", 8);
Graphics graphics = this.CreateGraphics();
SizeF textSize = graphics.MeasureString("This is a test", myFont);
The problem is that you just cannot create a Graphics object by simply allocating it since it has no public constructor, so you should better go and use TextRenderer.MeasureText, as done in http://msdn.microsoft.com/en-us/library/y4xdbe66.aspx
TextRenderer is less accurate because it uses GDI and Graphics uses GDI+, so maybe you should leave a little margin on the value you get from the Width property.
Hope this helps

UserControl not rendering within FlowLayoutPanel when dock changed

When I add my UserControls to a FlowLayoutPanel, they display properly. When I change the Dock or Anchor properties on the UserControls before adding them, they are still added but do not render.
According to "How to: Anchor and Dock Child Controls" this should be possible.
I can tell that the controls are added (despite not drawing) because adding enough of them causes a vertical scrollbar to appear.
Setting the "Dock" property of the UserControls to "Left" or "None" will cause them to render, but none of the other options.
Setting the "Anchor" property on the UserControls to anything but Top | Left does not render.
Setting the dock before or after adding the control makes no difference (Add, Dock vs. Dock, Add).
The FlowLayoutPanel is itself is docked (Fill), has FlowDirection set to TopDown, has WrapContents set to false, has AutoScroll set to true, and is otherwise default.
I am using .NET 3.5.
In answer to a comment, the two commented lines are the locations I tried to change the dock. The second spot definitely makes more sense, but I tried the other because it couldn't hurt.
public void CreateObjectControl( object o )
{
ObjectControl oc = new ObjectControl();
oc.MyObject = o;
//This was a spot I mentioned:
//oc.Dock = DockStyle.Fill;
ObjectDictionary.Add( o, oc );
flowLayoutPanel1.Controls.Add( oc );
//This is the other spot I mentioned:
oc.Dock = DockStyle.Fill;
}
try using SuspendLayout and Resumelayout function for the controls before making any amendments which need rendering for proper viewing.
You could see the code from Designer.cs for that particular control
Syntax
control.SuspendLayout();
{Your code for designer amendments}
control.resumeaLayout();
I think I may have found a workaround (read: dirty trick) ... this answer helped to point me in the right direction. Here's an excerpt from the MS article that you also linked to:
For vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
The behavior works in a similar way for horizontal flow directions. The FlowLayoutPanel control calculates the height of an implied row from the tallest child control in the row, and all docked or anchored child controls in this row are aligned or sized to fit the implied row.
This page does not specifically mention that you can't Dock/Anchor the tallest/widest control. But as this control defines the layout behaviour of the FlowLayoutPanel, and thus influences the way all other sibling controls are displayed, it is well possible that Dock and Anchor don't work properly for that 'master control'. Even though I can't find any official documentation regarding that, I believe it to be the case.
So, which options do we have? At runtime, we could add a panel control of height 0 and width of the FlowLayoutPanel client area before you add your usercontrol. You can even set that panel's visibility to false. Subscribing to some Resize/Layout events of the FlowLayoutPanel to keep that panel's size will to the trick. But this does not play nicely at design time. The events won't fire and thus you can't really design the surface the way you want it to look.
I'd prefer a solution that "just works" at design time as well. So, here's an attempt at an "invisible" control that I put together, to fix the controls resizing to zero width if no other control is present. Dropping this as first control onto the FlowLayoutPanel at design time seems to provide the desired effect, and any control subsequently placed on the FlowLayoutPanel is anchorable to the right without shrinking to zero width. The only problem is that, once this invisible control is there, it seems I can't remove it anymore via the IDE. It probably needs some special treatment using a ControlDesigner to achieve that. It can still be removed in the form's designer code though.
This control, once placed onto the FlowLayoutPanel, will listen for resize events of it's parent control, and resize itself according to the ClientSize of the parent control. Use with caution, as this may contain pitfalls that didn't occur to me during the few hours I played with this. For example, I didn't try placing controls that were wider than the FlowLayoutPanel's client area.
As a side note, what will still fail is trying to anchor to the bottom, but that wasn't part of the question ;-)
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
namespace ControlTest
{
public sealed class InvisibleControl : Control
{
public InvisibleControl()
{
TabStop = false;
}
#region public interface
// Reduce the temptation ...
public new AnchorStyles Anchor
{
get { return base.Anchor; }
set { base.Anchor = AnchorStyles.None; }
}
public new DockStyle Dock
{
get { return base.Dock; }
set { base.Dock = DockStyle.None; }
}
// We don't ever want to move away from (0,0)
public new Point Location
{
get { return base.Location; }
set { base.Location = Point.Empty; }
}
// Horizontal or vertical orientation?
private Orientation _orientation = Orientation.Horizontal;
[DefaultValue(typeof(Orientation), "Horizontal")]
public Orientation Orientation
{
get { return _orientation; }
set
{
if (_orientation == value) return;
_orientation = value;
ChangeSize();
}
}
#endregion
#region overrides of default behaviour
// We don't want any margin around us
protected override Padding DefaultMargin => Padding.Empty;
// Clean up parent references
protected override void Dispose(bool disposing)
{
if (disposing)
SetParent(null);
base.Dispose(disposing);
}
// This seems to be needed for IDE support, as OnParentChanged does not seem
// to fire if the control is dropped onto a surface for the first time
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ChangeSize();
}
// Make sure we don't inadvertantly paint anything
protected override void OnPaint(PaintEventArgs e) { }
protected override void OnPaintBackground(PaintEventArgs pevent) { }
// If the parent changes, we need to:
// A) Unsubscribe from the previous parent's Resize event, if applicable
// B) Subscribe to the new parent's Resize event
// C) Resize our control according to the new parent dimensions
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
// Perform A+B
SetParent(Parent);
// Perform C
ChangeSize();
}
// We don't really want to be resized, so deal with it
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ChangeSize();
}
#endregion
#region private stuff
// Make this a default handler signature with optional params, so that this can
// directly subscribe to the parent resize event, but also be called without parameters
private void ChangeSize(object sender = null, EventArgs e = null)
{
Rectangle client = Parent?.ClientRectangle ?? new Rectangle(0, 0, 10, 10);
Size proposedSize = _orientation == Orientation.Horizontal
? new Size(client.Width, 0)
: new Size(0, client.Height);
if (!Size.Equals(proposedSize)) Size = proposedSize;
}
// Handles reparenting
private Control boundParent;
private void SetParent(Control parent)
{
if (boundParent != null)
boundParent.Resize -= ChangeSize;
boundParent = parent;
if (boundParent != null)
boundParent.Resize += ChangeSize;
}
#endregion
}
}

How to make a ToolStripComboBox to fill all the space available on a ToolStrip?

A ToolStripComboBox is placed after a ToolStripButton and is folowed by another one, which is right-aligned. How do I best set up the ToolStripComboBox to always adjust its length to fill all the space available between the preceeding and the folowing ToolStripButtons?
In past I used to handle a parent resize event, calculate the new length to set based on neighboring elements coordinates and setting the new size. But now, as I am developing a new application, I wonder if there is no better way.
I use the following with great success:
private void toolStrip1_Layout(System.Object sender, System.Windows.Forms.LayoutEventArgs e)
{
int width = toolStrip1.DisplayRectangle.Width;
foreach (ToolStripItem tsi in toolStrip1.Items) {
if (!(tsi == toolStripComboBox1)) {
width -= tsi.Width;
width -= tsi.Margin.Horizontal;
}
}
toolStripComboBox1.Width = Math.Max(0, width - toolStripComboBox1.Margin.Horizontal);
}
The above code does not suffer from the disapearing control problem.
There's no automatic layout option for this. But you can easily do it by implementing the ToolStrip.Resize event. This worked well:
private void toolStrip1_Resize(object sender, EventArgs e) {
toolStripComboBox1.Width = toolStripComboBox2.Bounds.Left - toolStripButton1.Bounds.Right - 4;
}
protected override void OnLoad(EventArgs e) {
toolStrip1_Resize(this, e);
}
Be sure to set the TSCB's AutoResize property to False or it won't work.
ToolStrip ts = new ToolStrip();
ToolStripComboBox comboBox = new TooLStripComboBox();
comboBox.Dock = DockStyle.Fill;
ts.LayoutStyle = ToolStripLayoutStyle.Table;
((TableLayoutSettings)ts.LayoutSettings).ColumnCount = 1;
((TableLayoutSettings)ts.LayoutSettings).RowCount = 1;
((TableLayoutSettings)ts.LayoutSettings).SetColumnSpan(comboBox,1);
ts.Items.Add(comboBox);
Now the combobox will dock fill correctly. Set Column or Row span accordingly.

Categories

Resources