Синхронизированный ListViews в .Net - PullRequest
4 голосов
/ 02 октября 2008

Я работаю над элементом управления, чтобы связать воедино представление из одного 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 Tech, которые я пытался перехватить и обработать событие 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. Можно ли обновить дочерние ListViews при перетаскивании основного ListView ScrollBar?
  2. Если да, то как?

Ответы [ 4 ]

2 голосов
/ 09 ноября 2008

Я хотел сделать то же самое, и после поиска я нашел здесь ваш код, который помог, но, конечно, не решил проблему. Но поиграв с этим, я нашел решение.

Ключ пришел, когда я понял, что, поскольку кнопки прокрутки работают, вы можете использовать это, чтобы заставить работать ползунок. Другими словами, когда наступает событие 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;
    });         

Хотя все это кажется чем-то вроде клуджа, у меня это работает.

1 голос
/ 03 октября 2008

Это предположение только для того, чтобы заставить психические соки течь, поэтому примите это как хотите: В обработчике прокрутки для основного списка вы можете вызвать обработчик прокрутки для дочернего списка (передавая отправитель и 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
}
1 голос
/ 06 октября 2008

Я бы создал свой собственный класс, унаследованный от 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); 
}
0 голосов
/ 03 октября 2008

Наивным решением вашей проблемы может быть обработка сообщения рисования в представлении родительского списка и проверка отображения правильных данных в связанных представлениях списка. Если они этого не делают, обновите их, чтобы они отображали правильные данные, вызвав метод EnsureVisible.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...