Stop flickering when the rich text box is resized - c#

The following is causing a flicker when the height goes over 810.
How can I prevent this from happening?
private const int EM_GETLINECOUNT = 0xba;
[DllImport("user32",EntryPoint = "SendMessageA",CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)]
private static extern int SendMessage(int hwnd,int wMsg,int wParam,int lParam);
private void rtbScript_TextChanged(object sender,EventArgs e)
{
var numberOfLines = SendMessage(rtbScript.Handle.ToInt32(),EM_GETLINECOUNT,0,0);
this.rtbScript.Height = (rtbScript.Font.Height + 2) * numberOfLines;
if(this.rtbScript.Height>810)
{
this.rtbScript.Height = 810;
}
}

You set the Height two times, instead of one, causing the Control to repaint itself twice.
To prevent that effect store your calculation of the new height and then assign only once.
private void rtbScript_TextChanged(object sender,EventArgs e)
{
var numberOfLines = SendMessage(rtbScript.Handle.ToInt32(),EM_GETLINECOUNT,0,0);
var newHeight = (rtbScript.Font.Height + 2) * numberOfLines;
if(newHeight>810)
{
this.rtbScript.Height = 810;
}
else
{
this.rtbScript.Height = newHeight;
}
}

Try this: https://stackoverflow.com/a/3718648/5106041
The reason it flickers is because winforms doesn't do double buffering by default, that's one of the reasons WPF was created, not only it fixes these issues (we get some new ones thou) but you'll have a much richer layout system.

Related

Sliding panels - one works, the others don't

I've run into a bit of a wall and I don't know how I've managed to stuff it up. I'm trying to have multiple panels on my application in C# and each slides in and out from the menu along the side. I've written a separate slide class:
class Slide
{
Panel pane;
Button btn;
bool hidden;
Timer t;
const int maxWidth = 315;
public Slide(Panel p, Button b)
{
this.pane = p;
this.btn = b;
hidden = true;
btn.Click += new EventHandler(btnClick);
t = new Timer();
t.Interval = 15;
t.Tick += new EventHandler(timeTick);
}
private void timeTick(object sender, EventArgs e)
{
if(hidden)
{
SlidingPane(+10);
}
else
{
SlidingPane(-10);
}
}
private void btnClick(object sender, EventArgs e)
{
t.Start();
}
private void SlidingPane(int i)
{
pane.Width += i;
if(pane.Width >= maxWidth || pane.Width <= 0)
{
t.Stop();
hidden = !hidden;
}
}
}
And I've initialised the panels as follows:
Slide menuP, calendarP, peopleP, taskP, settingsP;
public Form1()
{
InitializeComponent();
ButtonColours();
InitialisePanes();
}
private void InitialisePanes()
{
menuP = new Slide(menuPane, menuButton);
calendarP = new Slide(calendarPane, calendarButton);
peopleP = new Slide(peoplePane, peopleButton);
taskP = new Slide(taskPane, toDoButton);
settingsP = new Slide(settingsPane, settingsButton);
}
And here's the Form designer code for the working panel:
this.menuPane.BackColor = System.Drawing.Color.SlateGray;
this.menuPane.Controls.Add(this.peoplePane);
this.menuPane.Dock = System.Windows.Forms.DockStyle.Left;
this.menuPane.Location = new System.Drawing.Point(67, 0);
this.menuPane.Name = "menuPane";
this.menuPane.Size = new System.Drawing.Size(0, 652);
this.menuPane.TabIndex = 2;
And the others are exactly the same. Eg:
this.peoplePane.BackColor = System.Drawing.Color.SlateGray;
this.peoplePane.Controls.Add(this.calendarPane);
this.peoplePane.Dock = System.Windows.Forms.DockStyle.Left;
this.peoplePane.Location = new System.Drawing.Point(67, 0);
this.peoplePane.Name = "peoplePane";
this.peoplePane.Size = new System.Drawing.Size(0, 652);
this.peoplePane.TabIndex = 2;
I've started up my application and I click on the menuButton, it works. Slides in and out beautifully. I click on the others and....nothing happens.
Can anyone see why this is happening? Everything I'm looking at tells me that it should be working.
To make sure all panes are correctly aligned with each other and (not) nested you could use code like this:
foreach( Control ctl in new[] { peoplePane, calendarPane, taskPane, settingsPane })
{
ctl.Parent = menuPane.Parent;
ctl.Location = menuPane.Location;
}
It assumes that menuPane is at the right spot and makes all others sit right on top without nesting them.
The code you posted contained incorrect nesting and moving panels to the same spot with the mouse will also create (in this case unwanted) nesting. Moving with the keyboard avoids it but is tedious.

