Is the size already updated in OnResize()? - c#

I'm writing a PlotBox control, which inherits from a PictureBox. Every time the control changes its size, I want to redraw the plot. If I do something like that:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Replot();
}
private void Replot()
{
//logic goes here, for example:
int width = Size.Width;
int height = Size.Height;
}
Will the width and height variables hold the new control size? Are there any additional problems I should have in mind?

The size will already have been updated by the time your Replot() method is called.

Related

Growing Rectangle

I have a growing rectangle drawn on top of a TableLayoutPanel but when it grows it causes a horrible flicker even with Double Buffer.
I am using e.Graphics.FillRectangle and a global variable that increases the size of the rectangle. I set up a timer to make it grow every 1/10th of a second by 1 pixel. Why does it flicker so much and how can I stop the flicker?
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width-grow)/2, (tableLayoutPanel1.Height-grow)/2, grow,grow);
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Refresh();
}
In order to rule out all other possibilities I removed everything from my program and started from scratch with just a growing rectangle and it still creates this horrible flicker.
Ok, here is the code. You first need to make background buffer Bitmap with the size of your control. After that, you will need to draw everything on the Bitmap, and than draw that bitmap onto the control.
Bitmap backBuffer = null;
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
if (backBuffer == null)
backBuffer = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
Graphics g = Graphics.FromImage(backBuffer);
g.Clear(tableLayoutPanel1.BackColor);
g.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width - grow) / 2, (tableLayoutPanel1.Height - grow) / 2, grow, grow);
e.Graphics.DrawImage(backBuffer, 0, 0, backBuffer.Width, backBuffer.Height);
g.Dispose();
}
private void tableLayoutPanel1_Resize(object sender, EventArgs e)
{
backBuffer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Invalidate();
}
Note that you will need to create new Bitmap each time you resize the TableLayoutPanel. In addition I suggest using Invalidate() instead of Refresh().
But this will still include some potential flickering. In order to completely avoid flicker, in addition to the previous code, you will need to subclass the TableLayoutPanel and override the OnPaintBackground() method in such a way that base.OnPaintBackground is never called. This way way won't have any flickering at all. The reason why you have the flickering is because the background is redrawn before your Rectangle, any time you draw it. Switch the original TableLayoutPanel class with this BackgroundlessTableLayoutPanel
public class BackgroundlessTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Most controls have a Paint event where you can implement any custom drawing you need to do. It is also possible to implement your own control where you override the OnPaint method. See the article here.
Both these should give ok results.

Dynamic Fontsize for TextBlock with wrapping

I have a TextBlock with a fixed size thats wrapping text. sometimes short sometimes long.
If the text is getting to long it isnt displayed entirely like this
How can i make the Fontsize flexible to make the text fit the TextBox with static size?
My solution is the following:
Set the fontsize to a value, than which you don't want any bigger.
The ActualHeight of the TextBlock changes, when you change the font size or when the content is changed. I built the solution based upon this.
You should create an event handler for the SizeChanged event and write the following code to it.
private void MyTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
double desiredHeight = 80; // Here you'll write the height you want the text to use
if (this.MyTextBlock.ActualHeight > desiredHeight)
{
// You want to know, how many times bigger the actual height is, than what you want to have.
// The reason for Math.Sqrt() is explained below in the text.
double fontsizeMultiplier = Math.Sqrt(desiredHeight / this.MyTextBlock.ActualHeight);
// Math.Floor() can be omitted in the next line if you don't want a very tall and narrow TextBox.
this.MyTextBlock.FontSize = Math.Floor(this.MyTextBlock.FontSize * fontsizeMultiplier);
}
this.MyTextBlock.Height = desiredHeight; // ActualHeight will be changed if the text is too big, after the text was resized, but in the end you want the box to be as big as the desiredHeight.
}
The reason why I used the Math.Sqrt() is that if you set the font size to half as big as before, then the area that the font will use, will be one quarter the size, then before (because it became half as wide and half as tall as before). And you obviously want to keep the width of the TextBox and only change the height of it.
If you were lucky, the font size will be appropriate after this method gets executed once. However, depending on the text that gets re-wrapped after the font size change, you might be so "unlucky", that the text will be one line longer than you would want it to be.
Luckily the event handler will be called again (because you changed the font size) and the resizing will be done again if it is still too big.
I tried it, it was fast and the results looked good.
However, I can imagine, that in a really unlucky choice of text and height, the correct font size would be reached after several iterations. This is why I used Math.Floor(). All in all, it doesn't matter much if the font size is in the end 12.34 or 12 and this way I wouldn't be concerned about an "unlucky" text, which will take too long to render.
But I think Math.Floor() can be omitted if you don't want to have a really tall text box (like 2000 pixels) with a lot of text.
Here's a full solution including an option to set maxheight / maxwidth and and it is calculated straight on render:
public class TextBlockAutoShrink : TextBlock
{
private double _defaultMargin = 6;
private Typeface _typeface;
static TextBlockAutoShrink()
{
TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
}
public TextBlockAutoShrink() : base()
{
_typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
}
private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var t = sender as TextBlockAutoShrink;
if (t != null)
{
t.FitSize();
}
}
void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FitSize();
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
FitSize();
base.OnRenderSizeChanged(sizeInfo);
}
private void FitSize()
{
FrameworkElement parent = this.Parent as FrameworkElement;
if (parent != null)
{
var targetWidthSize = this.FontSize;
var targetHeightSize = this.FontSize;
var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;
if (this.ActualWidth > maxWidth)
{
targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
}
if (this.ActualHeight > maxHeight)
{
var ratio = maxHeight / (this.ActualHeight);
// Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
// And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;
targetHeightSize = (double)(this.FontSize * ratio);
}
this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
}
}
}

