How to restrict form movement to horizontal? - c#

I have a standard form with standard title bar that user may grab and move the form around. In certain situations I want to restrict this movement to horizontal only, so no matter how the mouse actually moves, the form remains on same Y coordinate.
To do this, I catch move event and when I detect deviation from Y, I move the form back to the original Y. Like that:
private void TemplateSlide_Move(object sender, EventArgs e)
{
int y = SlideSettings.LastLocation.Y;
if (y != this.Location.Y)
{
SlideSettings.LastLocation = new Point(this.Location.X, y);
this.Location=Settings.LastLocation;
}
}
But this causes a lot of flicker. Also because form actually moves for a brief moment away from desired Y this causes other issues specific to my program.
Is there a way to prevent form from moving away from desired Y coordinate?

Trap WM_MOVING and modify the RECT structure in LPARAM accordingly.
Something like:
public partial class Form1 : Form
{
public const int WM_MOVING = 0x216;
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private int OriginalY = 0;
private int OriginalHeight = 0;
private bool HorizontalMovementOnly = true;
public Form1()
{
InitializeComponent();
this.Shown += new EventHandler(Form1_Shown);
this.SizeChanged += new EventHandler(Form1_SizeChanged);
this.Move += new EventHandler(Form1_Move);
}
void Form1_Move(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_SizeChanged(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_Shown(object sender, EventArgs e)
{
this.SaveValues();
}
private void SaveValues()
{
this.OriginalY = this.Location.Y;
this.OriginalHeight = this.Size.Height;
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_MOVING:
if (this.HorizontalMovementOnly)
{
RECT rect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));
rect.Top = this.OriginalY;
rect.Bottom = rect.Top + this.OriginalHeight;
System.Runtime.InteropServices.Marshal.StructureToPtr(rect, m.LParam, false);
}
break;
}
base.WndProc(ref m);
}
}

When it's appropriate, just use the Mouse's Y coordinate, substituting the X coordinate with your static value. e.g.
... // e.g Mouse Down
originalX = Mouse.X; // Or whatever static X value you have.
... // e.g Mouse Move
// Y is dynamically updated while X remains static
YourObject.Location = new Point(originalX, Mouse.Y);

Related

how do I make beautiful flipping images?

When I opened the game launcher, I noticed how the news was implemented there.
And I really liked this idea, so I decided to do it in my project, first of all I made a panel, stuffed a few images into it, and then actually made two buttons with which I plan to flip through the images. BUT how to do it smoothly? Here is where the problem is, I do not understand how to make a smooth flipping of
the image
I can't tell how the images are sliding, flipping, stretching or whatever, but I think WinForms with GDI+ isn't the best choice. I think WPF would be better. I would also recommend using a suitable library for those kind of image manipulations.
However, if you want it very(!) simple you could use this class:
public class SlideAnimation
{
public event EventHandler<AnimationEventArgs> AnimationFinished;
private readonly Control Control;
private readonly Timer Timer;
private float fromXPosition;
public SlideAnimation(Control ctrl)
{
Control = ctrl;
Timer = new Timer();
Timer.Interval = 10;
Timer.Tick += Timer_Tick;
Control.Paint += Control_Paint;
}
public float Speed { get; set; }
public Image From { get; set; }
public Image To { get; set; }
public AnimationDirection Direction { get; set; }
public bool IsRunning
{
get
{
return Timer.Enabled;
}
}
public void StartAnimation()
{
// maybe move those checks into the setter of the corresponding property
if (this.From == null)
throw new InvalidOperationException();
if (this.To == null)
throw new InvalidOperationException();
if (this.Speed <= 0)
throw new InvalidOperationException();
fromXPosition = 0;
Timer.Enabled = true;
}
protected void OnAnimationFinished(AnimationEventArgs e)
{
AnimationFinished?.Invoke(this, e);
}
private void Timer_Tick(object sender, EventArgs e)
{
// increase or decrease the position of the first image
fromXPosition = fromXPosition + (this.Speed * this.Direction);
Control.Invalidate();
if (Math.Abs(fromXPosition) >= this.From.Width)
{
Timer.Enabled = false;
OnAnimationFinished(new AnimationEventArgs(this.Direction));
}
}
private void Control_Paint(object sender, PaintEventArgs e)
{
if (!Timer.Enabled)
return;
// draw both images next to each other depending on the direction
e.Graphics.DrawImage(this.From, new PointF(fromXPosition, 0));
e.Graphics.DrawImage(this.To, new PointF(fromXPosition - (this.From.Width * this.Direction), 0));
}
}
public enum AnimationDirection
{
Forward = -1,
Backward = 1
}
public class AnimationEventArgs : EventArgs
{
public AnimationEventArgs(AnimationDirection direction)
{
Direction = direction;
}
public AnimationDirection Direction { get; }
}
This class will only draw the images while the animation is active. Every other invalidation will not trigger the Control_Paint method.
Use following code for your Form:
public class Form1
{
private List<Image> imgList = new List<Image>();
private int currentIndex = 0;
private SlideAnimation animation;
public Slideshow()
{
InitializeComponent();
imgList.Add(Image.FromFile("pic1.bmp"));
imgList.Add(Image.FromFile("pic2.bmp"));
imgList.Add(Image.FromFile("pic3.bmp"));
imgList.Add(Image.FromFile("pic4.bmp"));
imgList.Add(Image.FromFile("pic5.bmp"));
animation = new SlideAnimation(this.Panel1);
animation.Speed = 20;
animation.AnimationFinished += AnimationFinished;
}
private void btnPrev_Click(object sender, EventArgs e)
{
if (currentIndex == 0)
return;
if (animation.IsRunning)
return;
animation.Direction = AnimationDirection.Backward;
animation.From = imgList[currentIndex];
animation.To = imgList[currentIndex - 1];
animation.StartAnimation();
}
private void btnNext_Click(object sender, EventArgs e)
{
if (currentIndex == imgList.Count - 1)
return;
if (animation.IsRunning)
return;
animation.Direction = AnimationDirection.Forward;
animation.From = imgList[currentIndex];
animation.To = imgList[currentIndex + 1];
animation.StartAnimation();
}
private void AnimationFinished(object sender, AnimationEventArgs e)
{
currentIndex = currentIndex - (1 * e.Direction);
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(imgList[currentIndex], 0, 0);
}
}
Since there are a lot of drawing operations you may use a panel which supports DoubleBuffer.
public class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
}
Keep in mind that this example is very simple and far from "fancy".