How to make an animation look consistent and smooth

`So I have a total of 6 images that when animated, creates the illusion of a person walking. The problem is its not smooth, refresh(), invalidate(),update() have failed me. How do I go about this.
namespace Runner
{
public partial class Form1 : Form
{
Keys moveRight;
Keys moveLeft;
public static bool isMovingR = false;
public static bool isMovingL = false;
Bitmap stnd = new Bitmap(Properties.Resources.Standing);
static Bitmap wlk_1_RL = new Bitmap(Properties.Resources.Walk_1_RL);
static Bitmap wlk_2_RL = new Bitmap(Properties.Resources.Walk_2_RL);
static Bitmap wlk_3_RL = new Bitmap(Properties.Resources.Walk_3_RL);
static Bitmap wlk_4_LL = new Bitmap(Properties.Resources.Walk_4_LL);
static Bitmap wlk_5_LL = new Bitmap(Properties.Resources.Walk_5__LL);
static Bitmap wlk_6_LL = new Bitmap(Properties.Resources.Walk_6_LL);
Graphics gfx;
Animation animate = new Animation(new Bitmap[] { wlk_1_RL, wlk_2_RL, wlk_3_RL,
wlk_4_LL, wlk_5_LL, wlk_6_LL });
Timer timer = new Timer();
int imageX = 5;
int imageY = 234;
public Form1()
{
InitializeComponent();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
moveRight = Keys.D;
moveLeft = Keys.A;
if (Keys.D == moveRight)
{
isMovingR = true;
timer.Enabled = true;
timer.Interval = 50;
timer.Tick += timer1_Tick;
//imageX += 5;
Refresh();
} else if (Keys.A == moveLeft)
{
isMovingL = true;
imageX -= 5;
Refresh();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
gfx = this.CreateGraphics();
gfx.DrawImage(animate.Frame2Draw(), imageX, imageY);
//Refresh(); Invalidate(); Update();
}
}
}
Now, the problem is being able to make the animation consistent and smooth
UPDATE...animation class
public class Animation
{
int slctdImg = 0;
public static Bitmap[] images;
Bitmap frame2Draw;
public Animation(Bitmap[] frames)
{
foreach (Bitmap btm in frames)
{
btm.MakeTransparent();
}
images = frames;
}
public Bitmap Frame2Draw()
{
if (slctdImg < images.Length)
{
frame2Draw = images[slctdImg];
slctdImg++;
}
if (slctdImg >= images.Length)
{
slctdImg = 0;
frame2Draw = images[slctdImg];
}
return frame2Draw;
}
}
Many issues:
I wonder why you would call MakeTransparent on each Tick?? I doubt it does what you expect.. It is changing pixels and rather expensive; you ought to cache the images instead.
Nor why you create an array of bitmap on each Tick??? And since it is always created the same way it always displays only the 1st image.. This should answer your question.
Further issues:
Using this.CreateGraphics(); will fail to create a persistent result although that may not be your aim as you try to animate.
Remember that a Timer.Interval can't run faster than 15-30 ms; also that winforms is notoriously bad at animation.
Remember that c# is zero-based, so this slctdImg > images.Length should probably be slctdImg >= images.Length
Here is what you should do instead:
Move the instantiation either to the form load or maybe to a key event.
Move the drawing to the Paint event of the form.
In the Tick count up frames and/or position and trigger the Paint by calling Invalidate on the form!
Update:
One more issue is the way you hook up the Tick event each time the right key is pressed. Hooking up an event multiple times will result in it running multiple times; this will create to gaps/jumps in your animation..
Either add an unhook each time before hooking up (it will fail quietly the 1st time)
timer.Tick -= timer1_Tick;
timer.Tick += timer1_Tick;
or (better) hook it up only once in the original set up!

Animated Panel in C#

I am trying to add a panel when a button click. My code is below and I did it. But now I am trying to put on my panel other buttons etc and when you click the first button and the panel slide in there aren't any of my new buttons.
//Constants
const int AW_SLIDE = 0X40000;
const int AW_HOR_POSITIVE = 0X1;
const int AW_HOR_NEGATIVE = 0X2;
const int AW_BLEND = 0X80000;
[DllImport("user32")]
static extern bool AnimateWindow(IntPtr hwnd, int time, int flags);
photosflag=0;
private void photosbutton_Click(object sender, EventArgs e)
{
if (photosflag == 0)
{
object O = Controller.Properties.Resources.ResourceManager.GetObject("photospressed");
photosbutton.Image = (System.Drawing.Image)O;
photosflag = 1;
int ylocation = photosbutton.Location.Y;
//Set the Location
photospanel.Location = new Point(101, ylocation);
//Animate form
AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);
}
else
{
object O = Controller.Properties.Resources.ResourceManager.GetObject("photos");
photosbutton.Image = (System.Drawing.Image)O;
photosflag = 0;
photospanel.Visible = false;
}
}
In the photos panel, I have three picture boxes. But when the panel shows up (slide-in) the picture boxes there aren't exist.
Okay - here is a really simple example that doesn't depend on the AnimateWindow API:
Add a timer control to your form. On mine, I set the interval to 10 (milliseconds). You can play with this value to smooth out the animation as necessary
I have the button and panel (not visible) on the form
I declared the following private members on the form - they are the start X position of the panel, the end position, and the number of pixels to move per increment - again, tweak to affect speed/smoothness/etc
private int _startLeft = -200; // start position of the panel
private int _endLeft = 10; // end position of the panel
private int _stepSize = 10; // pixels to move
Then on the button click, I enable the timer:
animationTimer.Enabled = true;
Finally, the code in the timer tick event makes the panel visible, moves it into place, and disables itself when done:
private void animationTimer_Tick(object sender, EventArgs e)
{
// if just starting, move to start location and make visible
if (!photosPanel.Visible)
{
photosPanel.Left = _startLeft;
photosPanel.Visible = true;
}
// incrementally move
photosPanel.Left += _stepSize;
// make sure we didn't over shoot
if (photosPanel.Left > _endLeft) photosPanel.Left = _endLeft;
// have we arrived?
if (photosPanel.Left == _endLeft)
{
animationTimer.Enabled = false;
}
}
I know this is an old thread, but there’s an easy fix for the photos not showing. The panel is originally set to visible = false, and because AnimateWindow doesn't actually show the full control, so make sure you set control.Visible = true after calling AnimateWindow.
so after the code line:
//Animate form
AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);
// just add this:
photopanel.Visible = true;
// or in one line
Photopanel.Visible = AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);

How to maintain the default font size of Windows Forms application even after the entire Windows Text size is changed from Small to Medium?

I have a windows form application where the font size changes based on the text size is changed for the entire windows. How do I maintain the default font size in my Windows Form application even the text size for entire windows is changed from Small to Medium ? I already used AutoScalMode to None, but the font size overlaps a control if they are very close to each other.
Thanks
Unfortunately it's a common problem with WinForms.
If I understood your problem - problem occurs when you manually set font in WinForms control f.e. Label.
According to this answer, i'd written code below. Maybe it would help
Getting actual Dpi display size:
public class FontResizeFactorProvider
{
private const int DpiSmallSize = 96;
private const int DpiMediumSize = 120;
private const int DpiLargeSize = 144;
public float GetFontResizeFactor()
{
var graphics = Graphics.FromHwnd(IntPtr.Zero);
IntPtr desktop = graphics.GetHdc();
var displayDimensions = new List<int>
{
GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSX),
GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSY)
};
var matchedDim = displayDimensions.First(dim => dim == DpiSmallSize || dim == DpiMediumSize || dim == DpiLargeSize);
if (matchedDim == default(int))
{
throw new ArgumentException("Dpi size not standard.");
}
var resizeFactor = (float) DpiSmallSize/matchedDim;
return resizeFactor;
}
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
public enum DeviceCap
{
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90
// Other constants may be founded on pinvoke.net
}
}
Service responsible for re-sizing fonts in Form:
public class FormFontsResizeService
{
private const double EPSILON = 0.1;
private readonly FontResizeFactorProvider _fontResizeFactorProvider;
public FormFontsResizeService():this(new FontResizeFactorProvider())
{
}
public FormFontsResizeService(FontResizeFactorProvider fontResizeFactorProvider)
{
_fontResizeFactorProvider = fontResizeFactorProvider;
}
public void ResizeControlFonts(Form form)
{
var resizeFactor = _fontResizeFactorProvider.GetFontResizeFactor();
foreach (Control control in form.Controls)
{
var baseFont = control.Font;
if (Math.Abs(baseFont.Size - SystemFonts.DefaultFont.Size) < EPSILON)
{
continue;
}
var scaledFont = new Font(baseFont.FontFamily, baseFont.Size*resizeFactor, baseFont.Style);
control.Font = scaledFont;
}
}
}
Usage in Form:
protected override void OnShown(System.EventArgs e)
{
var formFontsResizeService = new FormFontsResizeService();
formFontsResizeService.ResizeControlFonts(this);
}
Also Check this question.

C# Implementing Auto-Scroll in a ListView while Drag & Dropping

How do I implement Auto-Scrolling (e.g. the ListView scrolls when you near the top or bottom) in a Winforms ListView? I've hunted around on google with little luck. I can't believe this doesn't work out of the box!
Thanks in advance
Dave
Thanks for the link (http://www.knowdotnet.com/articles/listviewdragdropscroll.html), I C#ised it
class ListViewBase:ListView
{
private Timer tmrLVScroll;
private System.ComponentModel.IContainer components;
private int mintScrollDirection;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
const int WM_VSCROLL = 277; // Vertical scroll
const int SB_LINEUP = 0; // Scrolls one line up
const int SB_LINEDOWN = 1; // Scrolls one line down
public ListViewBase()
{
InitializeComponent();
}
protected void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.tmrLVScroll = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// tmrLVScroll
//
this.tmrLVScroll.Tick += new System.EventHandler(this.tmrLVScroll_Tick);
//
// ListViewBase
//
this.DragOver += new System.Windows.Forms.DragEventHandler(this.ListViewBase_DragOver);
this.ResumeLayout(false);
}
protected void ListViewBase_DragOver(object sender, DragEventArgs e)
{
Point position = PointToClient(new Point(e.X, e.Y));
if (position.Y <= (Font.Height / 2))
{
// getting close to top, ensure previous item is visible
mintScrollDirection = SB_LINEUP;
tmrLVScroll.Enabled = true;
}else if (position.Y >= ClientSize.Height - Font.Height / 2)
{
// getting close to bottom, ensure next item is visible
mintScrollDirection = SB_LINEDOWN;
tmrLVScroll.Enabled = true;
}else{
tmrLVScroll.Enabled = false;
}
}
private void tmrLVScroll_Tick(object sender, EventArgs e)
{
SendMessage(Handle, WM_VSCROLL, (IntPtr)mintScrollDirection, IntPtr.Zero);
}
}
Scrolling can be accomplished with the ListViewItem.EnsureVisible method.
You need to determine if the item you are currently dragging over is at the visible boundary of the list view and is not the first/last.
private static void RevealMoreItems(object sender, DragEventArgs e)
{
var listView = (ListView)sender;
var point = listView.PointToClient(new Point(e.X, e.Y));
var item = listView.GetItemAt(point.X, point.Y);
if (item == null)
return;
var index = item.Index;
var maxIndex = listView.Items.Count;
var scrollZoneHeight = listView.Font.Height;
if (index > 0 && point.Y < scrollZoneHeight)
{
listView.Items[index - 1].EnsureVisible();
}
else if (index < maxIndex && point.Y > listView.Height - scrollZoneHeight)
{
listView.Items[index + 1].EnsureVisible();
}
}
Then wire this method to the DragOver event.
targetListView.DragOver += RevealMoreItems;
targetListView.DragOver += (sender, e) =>
{
e.Effect = DragDropEffects.Move;
};
Have a look at ObjectListView. It does this stuff. You can read the code and use that if you don't want to use the ObjectListView itself.
Just a note for future googlers: to get this working on more complex controls (such as DataGridView), see this thread.

Categories