New area not repaints when user-drawn control size is increased

I think I'm missing something trivial here. I derived simple control directly from Control. I'm overriding OnPaint and painting the rectangle (e.Graphics.DrawRectangle)and a text inside it (e.Graphics.DrawString). I did not override any other members.
It paints itself well when the control is resized to the smaller size, but when it gets resized to the larger size, new area is not repainted properly. As soon as I resize it to the smaller size again, even if by one pixel, everything repaints correctly.
OnPaint gets called properly (with appropriate PaintEventArgs.ClipRectangle set correctly to new area), but the new area is not painted (artifacts appear) anyway.
What am I missing?
EDIT:
Code:
protected override void OnPaint(PaintEventArgs e)
{
// Adjust control's height based on current width, to fit current text:
base.Height = _GetFittingHeight(e.Graphics, base.Width);
// Draw frame (if available):
if (FrameThickness != 0)
{
e.Graphics.DrawRectangle(new Pen(FrameColor, FrameThickness),
FrameThickness / 2, FrameThickness / 2, base.Width - FrameThickness, base.Height - FrameThickness);
}
// Draw string:
e.Graphics.DrawString(base.Text, base.Font, new SolidBrush(base.ForeColor), new RectangleF(0, 0, base.Width, base.Height));
}
private int _GetFittingHeight(Graphics graphics, int width)
{
return (int)Math.Ceiling(graphics.MeasureString(base.Text, base.Font, width).Height);
}
Try adding this in your constructor:
public MyControl() {
this.ResizeRedraw = true;
this.DoubleBuffered = true;
}
and in your paint event, clear the previous drawing:
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(SystemColors.Control);
// yada-yada-yada
}
While ResizeRedraw will work, it forces the entire control to repaint for every resize event, rather than only painting the area that was revealed by the resize. This may or may not be desirable.
The problem the OP was having is caused by the fact that the old rectangle does not get invalidated; only the revealed area gets repainted, and old graphics stay where they were. To correct this, detect whether the size of your rectangle has increased vertically or horizontally, and invalidate the appropriate edge of the rectangle.
How you would specifically go about this would depend on your implementation. You would need to have something that erases the old rectangle edge and you would have to call Invalidate passing an area containing the old rectangle edge. It may be somewhat complicated to get it to work properly, depending what you're doing, and using ResizeRedraw after all may be much simpler if the performance difference is negligible.
Just for example, here is something you can do for this problem when drawing a border.
// member variable; should set to initial size in constructor
// (side note: should try to remember to give your controls a default non-zero size)
Size mLastSize;
int borderSize = 1; // some border size
...
// then put something like this in the resize event of your control
var diff = Size - mLastSize;
var wider = diff.Width > 0;
var taller = diff.Height > 0;
if (wider)
Invalidate(new Rectangle(
mLastSize.Width - borderSize, // x; some distance into the old area (here border)
0, // y; whole height since wider
borderSize, // width; size of the area (here border)
Height // height; all of it since wider
));
if (taller)
Invalidate(new Rectangle(
0, // x; whole width since taller
mLastSize.Height - borderSize, // y; some distance into the old area
Width, // width; all of it since taller
borderSize // height; size of the area (here border)
));
mLastSize = Size;