Tab height does not reflect high DPI on custom/userpaint TabControl

I have written a custom TabControl class, but I'm unable to make the tabs adapt their heights on high DPI screens. On a screen with 200% scaling, the tabs are halfway covered by the actual tab page and its controls, like this:
Apparently the TabControl doesn't adapt the tab heights to fit the larger font, and as a result, the top of the actual page is too high up and covers my tabs. What can I do to enforce the tabs to adapt?
The form has AutoScaleMode set to Dpi, and everything else looks fine, except for this. I'm targeting .NET 4.5.2, and the dpiAware setting is set to true in the manifest file.
Here is my code for the custom TabControl:
/// <summary>
/// A TabControl without 3D borders and other annoyances. Taken from
/// https://stackoverflow.com/questions/27469886/change-color-of-unused-space-of-tabcontrol/27472230
/// and modified.
/// </summary>
public class CleanTabControl : TabControl
{
private class NativeMethods
{
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}
private const int WM_SETFONT = 0x30;
private const int WM_FONTCHANGE = 0x1d;
private int hoverTab = -1;
public event MouseEventHandler CloseClick;
public CleanTabControl()
{
// Take over the painting completely, we want transparency and double-buffering
SetStyle(ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true);
DoubleBuffered = ResizeRedraw = true;
}
protected override void OnCreateControl()
{
// Necessary to give tabs the correct width
base.OnCreateControl();
OnFontChanged(EventArgs.Empty);
}
protected override void OnFontChanged(EventArgs e)
{
// Necessary to give tabs the correct width
base.OnFontChanged(e);
IntPtr hFont = Font.ToHfont();
NativeMethods.SendMessage(Handle, WM_SETFONT, hFont, (IntPtr)(-1));
NativeMethods.SendMessage(Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
UpdateStyles();
}
public override Color BackColor
{
// Override TabControl.BackColor, we need transparency
get { return Color.Transparent; }
set { base.BackColor = Color.Transparent; }
}
protected override void OnPaint(PaintEventArgs e)
{
// ... lot of painting code
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (SelectedTab != null)
{
if (GetImageRectangle(SelectedIndex).Contains(e.Location))
CloseClick(this, e);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
hoverTab = -1;
for (int i = 0; i < TabCount; i++)
{
if (GetTabRect(i).Contains(e.Location))
{
if (GetImageRectangle(i).Contains(e.Location))
TabPages[i].ImageIndex = 1;
else
{
hoverTab = i;
TabPages[i].ImageIndex = 0;
}
}
else
TabPages[i].ImageIndex = 0;
}
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
hoverTab = -1;
for (int i = 0; i < TabCount; i++)
{
TabPages[i].ImageIndex = 0;
}
Invalidate();
}
private Rectangle GetImageRectangle(int index)
{
Rectangle r = GetTabRect(index);
int width = ImageList.ImageSize.Width;
int height = ImageList.ImageSize.Height;
int x = r.Right - width - Padding.X;
int y = (r.Top + r.Height - height) / 2 + 1;
if (index != SelectedIndex)
y += 1;
return new Rectangle(x, y, width, height);
}
}
}
I found the solution. In OnCreateControl(), add:
ItemSize = new Size(ItemSize.Width, ItemSize.Height * DpiRatio);
where DpiRatio is the scale factor (e.g. 2 for 200% scaling).

Button needs to change status of an object, seems to work in debugging, but nothing happens?

So, I'm making a homework assignment in which I need to build an intersection along with automatic traffic lights, etc. in Windows Form.
Now, for testing purposes I made a button that changes the traffic light status from on to off (or vice-versa). The status is a enum called LampStatus which uses Aan (On), Uit (Off) and Storing (This doesn't need to get used yet).
The lamp class looks like this:
public class Lamp
{
protected Color kleur;
protected int x, y, straal;
protected LampStatus status;
public Color Kleur
{
get
{
if (status == LampStatus.Uit)
return Color.Gray;
else
return kleur;
}
set { kleur = value; }
}
public LampStatus Status
{
set
{
status = value;
}
get
{
return status;
}
}
// constructor
public Lamp()
{
kleur = Color.Red;
x = y = 0;
straal = 1;
status = LampStatus.Uit;
}
// constructor
public Lamp(Color kleur, int x, int y, int r)
{
this.kleur = kleur;
this.x = x;
this.y = y;
this.straal = r;
status = LampStatus.Uit;
}
public virtual void Teken(Graphics g)
{
if (g != null)
{
SolidBrush Brush = new SolidBrush(Kleur);
g.FillEllipse(Brush, x, y, straal, straal);
g.DrawEllipse(new Pen(Color.Black), x, y, straal, straal);
}
}
}
Now, when I press the button in Form1:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Lamp lamp = new Lamp(Color.Red, 15, 15, 50);
private void Form1_Paint(object sender, PaintEventArgs e)
{
lamp.Teken(e.Graphics);
}
private void testButton_Click(object sender, EventArgs e)
{
if (lamp.Status == LampStatus.Uit)
lamp.Status = LampStatus.Aan;
else
lamp.Status = LampStatus.Uit;
}
}
Nothing seems to happen, though when I debug the object lamp, both the color has changed to Color.Red and the status has changed to LampStatus.Aan.
When I hardcode: lamp.Status = LampStatus.Aan in the Form1_Paint method the color does change to red.
Edit; if there is any confusion, just comment and I'll try to explain.
Using this.Refresh() fixed the problem and made the lamp color properly change.

How do I control two listboxes using a Vertical Scrollbar?

I've got a Windows Form that has 6 listboxes in it.
I'm trying to find / make a code that would make them scroll together. So I dropped a Vertical Scroll Bar onto the form, then put in the following code:
private void vScrollR_Scroll(object sender, ScrollEventArgs e)
{
int i = vScrollR.Value;
lstcr1.SelectedIndex = i;
lstpr1.SelectedIndex = i;
lstsr1.SelectedIndex = i;
lstcr2.SelectedIndex = i;
lstpr2.SelectedIndex = i;
lstsr2.SelectedIndex = i;
}
For some reason though, it won't work (i always returns 0). Am I going about this the wrong way? Is there any other way to achieve what I want? Perhaps, there's a method I need first?
Many thanks to all who will answer.
Change SelectedIndex to TopIndex. I just tried this and it works.
To keep the UI in sync while updating, you can use Control.BeginUpdate and Control.EndUpdate
listBox1.BeginUpdate();
listBox2.BeginUpdate();
listBox1.TopIndex =
listBox2.TopIndex = ++x;
listBox1.EndUpdate();
listBox2.EndUpdate();
Try to Create a Separate Class that Inherits from Listbox.
I hope that this will help you.
using System;
using System.Windows.Forms;
public class myScrollingListBox : ListBox
{
// Event declaration
public delegate void myScrollingListBoxDelegate(object Sender, myScrollingListBoxScrollArgs e);
public event myScrollingListBoxDelegate Scroll;
// WM_VSCROLL message constants
private const int WM_VSCROLL = 0x0115;
private const int SB_THUMBTRACK = 5;
private const int SB_ENDSCROLL = 8;
protected override void WndProc(ref Message m)
{
// Trap the WM_VSCROLL message to generate the Scroll event
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL)
{
int nfy = m.WParam.ToInt32() & 0xFFFF;
if (Scroll != null && (nfy == SB_THUMBTRACK || nfy == SB_ENDSCROLL))
Scroll(this, new myScrollingListBoxScrollArgs(this.TopIndex, nfy == SB_THUMBTRACK));
}
}
public class myScrollingListBoxScrollArgs
{
// Scroll event argument
private int mTop;
private bool mTracking;
public myScrollingListBoxScrollArgs(int top, bool tracking)
{
mTop = top;
mTracking = tracking;
}
public int Top
{
get { return mTop; }
}
public bool Tracking
{
get { return mTracking; }
}
}
}

