4

我正在开发一个控件,将视图从一个 ListView 绑定到另一个,以便在滚动主 ListView 时,更新子 ListView 视图以匹配。

到目前为止,当单击主滚动条按钮时,我已经能够让子 ListViews 更新他们的视图。问题是当单击并拖动 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 的位置,但不会更改子视图中的实际视图。

所以我的问题是:

  1. 拖动主 ListView ScrollBar 时是否可以更新子 ListViews?
  2. 如果是这样,怎么做?
4

4 回答 4

2

我想做同样的事情,在四处搜索后,我在这里找到了你的代码,这有帮助,但当然没有解决问题。但是在玩了它之后,我找到了解决方案。

当我意识到由于滚动按钮起作用时,关键就来了,您可以使用它来使滑块起作用。换句话说,当 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;
    });         

虽然这一切看起来有点杂乱无章,但它对我有用。

于 2008-11-09T14:18:13.797 回答
1

这只是为了让精神汁液流动而进行的猜想,因此请随心所欲:在主列表的滚动处理程序中,您可以调用子列表的滚动处理程序(从主列表传递发送者和事件参数)吗?

将此添加到您的表单加载中:

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
}
于 2008-10-03T14:18:51.777 回答
1

我将创建自己的类,从 ListView 继承以公开垂直和水平滚动事件。

然后我会在我的表单中创建滚动处理程序来同步两个控件

这是应该允许列表视图发布滚动事件的示例代码:

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); 
}
于 2008-10-06T18:23:22.467 回答
0

您的问题的一个简单的解决方案是在父列表视图中处理绘制消息并检查链接列表视图是否显示正确的数据。如果没有,则通过调用 EnsureVisible 方法更新它们以显示正确的数据。

于 2008-10-03T09:17:24.110 回答