Синхронизированные ListViews в .Net

Я работаю над элементом управления, чтобы связать представление из одного ListView с другим, чтобы при прокрутке основного ListView дочернее представление ListView обновлялось, чтобы соответствовать.

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

Есть ли причина, по которой вы используете ListView вместо GridView?

Rob Allen 03.10.2008 20:09

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

akmad 03.10.2008 20:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
6 104
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Кажется, что метод EnsureVisible прокручивается только по вертикали (в режиме детализации), поскольку нет возможности указать SubItem. К сожалению, моя проблема связана именно с горизонтальной прокруткой.

akmad 03.10.2008 19:47

Ой, извини. Я не заметил, что вы говорили о горизонтальной прокрутке. Итак, вот поправка к приведенному выше предложению: возможно, вы можете получить позицию прокрутки с помощью P-вызова GetScrollPos в родительском списке и при необходимости обновить связанные списки.

Yuval Peled 03.10.2008 23:51

Это предположение только для того, чтобы заставить умственные соки течь, поэтому принимайте это как хотите: Можете ли вы в обработчике прокрутки для главного списка вызвать обработчик прокрутки для дочернего списка (передавая отправителя и события из главного списка)?

Добавьте это в свою загрузку формы:

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 не имеет события Scroll (по крайней мере, не в .Net 2.0 / 3.5).

akmad 03.10.2008 19:42

вы, по-видимому, можете (groups.google.com/group/…) наложить vScrollBar (и hScroll, как я могу представить) в своем пользовательском элементе управления, чтобы получить этот эффект.

Rob Allen 03.10.2008 20:05

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

akmad 03.10.2008 20:11

В этом случае вам нужно будет либо создать новый элемент управления полностью с нуля, либо использовать GridView или другой элемент управления, который поддерживает .scroll

Rob Allen 03.10.2008 20:15

Я думаю, что создание настраиваемого элемента управления - лучшее решение, чем вызывание и попытка взлома желаемого поведения.

user1228 06.10.2008 15:28

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

Это не решает проблему. Проблема, с которой я столкнулся, связана с перетаскиванием полосы прокрутки, а не с нажатием кнопок со стрелками.

akmad 07.10.2008 20:48
Ответ принят как подходящий

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

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

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

Другие вопросы по теме