Round shaped buttons

How do I make a button in a round shape rather than the conventional rectangle.
I am using winforms(2.0)
First make a class. Give it name: "RoundButton".
Then write the code directly as this:
using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Linq;
using System.Text;
namespace WindowsFormsApplication1
{
public class RoundButton : Button
{
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(0, 0, ClientSize.Width, ClientSize.Height);
this.Region = new System.Drawing.Region(grPath);
base.OnPaint(e);
}
}
}
Then, build your application and close this.
Now go to the toolbox and you will see a control named RoundButton.
Then drag and drop this on your Windows form and test it.
GraphicsPath p = new GraphicsPath();
p.AddEllipse(1, 1, button1.Width - 4, button1.Height - 4);
button1.Region = new Region(p);
Code project has many articles about these kinds of things, especially the article RoundButton Windows Control - Ever Decreasing Circles might be of interest since it shows you have to do different kinds of round buttons.
This or this could help if you need to implement your own Button class based on the default Windows Forms button. You can also search online for more examples.
What about 'GDI'?
Implementation Example:
#region <Round Corners> : (Properties)
// [Use Round Corners]
private bool useRoundCorners = false;
[Category("Control Corners"), DisplayName("Round Corners")]
[Description("Set Round Corners.")]
[Browsable(true)]
public bool UseRoundBorders
{
get { return useRoundCorners; }
set { if (useRoundCorners != value) { useRoundCorners = value; Invalidate(); } }
}
// [Ellipse Radius]
private int ellipseRadius = 20;
[Category("Control Corners"), DisplayName("Radius")]
[Description("Set Corner (Ellipse) Radius")]
[Browsable(true)]
public int EllipseRadius
{
get { return ellipseRadius.FixedValue(0, 90); }
set { if (ellipseRadius != value.FixedValue(0, 90)) { ellipseRadius = value; Invalidate(); } }
}
#endregion
#region <Round Corners> : (Draw)
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner-
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // width of ellipse
int nHeightEllipse // height of ellipse
);
/// <summary> Draw Corners (Round or Square). </summary>
/// <param name="e"></param>
private void DrawCorners()
{
if (useRoundCorners) { this.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, ellipseRadius, ellipseRadius)); }
else { this.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 0, 0)); }
}
#endregion
/// <summary> Redraw (Update) the Control. </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawCorners();
}
Extension to Limit Round Corner Radius (*Range: 0-90)
Author: Salvador
// Extension to Set Min. & Max. Values to Properties
public static class Extensions
{
public static int FixedValue(this int value, int min, int max)
{
if (value >= min && value <= max) { return value; }
else if (value > max) { return max; }
else if (value < min) { return min; }
else { return 1; }
}
}
This is what you want
public class RoundButton : Control
{
private readonly Label lbl;
public RoundButton() : base()
{
lbl = new Label
{
Text = Text,
ForeColor = ForeColor,
BackColor = BackColor,
Font = Font
};
CenterInParent();
}
private void CenterInParent()
{
lbl.Left = (Width - lbl.Width) / 2;
lbl.Top = (Height - lbl.Height) / 2;
}
protected override void OnPaint(PaintEventArgs e)
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(0, 0, ClientSize.Width, ClientSize.Height);
Region = new Region(grPath);
base.OnPaint(e);
}
protected override void OnMove(EventArgs e)
{
CenterInParent();
base.OnMove(e);
}
protected override void OnTextChanged(EventArgs e)
{
lbl.Text = Text;
base.OnTextChanged(e);
}
protected override void OnForeColorChanged(EventArgs e)
{
lbl.ForeColor = ForeColor;
base.OnForeColorChanged(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
lbl.BackColor = BackColor;
base.OnBackColorChanged(e);
}
protected override void OnFontChanged(EventArgs e)
{
lbl.Font = Font;
base.OnFontChanged(e);
}
}
Pros:
No cuts
Round
Cons:
Wrong designer loader for round button
public class OptionsMenu : Button
{
public OptionsMenu(int NoOfOptions, Point Location, ControlCollection controls,
Size ButtonSize, int DistanceBetweenOptions)
{
Button[] buttons = new Button[NoOfOptions];
for (int i = 0; i < NoOfOptions; i++)
{
buttons[i] = new Button()
{
Size = ButtonSize,
};
GraphicsPath p = new GraphicsPath();
p.AddEllipse(1, 1, buttons[i].Width - 4, buttons[i].Height - 4);
buttons[i].Region = new Region(p);
buttons[i].Location = new Point(Location.X, Location.Y + DistanceBetweenOptions * i);
controls.Add(buttons[i]);
}
}
}
You can call it like this:
OptionsMenu menu = new OptionsMenu(4, new Point(50, 50), Controls, new Size(20, 20), 40);
Controls.AddRange(new Control[]
{
menu
});

Categories