Is it Possible to set the height smaller than 21 pixels for the System.Windows.Forms.Combobox-Control

Hello Community,
i've a problem with the height of the System.Windows.Forms.Combobox-Control. I can't change it. I want to use that to write an own implementation (owner drawn custom control).
The following code does not work for me (It's only to try). The height is still 21px!
public class TestBox : ComboBox
{
public TestBox()
{
DropDownHeight = 15;
}
protected override Size DefaultSize
{
get
{
return new Size(15,15);
}
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, 15, 15, specified);
}
}
Please help me.
Regars,
Marco
The ComboBox height should be resized
based on the font that is assigned to
it.
So, change the combo font. see another discussion on this subject.
ComboBox's MinimumSize property is coded like this:
public override Size MinimumSize
{
get
{
return base.MinimumSize;
}
set
{
// can see that Height is not taken in consideration - is 0
base.MinimumSize = new Size(value.Width, 0);
}
}

How do I make a PictureBox use Nearest Neighbor resampling?

I am using StretchImage because the box is resizable with splitters. It looks like the default is some kind of smooth bilinear filtering, causing my image to be blurry and have moire patterns.
I needed this functionality also. I made a class that inherits PictureBox, overrides OnPaint and adds a property to allow the interpolation mode to be set:
using System.Drawing.Drawing2D;
using System.Windows.Forms;
/// <summary>
/// Inherits from PictureBox; adds Interpolation Mode Setting
/// </summary>
public class PictureBoxWithInterpolationMode : PictureBox
{
public InterpolationMode InterpolationMode { get; set; }
protected override void OnPaint(PaintEventArgs paintEventArgs)
{
paintEventArgs.Graphics.InterpolationMode = InterpolationMode;
base.OnPaint(paintEventArgs);
}
}
I suspect you're going to have to do the resizing manually thru the Image class and DrawImage function and respond to the resize events on the PictureBox.
I did a MSDN search and turns out there's an article on this, which is not very detailed but outlines that you should use the paint event.
http://msdn.microsoft.com/en-us/library/k0fsyd4e.aspx
I edited a commonly available image zooming example to use this feature, see below
Edited from: http://www.dotnetcurry.com/ShowArticle.aspx?ID=196&AspxAutoDetectCookieSupport=1
Hope this helps
private void Form1_Load(object sender, EventArgs e)
{
// set image location
imgOriginal = new Bitmap(Image.FromFile(#"C:\images\TestImage.bmp"));
picBox.Image = imgOriginal;
// set Picture Box Attributes
picBox.SizeMode = PictureBoxSizeMode.StretchImage;
// set Slider Attributes
zoomSlider.Minimum = 1;
zoomSlider.Maximum = 5;
zoomSlider.SmallChange = 1;
zoomSlider.LargeChange = 1;
zoomSlider.UseWaitCursor = false;
SetPictureBoxSize();
// reduce flickering
this.DoubleBuffered = true;
}
// picturebox size changed triggers paint event
private void SetPictureBoxSize()
{
Size s = new Size(Convert.ToInt32(imgOriginal.Width * zoomSlider.Value), Convert.ToInt32(imgOriginal.Height * zoomSlider.Value));
picBox.Size = s;
}
// looks for user trackbar changes
private void trackBar1_Scroll(object sender, EventArgs e)
{
if (zoomSlider.Value > 0)
{
SetPictureBoxSize();
}
}
// redraws image using nearest neighbour resampling
private void picBox_Paint_1(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
e.Graphics.DrawImage(
imgOriginal,
new Rectangle(0, 0, picBox.Width, picBox.Height),
// destination rectangle
0,
0, // upper-left corner of source rectangle
imgOriginal.Width, // width of source rectangle
imgOriginal.Height, // height of source rectangle
GraphicsUnit.Pixel);
}
When resizing an image in .net, the System.Drawing.Drawing2D.InterpolationMode offers the following resize methods:
Bicubic
Bilinear
High
HighQualityBicubic
HighQualityBilinear
Low
NearestNeighbor
Default

Categories