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.
Related
I have added a DateTimePicker in ContextMenuStrip using ToolStripControlHost. I am using below code.
DateTimePicker startDate = new DateTimePicker();
(cms.Items["mnuStartDate"] as ToolStripMenuItem).DropDownItems.Add(new ToolStripControlHost(startDate));
Selecting of date is working but when I clicked on the arrows to go to the previous or next month, or if I clicked on the Month, the ToolStrip is closing. Is there any workaround here? Thanks.
Note: I am showing the cms(ContextMenuStrip ) when Right Clicking on a control.
Looks like the ToolStrip behavior closes when an item is selected. So I juts used CalendarMonth Control as a replacement. Link here
It seems to be related to Application.EnableVisualStyles(); When not-enabled, the menu doesn't unexpectedly close.
When enabled, to prevent closing, detect if the CalendarMonth menu is currently visible, and if the mouse was clicked on it.
public class DatePicker : DateTimePicker {
private const int DtmFirst = 0x1000;
private const int DtmGetmonthcal = (DtmFirst + 8);
private bool isDroppedDown = false;
private IntPtr hwndCalendarWindow = IntPtr.Zero;
protected override void OnDropDown(EventArgs e) {
isDroppedDown = true;
hwndCalendarWindow = SendMessage(Handle, DtmGetmonthcal, 0, 0);
base.OnDropDown(e);
}
protected override void OnCloseUp(EventArgs eventargs) {
isDroppedDown = false;
base.OnCloseUp(eventargs);
}
public bool CalendarMonthClicked(Point pt) {
if (!isDroppedDown)
return false;
RECT r = new RECT();
GetWindowRect(hwndCalendarWindow, out r);
return pt.X >= r.Left && pt.X <= r.Right && pt.Y >= r.Top && pt.Y <= r.Bottom;
}
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr h, int msg, int param, int data);
[StructLayout(LayoutKind.Sequential)]
private struct RECT {
public int Left, Top, Right, Bottom;
}
}
static class Program {
//[DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
//public extern static Int32 SetWindowTheme(IntPtr hWnd, String textSubAppName, String textSubIdList);
[STAThread]
static void Main() {
Application.EnableVisualStyles(); // if you disable visual styles then the menu won't close
DatePicker dpStart = new DatePicker();
dpStart.MinimumSize = new Size(100, 50);
Form ff = new Form();
ContextMenuStrip cms = new ContextMenuStrip();
ToolStripMenuItem menu = new ToolStripMenuItem("Menu");
menu.DropDown.Closing += (o, e) => {
e.Cancel = (e.CloseReason == ToolStripDropDownCloseReason.AppClicked && dpStart.CalendarMonthClicked(Cursor.Position));
};
// sub-menu doesn't close properly, probably related to the ToolStripControlHost
cms.Opening += delegate {
menu.DropDown.Close(ToolStripDropDownCloseReason.CloseCalled);
};
cms.Closed += delegate {
menu.DropDown.Close(ToolStripDropDownCloseReason.CloseCalled);
};
ff.ContextMenuStrip = cms;
cms.Items.Add(menu);
ToolStripControlHost item = new ToolStripControlHost(dpStart) { AutoSize = true };
menu.DropDownItems.Add(item);
Application.Run(ff);
}
}
I am creating a form inside another form. In the child form I have created taskbar button. I am overriding WndProc in the button.I am referring https://www.codeproject.com/Articles/10171/Adding-a-Minimize-to-tray-button-to-a-Form-s-caption bar. But whenever I am getting messages(mouse coordinates) , it is given with respect to the parent form. This makes trouble in calculations .I want those with respect to child form.How to achieve this?
This is the code for form1
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Form2 form2 = new Form2();
form2.TopLevel = false;
form2.AutoScroll = true;
form2.Anchor = AnchorStyles.Top;
form2.Dock = DockStyle.Fill;
this.splitContainer1.Panel2.Controls.Add(form2);
form2.Show();
}
}
this is form2.
public partial class Form2 : Form
{
TyronM.MinTrayBtn Pin_Button;
public Form2()
{
InitializeComponent();
Pin_Button = new TyronM.MinTrayBtn(this);
Pin_Button.MinTrayBtnClicked += new TyronM.MinTrayBtnClickedEventHandler(this.Pin_Button_Clicked);
}
public void Pin_Button_Clicked(object sender, EventArgs e)
{
MessageBox.Show("Pin button got Clicked");
}
protected override void WndProc(ref Message message)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_MOVE = 0xF010;
switch (message.Msg)
{
case WM_SYSCOMMAND:
int command = message.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE)
return;
break;
}
base.WndProc(ref message);
}
}
And this is for button.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Diagnostics;
using System.Resources;
using System.Runtime.InteropServices;
namespace TyronM {
public delegate void MinTrayBtnClickedEventHandler(object sender, EventArgs e);
/// <summary>
/// Summary description for Class.
/// </summary>
public class MinTrayBtn : NativeWindow {
bool pinned = false;
bool pressed = false;
Size wnd_size = new Size();
public bool captured;
Form parent;
public event MinTrayBtnClickedEventHandler MinTrayBtnClicked;
#region Constants
const int WM_SIZE = 5;
const int WM_SYNCPAINT = 136;
const int WM_MOVE = 3;
const int WM_ACTIVATE = 6;
const int WM_LBUTTONDOWN =513;
const int WM_LBUTTONUP =514;
const int WM_LBUTTONDBLCLK =515;
const int WM_MOUSEMOVE = 512;
const int WM_PAINT = 15;
const int WM_GETTEXT = 13;
const int WM_NCCREATE =129;
const int WM_NCLBUTTONDOWN = 161;
const int WM_NCLBUTTONUP = 162;
const int WM_NCMOUSEMOVE = 160;
const int WM_NCACTIVATE =134;
const int WM_NCPAINT = 133;
const int WM_NCHITTEST = 132;
const int WM_NCLBUTTONDBLCLK = 163;
const int VK_LBUTTON = 1;
const int SM_CXSIZE = 30;
const int SM_CYSIZE = 31;
#endregion
#region Extra Constants
const int WM_MBUTTONUP = 0x0208;
#endregion
#region WinAPI Imports
[DllImport("user32")]
public static extern int GetWindowDC(int hwnd);
[DllImport("user32")]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32")]
public static extern int SetCapture(int hwnd);
[DllImport("user32")]
public static extern bool ReleaseCapture();
[DllImport("user32")]
public static extern int GetSysColor(int nIndex);
[DllImport("user32")]
public static extern int GetSystemMetrics(int nIndex);
#endregion
#region Constructor and Handle-Handler ^^
public MinTrayBtn(Form parent) {
parent.HandleCreated += new EventHandler(this.OnHandleCreated);
parent.HandleDestroyed+= new EventHandler(this.OnHandleDestroyed);
parent.TextChanged+= new EventHandler(this.OnTextChanged);
this.parent = parent;
}
// Listen for the control's window creation and then hook into it.
internal void OnHandleCreated(object sender, EventArgs e){
// Window is now created, assign handle to NativeWindow.
AssignHandle(((Form)sender).Handle);
}
internal void OnHandleDestroyed(object sender, EventArgs e) {
// Window was destroyed, release hook.
ReleaseHandle();
}
// Changing the Text invalidates the Window, so we got to Draw the Button again
private void OnTextChanged(object sender, EventArgs e) {
DrawButton();
}
#endregion
#region WndProc
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
protected override void WndProc(ref Message m){
//label3.Text = "Button pressed: " + pressed;
//label4.Text = "Mouse captured: " + captured;
// Change the Pressed-State of the Button when the User pressed the
// left mouse button and moves the cursor over the button
if (m.Msg == WM_LBUTTONDBLCLK ||m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONUP
||m.Msg == WM_MBUTTONUP
)
MessageBox.Show("click happened");
if(m.Msg==WM_MOUSEMOVE) {
Point pnt2 = new Point((int)m.LParam);
Size rel_pos2 = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
// Not needed because SetCapture seems to convert the cordinates anyway
//pnt2 = PointToClient(pnt2);
pnt2-=rel_pos2;
//label2.Text = "Cursor #"+pnt2.X+"/"+pnt2.Y;
if(pressed) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(!MouseinBtn(pnt)) {
pressed = false;
DrawButton();
}
} else {
if((GetAsyncKeyState(VK_LBUTTON)&(-32768))!=0) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
pressed = true;
DrawButton();
}
}
}
}
// Ignore Double-Clicks on the Traybutton
if(m.Msg==WM_NCLBUTTONDBLCLK) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
return;
}
}
#region NOT WORKING
// Button released and eventually clicked
if(m.Msg==WM_LBUTTONUP)
{
ReleaseCapture();
captured = false;
if(pressed) {
pressed = false;
DrawButton();
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
//TrayButton_clicked();
EventArgs e = new EventArgs();
if (MinTrayBtnClicked != null)
MinTrayBtnClicked(this, e);
return;
}
}
}
#endregion
// Clicking the Button - Capture the Mouse and await until the Uses relases the Button again
if(m.Msg==WM_NCLBUTTONDOWN) {
//MessageBox.Show("clicked");
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
MessageBox.Show("clicked");
pressed = true;
DrawButton();
SetCapture((int)parent.Handle);
captured = true;
return;
}
}
// Drawing the Button and getting the Real Size of the Window
if(m.Msg == WM_ACTIVATE || m.Msg==WM_SIZE || m.Msg==WM_SYNCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCCREATE || m.Msg==WM_NCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCHITTEST || m.Msg==WM_PAINT) {
if(m.Msg==WM_SIZE) wnd_size = new Size(new Point((int)m.LParam));
DrawButton();
}
base.WndProc(ref m);
}
#endregion
#region Button-Specific Functions
public bool MouseinBtn(Point click) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
Size btn_size = new Size(btn_width, btn_height);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
return click.X>=pos.X && click.X<=pos.X+btn_size.Width &&
click.Y>=pos.Y && click.Y<=pos.Y+btn_size.Height;
}
public void DrawButton() {
Graphics g = Graphics.FromHdc((IntPtr)GetWindowDC((int)parent.Handle)); //m.HWnd));
DrawButton(g, pressed);
}
public void DrawButton(Graphics g, bool pressed) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
//Point pos = new Point(wnd_size.Width-3*btn_width-12-(btn_width-18),6);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
// real button size
btn_width-=2;
btn_height-=4;
Color light = SystemColors.ControlLightLight;
Color icon = SystemColors.ControlText;
Color background = SystemColors.Control;
Color shadow1 = SystemColors.ControlDark;
Color shadow2 = SystemColors.ControlDarkDark;
Color tmp1, tmp2;
if(pressed) {
tmp1 = shadow2;
tmp2 = light;
} else {
tmp1 = light;
tmp2 = shadow2;
}
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X+btn_width-1,pos.Y));
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X,pos.Y+btn_height-1));
if(pressed) {
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+btn_width-2, pos.Y+1);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+1, pos.Y+btn_height-2);
} else {
g.DrawLine(new Pen(shadow1),pos.X+btn_width-2, pos.Y+1, pos.X+btn_width-2, pos.Y+btn_height-2);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+btn_height-2, pos.X+btn_width-2, pos.Y+btn_height-2);
}
g.DrawLine(new Pen(tmp2),pos.X+btn_width-1, pos.Y+0, pos.X+btn_width-1, pos.Y+btn_height-1);
g.DrawLine(new Pen(tmp2),pos.X+0, pos.Y+btn_height-1, pos.X+btn_width-1, pos.Y+btn_height-1);
g.FillRectangle(new SolidBrush(background),pos.X+1+Convert.ToInt32(pressed), pos.Y+1+Convert.ToInt32(pressed), btn_width-3,btn_height-3);
#region Added Code
g.FillRectangle(new SolidBrush(icon),pos.X+(float)0.5625*btn_width+Convert.ToInt32(pressed),pos.Y+(float)0.6428*btn_height+Convert.ToInt32(pressed),btn_width*(float)0.1875,btn_height*(float)0.143);
g.DrawImage(Image.FromFile("red_Pushpin.jfif"),new Rectangle( pos,new Size(btn_width,btn_height)));
#endregion
}
#endregion
}
}
This is code for main
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
This is the screenshot of the winform.
EDIT:
Upon investigation I found that WM_LBUTTONUP(Mouse left button up) is not triggered. Or it is not coming inside WndProc.
EDIT1:
It seems there was a problem in brackets.I changed the if conditions into switch case. This somewhat cleared the "Mouse left button up" problem. Now it is properly triggering. The remaining problem is mouse points based on parent form.
I am currently working on a windows touch application. Some winForm code remains. As you can see the height of the scroll/arrow buttons height is really too small for a touch button. Is there a way to increase the height to 35/40pixels?
The following link is a VS2012 c# example project download page.
download example here
Thank you.
This solution enumerates the child windows of the ContextMenuStrip. It might happen that there are two child windows (scroll buttons) or zero child windows.
The control used for the scroll buttons is a label, and the default uses a small 9x5 image. The image is updated to a larger image (using the Marlett font), and then AutoSize is set to true which causes the label to resize itself.
Edit: changed implementation to an extension method for better flexibility
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Opulos.Core.UI {
///<summary>Extension class to increase the size of the scroll up-down arrows on a drop down context menu or tool strip menu. The default up-down arrows are only 5 pixels high.</summary>
public static class ToolStripEx {
private static Hashtable htData = new Hashtable();
private class Data {
public bool needsUpdate = true;
public bool disposeLastImage = false;
public ToolStrip toolStrip = null;
public List<Image> currentImages = new List<Image>();
}
public static void BigButtons(this ToolStrip toolStrip) {
htData[toolStrip] = new Data() { toolStrip = toolStrip };
toolStrip.VisibleChanged += toolStrip_VisibleChanged;
toolStrip.ForeColorChanged += toolStrip_ForeColorChanged;
toolStrip.Disposed += toolStrip_Disposed;
}
static void toolStrip_Disposed(object sender, EventArgs e) {
Data d = (Data) htData[sender];
if (d != null && d.currentImages != null) {
foreach (var img in d.currentImages)
img.Dispose();
d.currentImages = null;
htData.Remove(sender);
}
}
static void toolStrip_ForeColorChanged(object sender, EventArgs e) {
Data d = (Data) htData[sender];
d.needsUpdate = true;
UpdateImages(d);
}
static void toolStrip_VisibleChanged(object sender, EventArgs e) {
Data d = (Data) htData[sender];
UpdateImages(d);
}
private static void UpdateImages(Data d) {
if (!d.needsUpdate)
return;
d.toolStrip.BeginInvoke((Action) delegate {
try {
var list = GetChildWindows(d.toolStrip.Handle);
if (list.Count == 0)
return;
List<Image> newImages = new List<Image>();
int k = 0;
foreach (var i in list) {
var c = Control.FromHandle(i) as Label;
if (c != null && d.needsUpdate) {
String glyph = (k == 0 ? "t" : "u");
using (Font f = new System.Drawing.Font("Marlett", 20f)) {
Size s = TextRenderer.MeasureText("t", f);
var oldImage = c.Image;
c.Image = new Bitmap(s.Width, s.Height);
newImages.Add(c.Image);
// avoid disposing the default image
// might cause problems, not sure
if (d.disposeLastImage)
oldImage.Dispose();
using (Graphics g = Graphics.FromImage(c.Image)) {
using (Brush b = new SolidBrush(d.toolStrip.ForeColor))
g.DrawString(glyph, f, b, 0, 0);
}
c.AutoSize = true;
}
k++;
}
}
if (newImages.Count > 0) {
d.needsUpdate = false;
d.disposeLastImage = true;
d.currentImages = newImages;
}
} catch {} // protect against crash (just in case)
});
}
private static List<IntPtr> GetChildWindows(IntPtr parent) {
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try {
EnumChildWindows(parent, enumProc, GCHandle.ToIntPtr(listHandle));
} finally {
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
private delegate bool EnumChildProc(IntPtr hWnd, IntPtr lParam);
private static EnumChildProc enumProc = new EnumChildProc(CallChildEnumProc);
private static bool CallChildEnumProc(IntPtr hWnd, IntPtr lParam) {
GCHandle gch = GCHandle.FromIntPtr(lParam);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
list.Add(hWnd);
return true;
}
[DllImport("user32.dll")]
private static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildProc lpEnumFunc, IntPtr lParam);
}
}
[STAThread]
static void Main() {
Application.EnableVisualStyles();
var cms = new ContextMenuStrip();
cms.BigButtons();
for (int i = 0; i < 20; i++)
cms.Items.Add(new ToolStripMenuItem("Item " + i));
cms.MaximumSize = new Size(200, 400);
Form f = new Form();
f.ContextMenuStrip = cms;
Application.Run(f);
}
Inside my richtextbox have a checkbox, when scroll down or up my richtextbox, checkbox not scrolling whit it.
CheckBox chk = new CheckBox();
chk.Name = "Chk" + i;
chk.Location = new Point(80,10+i);
chk.Text = "Save";
chk.Size = new System.Drawing.Size(20, 100);
richTextBox1.Controls.Add(chk);
i++;
Can you tell me how to solve it.
A RichTextBox cannot contain a CheckBox object. and you have set static point for location of your checkpoint
chk.Location = new Point(80,10+i)
, and it is over RichTextBox , not into that
You can handle the MouseWheel and Scroll events of your RichTextBox and use the GetScrollPos win32 function to get the new position of the scrollbar of your RichTextBox, then update the position of your CheckBox accordingly. Notice that when the MouseWheel is raised, the position of the ScrollBar is not changed to the new one immediately, it will be changed smoothly from the current to the new one. That's why we have to use a Timer to call the GetScrollPos repeatedly an periodically until the return position is the same. The effect we get is not perfect as the smoothness of the scrollbar moving but it's close to that smoothness and far better than calling the GetScrollPos once right in the MouseWheel event handler.
However in this code, I would like to use NativeWindow to hook into the message loop and fetch the messages sent to the RichTextBox, here is the code which works OK, this code is just a demo and handle the Vertical scrolling only. You can find more info on WM_HSCROLL and GetScrollPos to handle the Horizontal scrolling (it's easy because it's very much similar to the Vertical scrolling):
public partial class Form1 : Form
{
[DllImport("user32")]
private static extern int GetScrollPos(IntPtr hwnd, int nBar);
public Form1()
{
InitializeComponent();
chk.Text = ".NET pro";
chk.Parent = richTextBox1;
chk.Top = 100;//Notice the initial Top to update the position properly later.
nativeRichText.AssignHandle(richTextBox1.Handle);
//Scroll event handler for the nativeRichText
nativeRichText.Scroll += (s, e) =>
{
chk.Top = 100-e.Y;
};
//TextChanged event handler for the richTextBox1
richTextBox1.TextChanged += (s, e) =>
{
chk.Top = 100-GetScrollPos(richTextBox1.Handle, 1);
};
}
CheckBox chk = new CheckBox();
NativeRichTextBox nativeRichText = new NativeRichTextBox();
public class NativeRichTextBox : NativeWindow
{
Timer t = new Timer();
int y = -1;
public NativeRichTextBox()
{
t.Interval = 30;
t.Tick += (s, e) =>
{
int y2 = Form1.GetScrollPos(Handle, 1);//nBar =1 => Vertical bar
if (y2 == y) { t.Stop(); return; }
y = y2;
if (Scroll != null) Scroll(this, new ScrollEventArgs(0, y));
};
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x115)//WM_VSCROLL = 0x115
{
int wp = m.WParam.ToInt32();
int low = wp & 0x00ff;
if (low == 4 || low == 5)//SB_THUMBPOSITION = 4 SB_THUMBTRACK = 5
{
if (Scroll != null) Scroll(this, new ScrollEventArgs(0, wp >> 16));
}
else t.Start();
}
if (m.Msg == 0x20a)//WM_MOUSEWHEEL = 0x20a
{
y = -1;
t.Start();
}
base.WndProc(ref m);
}
public class ScrollEventArgs : EventArgs
{
public int X { get; set; }
public int Y { get; set; }
public ScrollEventArgs(int x, int y)
{
X = x;
Y = y;
}
}
public delegate void ScrollEventHandler(object sender, ScrollEventArgs e);
public event ScrollEventHandler Scroll;
}
}
Most probably you have seen around checkboxes inside comboboxes or dropdown lists.
It is possible to do it. You may try with third parties controls like this, or code your own. This topic was already discussed in another question on SO, have a look here, so we do not restart debating the same topic in this question.
What you are obtaining with your code instead, is just to put graphically one control on another but they do not interact to each other. They are two separate controls.
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.