C# UserControl scrollbar issues - c#

I'm having trouble setting the VerticalScroll/HorizontalScroll.Value property. Every time I set the value, the scrollbar moves, then snaps back (see screenshot below)
Here is the code for my user control:
using System.Drawing;
using System.Windows.Forms;
namespace MyGanttChart
{
public class Chart : UserControl
{
public Chart()
{
HorizontalScroll.Visible = true;
VerticalScroll.Visible = true;
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
ScrollEventType scrollType = ScrollEventType.SmallIncrement;
if (e.Delta > 0)
scrollType = ScrollEventType.SmallDecrement;
ScrollOrientation orientation = ScrollOrientation.VerticalScroll;
if (ModifierKeys == Keys.Shift)
orientation = ScrollOrientation.HorizontalScroll;
ScrollEventArgs args = new ScrollEventArgs(scrollType, 1, orientation);
OnScroll(args);
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
if (se.ScrollOrientation == ScrollOrientation.HorizontalScroll)
{
//if (se.Type == ScrollEventType.SmallIncrement || se.Type == ScrollEventType.LargeIncrement)
// X -= HorizontalScroll.LargeChange; // se.NewValue;
//else if (se.Type == ScrollEventType.SmallDecrement || se.Type == ScrollEventType.LargeDecrement)
// X += HorizontalScroll.LargeChange;
if ((se.Type == ScrollEventType.SmallIncrement || se.Type == ScrollEventType.LargeIncrement) &&
HorizontalScroll.Value + HorizontalScroll.LargeChange <= HorizontalScroll.Maximum)
{
HorizontalScroll.Value += HorizontalScroll.LargeChange;
}
else if ((se.Type == ScrollEventType.SmallDecrement || se.Type == ScrollEventType.LargeDecrement) &&
HorizontalScroll.Value - HorizontalScroll.LargeChange >= HorizontalScroll.Minimum)
{
HorizontalScroll.Value -= HorizontalScroll.LargeChange;
}
}
else if (se.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
//if (se.Type == ScrollEventType.SmallIncrement || se.Type == ScrollEventType.LargeIncrement)
// Y -= VerticalScroll.LargeChange;
//else if (se.Type == ScrollEventType.SmallDecrement || se.Type == ScrollEventType.LargeDecrement)
// Y += VerticalScroll.LargeChange;
if ((se.Type == ScrollEventType.SmallIncrement || se.Type == ScrollEventType.LargeIncrement) &&
VerticalScroll.Value + VerticalScroll.LargeChange <= VerticalScroll.Maximum)
{
VerticalScroll.Value += VerticalScroll.LargeChange;
}
else if ((se.Type == ScrollEventType.SmallDecrement || se.Type == ScrollEventType.LargeDecrement) &&
VerticalScroll.Value - VerticalScroll.LargeChange >= VerticalScroll.Minimum)
{
VerticalScroll.Value -= VerticalScroll.LargeChange;
}
}
this.Invalidate(); //Force the control to redraw
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (!this.DesignMode)
Draw(e.Graphics, e.ClipRectangle);
}
public float X = 0;
public float Y = 0;
private void Draw(Graphics graphics, Rectangle rect)
{
graphics.TranslateTransform(X, Y);
graphics.Clear(Color.White);
Point center = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
graphics.DrawString("hi", this.Font, new SolidBrush(Color.Black), center.X, center.Y, new StringFormat() { Alignment = StringAlignment.Center});
graphics.Flush();
}
}
}
(The control view currently does not move - I had that working with the X and Y values, but I'm working on the scrollbar values right now)
I've seen this question but setting the value twice or calling PerformLayout() does not seem to work for me. Any suggestions?

From what I see of your code, you don't need any custom code to manage the scrolling of your drawing.
First, you need to add a child panel inside your usercontrol which will be the container of the drawing. So you move the drawing code to this panel. You must ensure that this panel will resize itself if the drawing needs to be bigger. This is what will show the scrollbars on the usercontrol.
Second, you must set the AutoScroll property of your usercontrol to True. Doing so will ensure that any child control having a size or a position exceeding the usercontrol size can be scrolled. Have a look at the AutoScrollMargin property too. This is done automagically by the framework, so no other code is needed. Isn't it beautiful?
Finally, you can remove the two scrollbars you added.
Edit:
Moreover, when you draw, you don't need to compensate for the scrolling position. You just draw with origin 0,0 without taking care of the scroll. In addition, doing it this way will prevent any flickering caused by constantly redrawing.

Related

WindowsForm - Flicker after resize

I've a WinForm application (double buffer enabled) with a TableLayoutPanel containing some DataGridView, some ActiveX control (from NI TestStand) and some labels. I added some code to Form CellPaint event to draw borders where I need.
One label is showing, via Systems.Windows.Forms.Timer, the actual DateTime, incrementing each second. At each update the form is flickering. If I comment the code in CellPaint event, flickering stops.
Adding:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
solves the flickering issue with CellPaint code enabled, but only if I don't resize the form. After resize, flickering starts and never stops.
I tried several proposal found here on SO, with no luck, suspending layout as suggester here, using reflection to enable double buffering on each control.
How can I avoid the flickering also after the resize?
EDIT:
Here below the code related to Cellpaint event:
private void LayoutMainWindow_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
SuspendLayout();
if (e.Row == 0 && e.Column > 0)
{
DrawBottomBorder(e, 1);
}
if (e.Row == 1 && e.Column != 0)
{
DrawBottomBorder(e, 2);
}
if (e.Row <= 8 && e.Column == 0)
{
DrawRightBorder(e, 2);
}
if (e.Row == 2 && e.Column == 0)
{
DrawBottomBorder(e, 2);
}
if ((e.Row >= 2 && e.Row <= 7) && e.Column == 2)
{
DrawRightBorder(e, 2);
}
if (e.Row == 7 && e.Column <= 4)
{
DrawBottomBorder(e, 2);
}
if (e.Row >= 8 && e.Row <= 9)
{
DrawBottomBorder(e, 2);
}
if (e.Row == 9 && e.Column == 0)
{
DrawRightBorder(e, 2);
}
ResumeLayout();
}
private static void DrawRightBorder(TableLayoutCellPaintEventArgs e, float width)
{
Rectangle r = e.CellBounds;
using (Pen pen = new Pen(Color.Gray, width))
{
e.Graphics.DrawLine(pen, r.X + r.Width, r.Y, r.X + r.Width, r.Y + r.Height);
}
}
private static void DrawBottomBorder(TableLayoutCellPaintEventArgs e, float width)
{
Rectangle r = e.CellBounds;
using (Pen pen = new Pen(Color.Gray, width))
{
e.Graphics.DrawLine(pen, r.X, r.Y + r.Height, r.X + r.Width, r.Y + r.Height);
}
}
Thanks to TaW suggestion, as far as CellPaint is related to a TableLayout containing all the controls inside the Form:
private static void SetDoubleBuffered(Control c)
{
if (System.Windows.Forms.SystemInformation.TerminalServerSession)
return;
System.Reflection.PropertyInfo aProp = typeof(System.Windows.Forms.Control).GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
aProp.SetValue(c, true, null);
}
And then:
SetDoubleBuffered(LayoutMainWindow);
It fixes my issue. Thank you very much guys.

