I have a Panel In my C# form and I have a button. When I click on the Button the invisible Panel Shows. Instead of that I want the Panel to move in or slide in.
For example when you click on a combobox the dropdown list doesnt just pop in. I want my Panel to appear like that. How can I do that ?
Window animation is a built-in feature for Windows. Here's a class that uses it:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public static class Util {
public enum Effect { Roll, Slide, Center, Blend }
public static void Animate(Control ctl, Effect effect, int msec, int angle) {
int flags = effmap[(int)effect];
if (ctl.Visible) { flags |= 0x10000; angle += 180; }
else {
if (ctl.TopLevelControl == ctl) flags |= 0x20000;
else if (effect == Effect.Blend) throw new ArgumentException();
}
flags |= dirmap[(angle % 360) / 45];
bool ok = AnimateWindow(ctl.Handle, msec, flags);
if (!ok) throw new Exception("Animation failed");
ctl.Visible = !ctl.Visible;
}
private static int[] dirmap = { 1, 5, 4, 6, 2, 10, 8, 9 };
private static int[] effmap = { 0, 0x40000, 0x10, 0x80000 };
[DllImport("user32.dll")]
private static extern bool AnimateWindow(IntPtr handle, int msec, int flags);
}
Sample usage:
private void button2_Click(object sender, EventArgs e) {
Util.Animate(button1, Util.Effect.Slide, 150, 180);
}
If you are using .NET 4 (if not replace Task with Thread), a function similar to this could be a start:
private void slideToDestination(Control destination, Control control, int delay, Action onFinish)
{
new Task(() =>
{
int directionX = destination.Left > control.Left ? 1 : -1;
int directionY = destination.Bottom > control.Top ? 1 : -1;
while (control.Left != destination.Left || control.Top != destination.Bottom)
{
try
{
if (control.Left != destination.Left)
{
this.Invoke((Action)delegate()
{
control.Left += directionX;
});
}
if (control.Top != destination.Bottom)
{
this.Invoke((Action)delegate()
{
control.Top += directionY;
});
}
Thread.Sleep(delay);
}
catch
{
// form could be disposed
break;
}
}
if (onFinish != null) onFinish();
}).Start();
}
Usage:
slideToDestination(sender as Control, panel1, 10, () => MessageBox.Show("Done!"));
slideToDestination(sender as Control, panel1, 0, null);
As action you would send some boolean variable to set to true so that you know that the animation has finished or some code to run after it. Beware of deadlocks when calling with a null action. You could run two animations on the same control in two different directions with the same speed, and it will stay where it was forever and of course two animations simultaneusly can make the control go infinitely in some direction because the while will never finish :)
Check out the library I wrote last year:
WinForm Animation Library [.Net3.5+]
A simple library for animating controls/values in .Net WinForm (.Net
3.5 and later). Key frame (Path) based and fully customizable.
https://falahati.github.io/WinFormAnimation/
new Animator2D(
new Path2D(new Float2D(-100, -100), c_control.Location.ToFloat2D(), 500))
.Play(c_control, Animator2D.KnownProperties.Location);
This moves the c_control control from -100, -100 to the location it was in first place in 500 ms.
For WinForms, you could start with the Panel location being off screen.
Employ a Timer, and in the Tick event, shift the Panel's location slowly into view until it is at your predefined coordinates.
Lots of ways to skin a cat, but this is how I'd do it.
Related
I'm building custom user control that will be used to display tiles map, as base class I've chosen ScrollableControl, because I want to have scrollbars in my control.
I've successfully created paint logic that is responsible for painting only needed elements.
Now I'm trying to add static text that will be always visible in same place (in my case white box with red text in top left corner):
This isn't clearly visible on above gif, but that white box blinks and jumps a bit when I scroll using mouse or scrollbars.
My question is how should I change my code to have scrollable content and fixed position content on top of that scrollable content?
Is ScrollableControl good choice as base class?
Below is my code:
class TestControl : ScrollableControl
{
private int _tileWidth = 40;
private int _tileHeight = 40;
private int _tilesX = 20;
private int _tilesY = 20;
public TestControl()
{
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
ResizeRedraw = true;
AutoScrollMinSize = new Size(_tilesX * _tileWidth, _tilesY * _tileHeight);
Scroll += (sender, args) => { Invalidate(); };
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var offsetX = (AutoScrollPosition.X * -1) / _tileWidth;
var offsetY = (AutoScrollPosition.Y * -1) / _tileHeight;
var visibleX = Width / _tileWidth + 2;
var visibleY = Height / _tileHeight + 2;
var x = Math.Min(visibleX + offsetX, _tilesX);
var y = Math.Min(visibleY + offsetY, _tilesY);
for (var i = offsetX; i < x; i++)
{
for (var j = offsetY; j < y; j++)
{
e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i * _tileWidth, j * _tileHeight, _tileWidth, _tileHeight));
}
}
using (var p = new Pen(Color.Black))
{
for (var i = offsetX + 1; i < x; i++)
{
e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
}
for (var i = offsetY + 1; i < y; i++)
{
e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
}
}
e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1, 35, 14);
e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1);
}
}
EDIT:
I've searched a bit and found UserControl that has similar functionality - https://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView and after reading a bit more on control's author blog http://objectlistview.sourceforge.net/cs/blog1.html#blog-overlays I found out that he is using Transparent Form that is positioned on top of control.
I really would like to avoid that, but still have overlay on top of my control.
You are doing battle with a Windows system option named "Show window content while dragging". Always turned on by default, this web page shows how to turn it off.
Solves the problem, but it is not something you can rely on since it affects all scrollable window in all apps. Demanding that the user turns it off for you is unrealistic, users like this option so they'll just ignore you. That they did not provide an option to turn it off for a specific window was a pretty major oversight. It is an okay solution in a kiosk app.
Briefly, the way the option works is that Windows itself scrolls the window content with the ScrollWindowEx() winapi function. Using a bitblt of the window content to move pixels and only generating a paint request for the part of the window that was revealed by the scroll. Usually only a few lines of pixels, so completes very fast. Problem is, that bitblt moves your fixed pixels as well. The repaint moves them back. Pretty noticeable, the human eye is very sensitive to motion like that, helped avoid being lion lunch for the past million years.
You'll have to take the sting out of ScrollWindowsEx(), preventing it from moving pixels even though you can't stop it from being called. That takes a heavy sledgehammer, LockWindowUpdate(). You'll find code in this post.
using System.Runtime.InteropServices;
...
protected override void OnScroll(ScrollEventArgs e) {
if (e.Type == ScrollEventType.First) {
LockWindowUpdate(this.Handle);
}
else {
LockWindowUpdate(IntPtr.Zero);
this.Update();
if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWindowUpdate(IntPtr hWnd);
Not that pretty, using a separate Label control ought to start sounding attractive.
can you just add a label to that control(on top), in other words - cant you use it as panel?
i am making an app that overlays other d3d games,
the app is working perfectly except it has a huge performance impact on the cpu
taking 21.4 % of the cpu when rendering only a single line !
i am using slimdx library on c# and here is my full code
OverLay.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SlimDX.Direct3D11;
using SlimDX.Windows;
using System.Runtime.InteropServices;
using System.Security;
using SlimDX;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D9.Device;
using Resource = SlimDX.Direct3D9.Resource;
using System.Threading;
using D3D = SlimDX.Direct3D9;
namespace OverlayForm
{
public partial class OverLay : RenderForm
{
RenderForm form;
Device device;
// D3D.Sprite sprite;
public OverLay()
{
InitializeComponent();
Paint += OverLay_Paint;
FormBorderStyle = FormBorderStyle.None;
ShowIcon = false;
ShowInTaskbar = false;
TopMost = true;
WindowState = FormWindowState.Maximized;
//Make the window's border completely transparant
//SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) ^ WS_EX_LAYERED ^ WS_EX_TRANSPARENT));
SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT));
//Set the Alpha on the Whole Window to 255 (solid)
SetLayeredWindowAttributes(Handle , 0 , 255 , LWA_ALPHA);
form = this;
form.FormClosing += Form_FormClosing;
//Init DirectX
//This initializes the DirectX device. It needs to be done once.
//The alpha channel in the backbuffer is critical.
D3D.PresentParameters presentParameters = new D3D.PresentParameters();
presentParameters.Windowed = true;
presentParameters.SwapEffect = D3D.SwapEffect.Discard;
presentParameters.BackBufferFormat = D3D.Format.A8R8G8B8;
device = new Device(new D3D.Direct3D() , 0 , D3D.DeviceType.Hardware , Handle ,
D3D.CreateFlags.HardwareVertexProcessing , presentParameters);
//sprite = new D3D.Sprite(device);
font = new D3D.Font(device , new Font("Arial" , 9 , FontStyle.Regular));
line = new D3D.Line(this.device);
MessagePump.Run(form , new MainLoop(dxThread));
}
private void Form_FormClosing(object sender , FormClosingEventArgs e)
{
device.Dispose();
}
int centerx = Screen.PrimaryScreen.WorkingArea.Width / 2;
int centery = Screen.PrimaryScreen.WorkingArea.Height / 2;
private void OverLay_Paint(object sender , PaintEventArgs e)
{
//Create a margin (the whole form)
marg.Left = 0;
marg.Top = 0;
marg.Right = Width;
marg.Bottom = Height;
//Expand the Aero Glass Effect Border to the WHOLE form.
// since we have already had the border invisible we now
// have a completely invisible window - apart from the DirectX
// renders NOT in black.
DwmExtendFrameIntoClientArea(Handle , ref marg);
}
private static D3D.Font font;
private static D3D.Line line;
private void dxThread()
{
form.TopMost = true;
device.SetRenderState(D3D.RenderState.ZEnable , false);
device.SetRenderState(D3D.RenderState.Lighting , false);
device.SetRenderState(D3D.RenderState.CullMode , D3D.Cull.None);
device.SetTransform(D3D.TransformState.Projection , Matrix.OrthoOffCenterLH(0 , Width , Height , 0 , 0 , 1));
device.BeginScene();
//DrawFilledBox(0 , 0 , 100 , 100 , Color.White);
//font.DrawString( null, "Swag" , 10, 10 , new Color4(Color.White));
//DrawBox(0 , 0 , 10 , 10 , 1 , Color.Green);
DrawLine(0 , 0 , Screen.PrimaryScreen.Bounds.Width , Screen.PrimaryScreen.Bounds.Height , 2 , Color.Pink);
device.EndScene();
device.Present();
}
public static void DrawFilledBox(float x , float y , float w , float h , Color Color)
{
Vector2[] vLine = new Vector2[2];
line.GLLines = true;
line.Antialias = false;
line.Width = w;
vLine[0].X = x + w / 2;
vLine[0].Y = y;
vLine[1].X = x + w / 2;
vLine[1].Y = y + h;
line.Begin();
line.Draw(vLine , new Color4(Color));
line.End();
}
public static void DrawLine(float x1 , float y1 , float x2 , float y2 , float w , Color Color)
{
Vector2[] vLine = new Vector2[2] { new Vector2(x1 , y1) , new Vector2(x2 , y2) };
line.GLLines = true;
line.Antialias = false;
line.Width = w;
line.Begin();
line.Draw(vLine , new Color4(Color));
line.End();
}
public static void DrawBox(float x , float y , float w , float h , float px , System.Drawing.Color Color)
{
DrawFilledBox(x , y + h , w , px , Color);
DrawFilledBox(x - px , y , px , h , Color);
DrawFilledBox(x , y - px , w , px , Color);
DrawFilledBox(x + w , y , px , h , Color);
}
#region Extras
private Margins marg;
//this is used to specify the boundaries of the transparent area
internal struct Margins
{
public int Left, Right, Top, Bottom;
}
[DllImport("user32.dll" , SetLastError = true)]
private static extern UInt32 GetWindowLong(IntPtr hWnd , int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd , int nIndex , IntPtr dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd , uint crKey , byte bAlpha , uint dwFlags);
public const int GWL_EXSTYLE = -20;
public const int WS_EX_LAYERED = 0x80000;
public const int WS_EX_TRANSPARENT = 0x20;
public const int LWA_ALPHA = 0x2;
public const int LWA_COLORKEY = 0x1;
[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd , ref Margins pMargins);
#endregion
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OverlayForm
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
using (OverLay x = new OverLay())
{
}
}
}
}
Please note :
i already saw this : Very high CPU usage directx 9
but i am using MessagePump.Run and don't know how to apply the answer.
The reason for the high CPU is that SlimDX is using PeekMessage rather than the more usual GetMessage (that the majority of Windows apps use). The former does not wait for a message to appear in the message pump unlike the latter. In other words, GetMessage() will block the current thread possibly reducing the CPU load which is what you want a well-behaved Windows desktop application to do.
MSDN:
Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
Unlike GetMessage, the PeekMessage function does not wait for a message to be posted before returning More...
Now a typical graceful Windows message pump looks like this:
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
...however SlimDX uses what some people refer to as an action game loop:
static bool AppStillIdle
{
get
{
Message msg;
return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
}
public void MainLoop()
{
// hook the application's idle event
Application.Idle += new EventHandler(OnApplicationIdle);
Application.Run(form);
}
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
}
}
With nothing to draw you will experience a very tight loop of PeekMessage with no waiting in between!
My suggestion is that you either use one of the MessagePump.Run overloads for idle, or add a sleep as per below:
Change this:
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
}
}
...to this:
void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
RenderFrame();
Thread.Sleep(0); // <------------- be graceful
}
}
Note the use of Thread.Sleep(0). This pauses for the minimal amount of time whilst still allowing the thread to be relinquished to the OS.
MSDN:
The number of milliseconds for which the thread is suspended. If the value of the millisecondsTimeout argument is zero, the thread relinquishes the remainder of its time slice to any thread of equal priority that is ready to run. If there are no other threads of equal priority that are ready to run, execution of the current thread is not suspended.
I see in your answer you already had a Thread.Sleep(50) but now it is good to know why SlimDX requires a Sleep in the first place and that 50 is perhaps too high a value.
GetMessage
OP:
i won't need a very active rendering mechanism, because i will only use this to show overlay from my music player about current song playing , next song , etc
Considering this is the case, the most CPU-efficient means is to replace your action loop with a turn-based game loop using GetMessage() instead of PeekMessage(). Then put your rendering in your application's OnIdle() callback.
As Nick Dandoulakis says in Windows Game Loop 50% CPU on Dual Core:
Nick:
That's a standard game loop for action games, where you must update objects positions / game world.
If you are making a board game GetMessage would be a better choice.
It really depends on what game you are making. More...
No Sleep() required.
the problem was that i was using full power of cpu even when not rendering, so adding
Thread.Sleep(50);
at the end of the dxThread method lowered it to only
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);
I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]
foreach (var node in stack)
{
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
Thread.Sleep(500);
}
The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?
For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0
Edit:
Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.
The code for using a Thread was just shorter and I wanted to give him a solution quickly.
Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.
This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.
Use the timer_Tick function to update the position.
You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.
public partial class Form1 : Form
{
private Point pos = new Point(1,1);
private float[] vel = new float[2];
private Size bounds = new Size(20,20);
private Timer ticky = new Timer(); //System.Windows.Forms.Timer
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ticky.Interval = 20;
ticky.Tick += ticky_Tick;
vel[0] = 4; vel[1] = 0;
ticky.Start();
}
void ticky_Tick(object sender, EventArgs e)
{
updatePosition();
//This tells our form to repaint itself (and call the OnPaint method)
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
}
private void updatePosition()
{
pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
vel[1] += .5f; //Apply some gravity
if (pos.X + bounds.Width > this.ClientSize.Width)
{
vel[0] *= -1;
pos.X = this.ClientSize.Width - bounds.Width;
}
else if (pos.X < 0)
{
vel[0] *= -1;
pos.X = 0;
}
if (pos.Y + bounds.Height > this.ClientSize.Height)
{
vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
pos.Y = this.ClientSize.Height - bounds.Height;
}
else if (pos.Y < 0)
{
vel[1] *= -1;
pos.Y = 0;
}
}
}
Results:
You can use timers to do all sorts of delayed form drawing:
Original Solution:
//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;
new Thread(() => {
foreach (var node in stack)
{
//The invoke only needs to be used when updating GUI Elements
this.Invoke((MethodInvoker)delegate() {
//Everything inside of this Invoke runs on the GUI Thread
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
});
Thread.Sleep(500);
}
}).Start();
Solution in 2022:
await Task.Delay(500);
Things really are better these days.
One of the forms in my C# .NET application has multiple DataGridViews that implement drag and drop to move the rows around. The drag and drop mostly works right, but I've been having a hard time getting the DataGridViews to AutoScroll - when a row is dragged near the top or bottom of the box, to scroll it in that direction.
So far, I've tried implementing a version of this solution. I have a ScrollingGridView class inheriting from DataGridView that implements the described timer, and according to the debugger, the timer is firing appropriately, but the timer code:
const int WM_VSCROLL = 277;
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private void ScrollingGridViewTimerTick(object sender, EventArgs e)
{
SendMessage(Handle, WM_VSCROLL, (IntPtr)scrollDirectionInt, IntPtr.Zero);
}
doesn't do anything as far as I can tell, possibly because I have multiple DataGridViews in the form. I also tried modifying the AutoScrollOffset property, but that didn't do anything either. Investigation of the DataGridView and ScrollBar classes doesn't seem to suggest any other commands or functions that will actually make the DataGridView scroll. Can anyone help me with a function that will actually scroll the DataGridView, or some other way to solve the problem?
private void TargetReasonGrid_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
//Converts window position to user control position (otherwise you can use MousePosition.Y)
int mousepos = PointToClient(Cursor.Position).Y;
//If the mouse is hovering over the bottom 5% of the grid
if (mousepos > (TargetReasonGrid.Location.Y + (TargetReasonGrid.Height * 0.95)))
{
//If the first row displayed isn't the last row in the grid
if (TargetReasonGrid.FirstDisplayedScrollingRowIndex < TargetReasonGrid.RowCount - 1)
{
//Increase the first row displayed index by 1 (scroll down 1 row)
TargetReasonGrid.FirstDisplayedScrollingRowIndex = TargetReasonGrid.FirstDisplayedScrollingRowIndex + 1;
}
}
//If the mouse is hovering over the top 5% of the grid
if (mousepos < (TargetReasonGrid.Location.Y + (TargetReasonGrid.Height * 0.05)))
{
//If the first row displayed isn't the first row in the grid
if (TargetReasonGrid.FirstDisplayedScrollingRowIndex > 0)
{
//Decrease the first row displayed index by 1 (scroll up 1 row)
TargetReasonGrid.FirstDisplayedScrollingRowIndex = TargetReasonGrid.FirstDisplayedScrollingRowIndex - 1;
}
}
}
Lots of helpful answers here, just thought I'd add a less-complicated solution to this issue. The code above is called when rows are dragged within a DataGridView. Mine is named "TargetReasonGrid".
I added notes that explain what I'm doing, but here's the steps spelled out:
Convert the mouse position so it's relative to grid locations (within your form/control)
Set imaginary regions on the edge of the displayed grid where mouse travel will trigger a scroll
Check to make sure you actually have different rows to scroll to
Scroll in increments of 1 row
Thanks to C4u (commented above), gave me the "imaginary regions" idea.
I haven't looked at this code in a while. But a while back I implemented a DataGridView that supported just this.
class DragOrderedDataGridView : System.Windows.Forms.DataGridView
{
public delegate void RowDroppedEventHangler(object source, DataGridViewRow sourceRow, DataGridViewRow destinationRow);
public event RowDroppedEventHangler RowDropped;
bool bDragging = false;
System.Windows.Forms.DataGridView.HitTestInfo hti = null;
System.Threading.Timer scrollTimer = null;
delegate void SetScrollDelegate(int value);
public bool AllowDragOrdering { get; set; }
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
if (AllowDragOrdering)
{
DataGridView.HitTestInfo hti = this.HitTest(e.X, e.Y);
if (hti.RowIndex != -1
&& hti.RowIndex != this.NewRowIndex
&& e.Button == MouseButtons.Left)
{
bDragging = true;
}
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (bDragging && e.Button == MouseButtons.Left)
{
DataGridView.HitTestInfo newhti = this.HitTest(e.X, e.Y);
if (hti != null && hti.RowIndex != newhti.RowIndex)
{
System.Diagnostics.Debug.WriteLine("invalidating " + hti.RowIndex.ToString());
Invalidate();
}
hti = newhti;
System.Diagnostics.Debug.WriteLine(string.Format("{0:000} {1} ", hti.RowIndex, e.Location));
Point clientPoint = this.PointToClient(e.Location);
System.Diagnostics.Debug.WriteLine(e.Location + " " + this.Bounds.Size);
if (scrollTimer == null
&& ShouldScrollDown(e.Location))
{
//
// enable the timer to scroll the screen
//
scrollTimer = new System.Threading.Timer(new System.Threading.TimerCallback(TimerScroll), 1, 0, 250);
}
if (scrollTimer == null
&& ShouldScrollUp(e.Location))
{
scrollTimer = new System.Threading.Timer(new System.Threading.TimerCallback(TimerScroll), -1, 0, 250);
}
}
else
{
bDragging = false;
}
if (!(ShouldScrollUp(e.Location) || ShouldScrollDown(e.Location)))
{
StopAutoScrolling();
}
base.OnMouseMove(e);
}
bool ShouldScrollUp(Point location)
{
return location.Y > this.ColumnHeadersHeight
&& location.Y < this.ColumnHeadersHeight + 15
&& location.X >= 0
&& location.X <= this.Bounds.Width;
}
bool ShouldScrollDown(Point location)
{
return location.Y > this.Bounds.Height - 15
&& location.Y < this.Bounds.Height
&& location.X >= 0
&& location.X <= this.Bounds.Width;
}
void StopAutoScrolling()
{
if (scrollTimer != null)
{
//
// disable the timer to scroll the screen
//
scrollTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
scrollTimer = null;
}
}
void TimerScroll(object state)
{
SetScrollBar((int)state);
}
bool scrolling = false;
void SetScrollBar(int direction)
{
if (scrolling)
{
return;
}
if (this.InvokeRequired)
{
this.Invoke(new Action<int>(SetScrollBar), new object[] {direction});
}
else
{
scrolling = true;
if (0 < direction)
{
if (this.FirstDisplayedScrollingRowIndex < this.Rows.Count - 1)
{
this.FirstDisplayedScrollingRowIndex++;
}
}
else
{
if (this.FirstDisplayedScrollingRowIndex > 0)
{
this.FirstDisplayedScrollingRowIndex--;
}
}
scrolling = false;
}
}
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
{
bDragging = false;
HitTestInfo livehti = hti;
hti = null;
if (RowDropped != null
&& livehti != null
&& livehti.RowIndex != -1
&& this.CurrentRow.Index != livehti.RowIndex)
{
RowDropped(this, this.CurrentRow, this.Rows[livehti.RowIndex]);
}
StopAutoScrolling();
Invalidate();
base.OnMouseUp(e);
}
protected override void OnCellPainting(System.Windows.Forms.DataGridViewCellPaintingEventArgs e)
{
if (bDragging && hti != null && hti.RowIndex != -1
&& e.RowIndex == hti.RowIndex)
{
//
// draw the indicator
//
Pen p = new Pen(Color.FromArgb(0, 0, 215));
p.Width = 4;
e.Graphics.DrawLine(p, e.CellBounds.Left, e.CellBounds.Top, e.CellBounds.Right, e.CellBounds.Top);
}
base.OnCellPainting(e);
}
}
Thanks for the help with my problem, perhaps I can help you with yours.
From this page:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
//winuser.h constants
private const int WM_VSCROLL = 277; // Vertical scroll
private const int SB_LINEUP = 0; // Scrolls one line up
private const int SB_LINEDOWN = 1; // Scrolls one line down
private const int SB_ENDSCROLL = 8; // Ends the scrolling
//Call this when you want to scroll
private void ScrollGridview(int direction)
{
SendMessage(Handle, WM_VSCROLL, (IntPtr)direction, VerticalScrollBar.Handle);
SendMessage(Handle, WM_VSCROLL, (IntPtr)SB_ENDSCROLL, VerticalScrollBar.Handle);
}
(The second SendMessage does not seem to be necessary, but I included it for good measure)
I wrote a DataGridView control which incorporates both this gbogumil's autoscroll solution and a correctly-functioning OnPaint highlighting - you can find it here.
I'd like to also point out this control, which I just found from another thread. It looks really nice, but unfortunately it's GPL, so you can only use it for GPL projects. It does all the things we need plus a lot more, though.