How do I sync scrolling in two treeviews using the slider
I am using Visual Studio 2010 (C#) and a Windows Forms application.
I have two treeviews side by side, and I have figured out how to synchronise the scrolling using the up/down buttons on the scrollbar, but when I use the slider it does not move the other treeview. I have taken a listview example that works, but the same code does not work for treeviews.
So far I have, in the main form:
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam);
private void myListBox1_Scroll(ref Message m)
{
SendMessage(myListBox2.Handle, (uint)m.Msg, (uint)m.WParam, (uint)m.LParam);
}
I have created a control:
public partial class MyTreeView : TreeView
{
public MyTreeView()
{
InitializeComponent();
}
public event ScrollEventHandler Scroll;
public delegate void ScrollEventHandler(ref Message m);
private const int WM_VSCROLL = 0x115;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_VSCROLL)
if (Scroll != null)
{
Scroll(ref m);
}
base.WndProc(ref m);
}
}
which I add two of to the form.
I can use the same code to have a listivew control the treeview and tha开发者_运维问答t will work if you drag the slider, but in reverse it only works with the up down buttons.
Rather than use SendMessage and mark your DLL as unsafe you can use the GetScrollPos
and SetScrollPos
functions from user32.dll.
I've wrapped the code up into your MyTreeView class so it's nicely encapsulated.
You just need to call the AddLinkedTreeView
method like so:
treeView1.AddLinkedTreeView(treeView2);
Here's the source for the MyTreeView class.
public partial class MyTreeView : TreeView
{
public MyTreeView() : base()
{
}
private List<MyTreeView> linkedTreeViews = new List<MyTreeView>();
/// <summary>
/// Links the specified tree view to this tree view. Whenever either treeview
/// scrolls, the other will scroll too.
/// </summary>
/// <param name="treeView">The TreeView to link.</param>
public void AddLinkedTreeView(MyTreeView treeView)
{
if (treeView == this)
throw new ArgumentException("Cannot link a TreeView to itself!", "treeView");
if (!linkedTreeViews.Contains(treeView))
{
//add the treeview to our list of linked treeviews
linkedTreeViews.Add(treeView);
//add this to the treeview's list of linked treeviews
treeView.AddLinkedTreeView(this);
//make sure the TreeView is linked to all of the other TreeViews that this TreeView is linked to
for (int i = 0; i < linkedTreeViews.Count; i++)
{
//get the linked treeview
var linkedTreeView = linkedTreeViews[i];
//link the treeviews together
if (linkedTreeView != treeView)
linkedTreeView.AddLinkedTreeView(treeView);
}
}
}
/// <summary>
/// Sets the destination's scroll positions to that of the source.
/// </summary>
/// <param name="source">The source of the scroll positions.</param>
/// <param name="dest">The destinations to set the scroll positions for.</param>
private void SetScrollPositions(MyTreeView source, MyTreeView dest)
{
//get the scroll positions of the source
int horizontal = User32.GetScrollPos(source.Handle, Orientation.Horizontal);
int vertical = User32.GetScrollPos(source.Handle, Orientation.Vertical);
//set the scroll positions of the destination
User32.SetScrollPos(dest.Handle, Orientation.Horizontal, horizontal, true);
User32.SetScrollPos(dest.Handle, Orientation.Vertical, vertical, true);
}
protected override void WndProc(ref Message m)
{
//process the message
base.WndProc(ref m);
//pass scroll messages onto any linked views
if (m.Msg == User32.WM_VSCROLL || m.Msg == User32.WM_MOUSEWHEEL)
{
foreach (var linkedTreeView in linkedTreeViews)
{
//set the scroll positions of the linked tree view
SetScrollPositions(this, linkedTreeView);
//copy the windows message
Message copy = new Message
{
HWnd = linkedTreeView.Handle,
LParam = m.LParam,
Msg = m.Msg,
Result = m.Result,
WParam = m.WParam
};
//pass the message onto the linked tree view
linkedTreeView.RecieveWndProc(ref copy);
}
}
}
/// <summary>
/// Recieves a WndProc message without passing it onto any linked treeviews. This is useful to avoid infinite loops.
/// </summary>
/// <param name="m">The windows message.</param>
private void RecieveWndProc(ref Message m)
{
base.WndProc(ref m);
}
/// <summary>
/// Imported functions from the User32.dll
/// </summary>
private class User32
{
public const int WM_VSCROLL = 0x115;
public const int WM_MOUSEWHEEL = 0x020A;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar);
[DllImport("user32.dll")]
public static extern int SetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar, int nPos, bool bRedraw);
}
}
Edit: Added forwarding of the WM_MOUSEWHEEL message as per MinnesotaFat's suggestion.
I have done this with TextBoxes
before, but I think the solution should work for you as well:
// Get/Set Scroll positions of a control handle
private unsafe Win32.POINT GetScrollPos(System.IntPtr myHandle)
{
Win32.POINT res = new Win32.POINT();
IntPtr ptr = new IntPtr(&res);
Win32.SendMessage(myHandle, Win32.EM_GETSCROLLPOS, 0, ptr);
return res;
}
private unsafe void SetScrollPos(Win32.POINT point, System.IntPtr myHandle)
{
IntPtr ptr = new IntPtr(&point);
Win32.SendMessage(myHandle, Win32.EM_SETSCROLLPOS, 0, ptr);
}
Win32 details
public const int WM_USER = 0x400;
public const int EM_GETSCROLLPOS = (WM_USER + 221);
public const int EM_SETSCROLLPOS = (WM_USER + 222);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[DllImport("user32")] public static extern int SendMessage(
HWND hwnd, int wMsg, int wParam, IntPtr lParam);
Then just attached to both of the ListView
scrolled events and do something like this:
private void ListView1Scrolled(object sender, System.EventArgs e)
{
SetScrollPos(GetScrollPos(ListView1.Handle), ListView2.Handle);
}
private void ListView2Scrolled(object sender, System.EventArgs e)
{
SetScrollPos(GetScrollPos(ListView2.Handle), ListView1.Handle);
}
DoctaJonez' answer works marvellously. For completeness, if you add another condition to the if
statement in the WndProc
method, you can handle the mouse wheel scrolling events as well:
if (m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL)
And declare WM_MOUSEWHEEL:
private cont int WM_MOUSEWHEEL = 0x020A;
精彩评论