.Net中的同步ListView
我正在一个控件上,以将视图从一个ListView绑定到另一个视图,以便在滚动主ListView时,子ListView视图被更新以匹配。
到目前为止,单击主滚动条按钮时,我已经能够使子ListView更新其视图。问题在于,当单击并拖动ScrollBar本身时,子ListViews不会更新。我已经查看了使用Spy ++发送的消息,并且正在发送正确的消息。
这是我当前的代码:
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); } } } } }
基于MS技术论坛上的这篇文章,我试图捕获和处理SB_THUMBTRACK事件:
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); }
这将更新子级ListView ScrollBar的位置,但不会更改子级中的实际视图。
所以我的问题是:
- 拖动主ListView ScrollBar时是否可以更新子级ListViews?
- 如果是这样,怎么办?
解决方案
幼稚的解决方案是在父级列表视图中处理绘制消息,并检查链接的列表视图是否显示正确的数据。如果不正确,则可以通过调用SecureVisible方法来更新它们以显示正确的数据。
这只是一种猜测,只是为了使精神力量畅通无阻,所以请尽可能采取以下措施:
在主列表的滚动处理程序中,我们可以调用子列表的滚动处理程序(从主列表传递发送者和eventargs)吗?
将此添加到表单加载中:
masterList.Scroll += new ScrollEventHandler(this.masterList_scroll);
引用了以下内容:
private void masterList_scroll(Object sender, System.ScrollEventArgs e) { childList_scroll(sender, e); } private void childList_scroll(Object sender, System.ScrollEventArgs e) { childList.value = e.NewValue }
我将创建自己的类,从ListView继承以显示Vertical和Horizontal滚动事件。
然后,我将在表单中创建滚动处理程序以同步两个控件
这是示例代码,应允许列表视图发布滚动事件:
public class MyListView : System.Windows.Forms.ListView { const int WM_HSCROLL = 0x0114; const int WM_VSCROLL = 0x0115; private ScrollEventHandler evtHScroll_m; private ScrollEventHandler evtVScroll_m; public event ScrollEventHandler OnHScroll { add { evtHScroll_m += value; } remove { evtHScroll_m -= value; } } public event ScrollEventHandler OnHVcroll { add { evtVScroll_m += value; } remove { evtVScroll_m -= value; } } protected override void WndProc(ref System.Windows.Forms.Message msg) { if (msg.Msg == WM_HSCROLL && evtHScroll_m != null) { evtHScroll_m(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32())); } if (msg.Msg == WM_VSCROLL && evtVScroll_m != null) { evtVScroll_m(this, new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32())); } base.WndProc(ref msg); }
现在处理表单中的滚动事件:
设置一个PInvoke方法,以便能够向控件发送Windows消息:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int iMsg, int iWParam, int iLParam);
设置事件处理程序(lstMaster和lstChild是两个列表框):
lstMaster.OnVScroll += new ScrollEventHandler(this.lstMaster_OnVScroll); lstMaster.OnHScroll += new ScrollEventHandler(this.lstMaster_OnHScroll); const int WM_HSCROLL = 0x0114; const int WM_VSCROLL = 0x0115; private void lstMaster_OnVScroll(Object sender, System.ScrollEventArgs e) { SendMessage(lstChild.Handle,WM_VSCROLL,(IntPtr)e.NewValue, IntPtr.Zero); } private void lstMaster_OnHScroll(Object sender, System.ScrollEventArgs e) { SendMessage(lstChild.Handle,WM_HSCROLL,(IntPtr)e.NewValue, IntPtr.Zero); }
我想做同样的事情,在搜索之后,我在这里找到了代码,这很有帮助,但是当然并不能解决问题。但是在玩了之后,我找到了解决方案。
关键是我意识到既然可以使用滚动按钮,那么可以使用它来使滑块起作用。换句话说,当SB_THUMBTRACK事件到来时,我会重复发出SB_LINELEFT和SB_LINERIGHT事件,直到我的子级ListView接近母版所在的位置。是的,这不是完美的,但效果足够好。
在我的情况下,我的主ListView称为" reportView",而我的子ListView称为" summaryView"。这是我的相关代码:
public class MyListView : ListView { public event ScrollEventHandler HScrollEvent; protected override void WndProc(ref System.Windows.Forms.Message msg) { if (msg.Msg==WM_HSCROLL && HScrollEvent != null) HScrollEvent(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, (int)msg.WParam)); base.WndProc(ref msg); } }
然后是事件处理程序本身:
reportView.HScrollEvent += new ScrollEventHandler((sender,e) => { if ((ushort) e.NewValue != SB_THUMBTRACK) SendMessage(summaryView.Handle, WM_HSCROLL, (IntPtr) e.NewValue, IntPtr.Zero); else { int newPos = e.NewValue >> 16; int oldPos = GetScrollPos(reportView .Handle, SB_HORZ); int pos = GetScrollPos(summaryView.Handle, SB_HORZ); int lst; if (pos != newPos) if (pos<newPos && oldPos<newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINERIGHT,IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) < newPos && pos!=lst); else if (pos>newPos && oldPos>newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINELEFT, IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) > newPos && pos!=lst); } });
抱歉,while循环的奇数格式化在那里,但是那是我更喜欢编码的方式。
下一个问题是摆脱子ListView中的滚动条。我注意到我们有一个名为HideScrollBar的方法。这对我来说真的没有用。在我的情况下,我发现一个更好的解决方案是将滚动条留在那儿,而是"覆盖"它。我也使用列标题进行此操作。我只是将子控件向上滑动到主控件下,以覆盖列标题。然后,我拉伸孩子,使其脱离包含它的面板。然后为了沿我的包含面板的边缘提供一些边框,我放入一个控件以覆盖子ListView的可见底部边缘。最终看起来还不错。
我还添加了一个事件处理程序来同步更改列的宽度,如下所示:
reportView.ColumnWidthChanging += new ColumnWidthChangingEventHandler((sender,e) => { summaryView.Columns[e.ColumnIndex].Width = e.NewWidth; });
尽管这一切似乎有些矛盾,但对我有用。