I’m working on a control to tie together the view from one ListView to another so that when the master ListView is scrolled, the child ListView view is updated to match.
So far I’ve been able to get the child ListViews to update their view when the master scrollbar buttons are clicked. The problem is that when clicking and dragging the ScrollBar itself, the child ListViews are not updated. I’ve looked at the messages being sent using Spy++ and the correct messages are getting sent.
Here is my current code:
public partial class LinkedListViewControl : ListView { [DllImport('User32.dll')] private static extern bool SendMessage(IntPtr hwnd, UInt32 msg, IntPtr wParam, IntPtr lParam); [DllImport('User32.dll')] private static extern bool ShowScrollBar(IntPtr hwnd, int wBar, bool bShow); [DllImport('user32.dll')] private static extern int SetScrollPos(IntPtr hWnd, int wBar, int nPos, bool bRedraw); private const int WM_HSCROLL = 0x114; private const int SB_HORZ = 0; private const int SB_VERT = 1; private const int SB_CTL = 2; private const int SB_BOTH = 3; private const int SB_THUMBPOSITION = 4; private const int SB_THUMBTRACK = 5; private const int SB_ENDSCROLL = 8; public LinkedListViewControl() { InitializeComponent(); } private readonly List<ListView> _linkedListViews = new List<ListView>(); public void AddLinkedView(ListView listView) { if (!_linkedListViews.Contains(listView)) { _linkedListViews.Add(listView); HideScrollBar(listView); } } public bool RemoveLinkedView(ListView listView) { return _linkedListViews.Remove(listView); } private void HideScrollBar(ListView listView) { //Make sure the list view is scrollable listView.Scrollable = true; //Then hide the scroll bar ShowScrollBar(listView.Handle, SB_BOTH, false); } protected override void WndProc(ref Message msg) { if (_linkedListViews.Count > 0) { //Look for WM_HSCROLL messages if (msg.Msg == WM_HSCROLL) { foreach (ListView view in _linkedListViews) { SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero); } } } } }
Based on this post on the MS Tech Forums I tried to capture and process the SB_THUMBTRACK event:
protected override void WndProc(ref Message msg) { if (_linkedListViews.Count > 0) { //Look for WM_HSCROLL messages if (msg.Msg == WM_HSCROLL) { Int16 hi = (Int16)((int)msg.WParam >> 16); Int16 lo = (Int16)msg.WParam; foreach (ListView view in _linkedListViews) { if (lo == SB_THUMBTRACK) { SetScrollPos(view.Handle, SB_HORZ, hi, true); int wParam = 4 + 0x10000 * hi; SendMessage(view.Handle, WM_HSCROLL, (IntPtr)(wParam), IntPtr.Zero); } else { SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero); } } } } // Pass message to default handler. base.WndProc(ref msg); }
This will update the location of the child ListView ScrollBar but does not change the actual view in the child.
So my questions are:
- Is it possible to update the child ListViews when the master ListView ScrollBar is dragged?
- If so, how?
I wanted to do the same thing, and after searching around I found your code here, which helped, but of course didn’t solve the problem. But after playing around with it, I have found a solution.
The key came when I realized that since the scroll buttons work, that you can use that to make the slider work. In other words, when the SB_THUMBTRACK event comes in, I issue repeated SB_LINELEFT and SB_LINERIGHT events until my child ListView gets close to where the master is. Yes, this isn’t perfect, but it works close enough.
In my case, my master ListView is called ‘reportView’, while my child ListView is called ‘summaryView’. Here’s my pertinent code:
And then the event handler itself:
Sorry about the odd formatting of the while loops there, but that’s how I prefer to code things like that.
The next problem was getting rid of the scroll bars in the child ListView. I noticed you had a method called HideScrollBar. This didn’t really work for me. I found a better solution in my case was leaving the scroll bar there, but ‘covering’ it up instead. I do this with the column header as well. I just slide my child control up under the master control to cover the column header. And then I stretch the child to fall out of the panel that contains it. And then to provide a bit of a border along the edge of my containing panel, I throw in a control to cover the visible bottom edge of my child ListView. It ends up looking rather nice.
I also added an event handler to sync changing column widths, as in:
While this all seems a bit of a kludge, it works for me.