Disabling smooth scrolling

I have a Panel in which a VerticalScroll is used. When the user clicks the VerticalScroll and drags it, the VerticalScroll scrolls smoothly. This is what I do not want to have. Rather than scrolling smoothly, I would like the VerticalScroll to scroll in discrete steps, to be precise in the same steps as I specified in the property SmallChange. How can I do that?
Edit: It basically seems to work when overriding the OnPaint method of the Panel, but how can I suppress the call to base.OnPaint conditionally?
This code sets the position of the Vertical Scroll to the right value:
protected override void OnPaint(PaintEventArgs e)
{
int v = this.VerticalScroll.Value;
this.VerticalScroll.Value = Math.Max(0, Form1.AlignFileButtonHeight(v, v + 1));
}
But it has the effect that the window "blinks" for an instant! How can I avoid that?
Edit: The best result I have achieved so far, I have achieved by overriding the OnScroll method:
protected override void OnScroll(ScrollEventArgs se)
{
if (se.Type == ScrollEventType.ThumbTrack)
{
int v = se.NewValue;
v = Math.Max(0, Form1.AlignFileButtonHeight(v, v + 1));
this.VerticalScroll.Value = v;
this.Refresh();
se.NewValue = v;
base.OnScroll(se);
this.Refresh();
base.OnScroll(se);
}
}
It still blinks a bit, but not as much as when overriding the OnPaint method. However, I'd like to get rid of blinking entirely.
Edit: I also tried this, with only very limited success.
protected override void OnScroll(ScrollEventArgs se)
{
if (se.Type == ScrollEventType.ThumbTrack && se.NewValue != se.OldValue)
{
int diff = se.NewValue - se.OldValue;
foreach (Control c in this.Controls)
{
int x = c.Location.X;
int y = c.Location.Y;
y = y + diff;
c.Location = new Point(x, y);
}
if (se.NewValue % Form1.fileButtonHeight == 0)
{
foreach (Control c in this.Controls)
{
int x = c.Location.X;
int y = c.Location.Y;
y = y + (diff < 0 ? 1 : -1) * Form1.fileButtonHeight;
c.Location = new Point(x, y);
}
}
}
}

C# objectlistview undefined offset in cell editor

I use TreeListView component from ObjectListView library. I make cell values editable and when i double click on them TextBox will appear with odd offset.
I want to remove this offset, but how?
before start editing
after start editing
As you can see first row, second column ("My new device"), TextBox is appeared with offset.
P.S. Editing work as expected. Only offset is annoying me
P.P.S. As you can see the offset depends of first column offset. How i can change it to zero?
After long searches i found solution!
Source code of OLV, ObjectListView.cs
public virtual void StartCellEdit(OLVListItem item, int subItemIndex) {
OLVColumn column = this.GetColumn(subItemIndex);
Control c = this.GetCellEditor(item, subItemIndex);
Rectangle cellBounds = this.CalculateCellBounds(item, subItemIndex);
c.Bounds = this.CalculateCellEditorBounds(item, subItemIndex, c.PreferredSize);
// Try to align the control as the column is aligned. Not all controls support this property
Munger.PutProperty(c, "TextAlign", column.TextAlign);
// Give the control the value from the model
this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject));
// Give the outside world the chance to munge with the process
this.CellEditEventArgs = new CellEditEventArgs(column, c, cellBounds, item, subItemIndex);
this.OnCellEditStarting(this.CellEditEventArgs);
if (this.CellEditEventArgs.Cancel)
return;
// The event handler may have completely changed the control, so we need to remember it
this.cellEditor = this.CellEditEventArgs.Control;
this.Invalidate();
this.Controls.Add(this.cellEditor);
this.ConfigureControl();
this.PauseAnimations(true);
}
I saw CellEditEventArgs contains Control, that drawed in area named Bounds.
This function in the source file append offset to control's bounds:
protected Rectangle CalculateCellEditorBoundsStandard(OLVListItem item, int subItemIndex, Rectangle cellBounds, Size preferredSize) {
if (this.View == View.Tile)
return cellBounds;
// Center the editor vertically
if (cellBounds.Height != preferredSize.Height)
cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2;
// Only Details view needs more processing
if (this.View != View.Details)
return cellBounds;
// Allow for image (if there is one).
int offset = 0;
object imageSelector = null;
if (subItemIndex == 0)
imageSelector = item.ImageSelector;
else {
// We only check for subitem images if we are owner drawn or showing subitem images
if (this.OwnerDraw || this.ShowImagesOnSubItems)
imageSelector = item.GetSubItem(subItemIndex).ImageSelector;
}
if (this.GetActualImageIndex(imageSelector) != -1) {
offset += this.SmallImageSize.Width + 2;
}
// Allow for checkbox
if (this.CheckBoxes && this.StateImageList != null && subItemIndex == 0) {
offset += this.StateImageList.ImageSize.Width + 2;
}
// Allow for indent (first column only)
if (subItemIndex == 0 && item.IndentCount > 0) {
offset += (this.SmallImageSize.Width * item.IndentCount);
}
// Do the adjustment
if (offset > 0) {
cellBounds.X += offset;
cellBounds.Width -= offset;
}
return cellBounds;
}
We can see, that offset appending to every cell (not only first). Also we can se, that CellEditEventArgs contains CellBounds.
So we can just reset Control.Bounds to CellBounds value:
//...
dataTreeView.CellEditStarting += DisableInputValueForCollections;
//...
private static void DisableInputValueForCollections(object sender, CellEditEventArgs e)
{
RemoveExtraOffsetForNotFirstColumnInputControl(e);
var node = e.RowObject as DataTreeNode;
if (node != null && e.Column.AspectName == "Value")
{
if (node.IsContainer()) e.Cancel = true;
}
}
//...
private static void RemoveExtraOffsetForNotFirstColumnInputControl(CellEditEventArgs e)
{
if (e.Column.AspectName != "Name")
{
e.Control.Bounds = e.CellBounds;
}
}
//...
The issue still exists in OLV v2.9.1. I worked around it slightly different than muzagursiy did.
olv.CellEditStarting += (sender, args) =>
{
// Left align the edit control
args.Control.Location = args.CellBounds.Location;
// Readjust the size of the control to fill the whole cell if CellEditUseWholeCellEffective is enabled
if (args.Column.CellEditUseWholeCellEffective)
{
args.Control.Size = args.CellBounds.Size;
}
};

Preventing flicker for my rubber band?

I have implemented a rubber band by adopting the following code:
https://support.microsoft.com/en-gb/kb/314945
This is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
namespace Test
{
public partial class TestX: UserControl
{
private Boolean m_bLeftButton { get; set; }
private Boolean m_bMiddleButton { get; set; }
private Boolean m_bZoomWindow { get; set; }
Point m_ptOriginal = new Point();
Point m_ptLast = new Point();
public TestX()
{
m_bZoomWindow = false;
m_bLeftButton = false;
m_bMiddleButton = false;
}
// Called when the left mouse button is pressed.
public void MyMouseDown(Object sender, MouseEventArgs e)
{
if(m_bZoomWindow && e.Button == MouseButtons.Left)
{
// Make a note that we "have the mouse".
m_bLeftButton = true;
// Store the "starting point" for this rubber-band rectangle.
m_ptOriginal.X = e.X;
m_ptOriginal.Y = e.Y;
// Special value lets us know that no previous
// rectangle needs to be erased.
m_ptLast.X = -1;
m_ptLast.Y = -1;
}
}
// Convert and normalize the points and draw the reversible frame.
private void MyDrawReversibleRectangle(Point p1, Point p2)
{
Rectangle rc = new Rectangle();
// Convert the points to screen coordinates.
p1 = PointToScreen(p1);
p2 = PointToScreen(p2);
// Normalize the rectangle.
if (p1.X < p2.X)
{
rc.X = p1.X;
rc.Width = p2.X - p1.X;
}
else
{
rc.X = p2.X;
rc.Width = p1.X - p2.X;
}
if (p1.Y < p2.Y)
{
rc.Y = p1.Y;
rc.Height = p2.Y - p1.Y;
}
else
{
rc.Y = p2.Y;
rc.Height = p1.Y - p2.Y;
}
// Draw the reversible frame.
ControlPaint.DrawReversibleFrame(rc,
Color.WhiteSmoke, FrameStyle.Thick);
}
// Called when the left mouse button is released.
public void MyMouseUp(Object sender, MouseEventArgs e)
{
if(m_bZoomWindow && e.Button == MouseButtons.Left)
{
// Set internal flag to know we no longer "have the mouse".
m_bZoomWindow = false;
m_bLeftButton = false;
// If we have drawn previously, draw again in that spot
// to remove the lines.
if (m_ptLast.X != -1)
{
Point ptCurrent = new Point(e.X, e.Y);
MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);
// Do zoom now ...
}
// Set flags to know that there is no "previous" line to reverse.
m_ptLast.X = -1;
m_ptLast.Y = -1;
m_ptOriginal.X = -1;
m_ptOriginal.Y = -1;
}
}
// Called when the mouse is moved.
public void MyMouseMove(Object sender, MouseEventArgs e)
{
Point ptCurrent = new Point(e.X, e.Y);
if(m_bLeftButton)
{
// If we "have the mouse", then we draw our lines.
if (m_bZoomWindow)
{
// If we have drawn previously, draw again in
// that spot to remove the lines.
if (m_ptLast.X != -1)
{
MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);
}
// Update last point.
if(ptCurrent != m_ptLast)
{
m_ptLast = ptCurrent;
// Draw new lines.
MyDrawReversibleRectangle(m_ptOriginal, ptCurrent);
}
}
}
}
// Set up delegates for mouse events.
protected override void OnLoad(System.EventArgs e)
{
MouseDown += new MouseEventHandler(MyMouseDown);
MouseUp += new MouseEventHandler(MyMouseUp);
MouseMove += new MouseEventHandler(MyMouseMove);
MouseWheel += new MouseEventHandler(MyMouseWheel);
m_bZoomWindow = false;
}
}
}
It itself it works, but the rectangle flashes. Other programs, like CAD packages, have zero flickering when drawing a rectangle.
My form is set to use DoubleBuffering so I thought it would be OK. Has anyone else encountered this issue?
Update: I thought I would go back to the beginning and do a test winforms project with a table layout panel and a embedded user control. I set the user control to work like the answer I was provided. The only difference was that I set the user control constructor like this:
public MyUserControl()
{
InitializeComponent();
_selectionPen = new Pen(Color.Black, 3.0f);
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
BackColor = Color.Transparent;
Dock = DockStyle.Fill;
Margin = new Padding(1);
}
Notice the additional control styles AllPaintingInWmPaint and UserPaint? It seems that I needed these in addition to the OptimizedDoubleBuffer style. I also set the form as double buffered.
When I make those adjustments I can draw a flicker free rubber band. Hoorah! But when I add back in the embedded class for rendering a DWG in the user control, I get the conflict of one over imposing the other.
So that is where I am at and I will wait to see what I can glean from the vendors of the DWG viewer class.
To demonstrate using the Paint event, from my comment to the OP.
Smoothest box draw I was able to get was by doing it in Paint and setting ControlStyles.OptimizeDoubleBuffer on the control. Of course, it depends on the intended bounds of your box -- this will not exceed the bounds of the control itself (i.e. will not draw onto the form or desktop):
using System.Drawing;
using System.Windows.Forms;
namespace WinformsScratch.RubberBand
{
public class TestY : Control
{
private Point? _selectionStart;
private Point? _selectionEnd;
private readonly Pen _selectionPen;
public TestY()
{
_selectionPen = new Pen(Color.Black, 3.0f);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
MouseDown += (s, e) => {
if (e.Button == MouseButtons.Left)
_selectionStart = _selectionEnd = e.Location;
};
MouseUp += (s, e) => {
if (e.Button == MouseButtons.Left)
{
_selectionStart = _selectionEnd = null;
Invalidate(false);
}
};
MouseMove += (s, e) => {
if (_selectionStart.HasValue &&
_selectionEnd.HasValue &&
_selectionEnd.Value != e.Location)
{
_selectionEnd = e.Location;
Invalidate(false);
}
};
Paint += (s, e) => {
if (_selectionStart.HasValue && _selectionEnd.HasValue)
e.Graphics.DrawRectangle(_selectionPen, GetSelectionRectangle());
};
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_selectionPen != null) _selectionPen.Dispose();
}
base.Dispose(disposing);
}
private Rectangle GetSelectionRectangle()
{
Rectangle rc = new Rectangle();
if (_selectionStart.HasValue && _selectionEnd.HasValue)
{
// Normalize the rectangle.
if (_selectionStart.Value.X < _selectionEnd.Value.X)
{
rc.X = _selectionStart.Value.X;
rc.Width = _selectionEnd.Value.X - _selectionStart.Value.X;
}
else
{
rc.X = _selectionEnd.Value.X;
rc.Width = _selectionStart.Value.X - _selectionEnd.Value.X;
}
if (_selectionStart.Value.Y < _selectionEnd.Value.Y)
{
rc.Y = _selectionStart.Value.Y;
rc.Height = _selectionEnd.Value.Y - _selectionStart.Value.Y;
}
else
{
rc.Y = _selectionEnd.Value.Y;
rc.Height = _selectionStart.Value.Y - _selectionEnd.Value.Y;
}
}
return rc;
}
}
}

How To AutoScroll a DataGridView during Drag and Drop

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.

Categories