Я работаю над элементом управления, чтобы связать представление из одного 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, но не изменит фактическое представление в дочернем элементе.
Итак, мои вопросы:
Я пытался заставить какой-то унаследованный код работать правильно с минимально возможным количеством изменений. Однако все больше и больше похоже на то, что GridView - лучший вариант.





Наивным решением вашей проблемы может быть обработка сообщения рисования в представлении родительского списка и проверка того, отображают ли представления связанного списка правильные данные. В противном случае обновите их, чтобы отобразить правильные данные, вызвав метод EnsureVisible.
Кажется, что метод EnsureVisible прокручивается только по вертикали (в режиме детализации), поскольку нет возможности указать SubItem. К сожалению, моя проблема связана именно с горизонтальной прокруткой.
Ой, извини. Я не заметил, что вы говорили о горизонтальной прокрутке. Итак, вот поправка к приведенному выше предложению: возможно, вы можете получить позицию прокрутки с помощью P-вызова GetScrollPos в родительском списке и при необходимости обновить связанные списки.
Это предположение только для того, чтобы заставить умственные соки течь, поэтому принимайте это как хотите: Можете ли вы в обработчике прокрутки для главного списка вызвать обработчик прокрутки для дочернего списка (передавая отправителя и события из главного списка)?
Добавьте это в свою загрузку формы:
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).
вы, по-видимому, можете (groups.google.com/group/…) наложить vScrollBar (и hScroll, как я могу представить) в своем пользовательском элементе управления, чтобы получить этот эффект.
Еще одна хорошая идея ... но она не сработает в моем конкретном сценарии. Мне нужно иметь возможность прокручивать по горизонтали, а метод EnsureVisible (используемый решением в этой ссылке), похоже, поддерживает только вертикальную прокрутку (нет возможности указать SubItem).
В этом случае вам нужно будет либо создать новый элемент управления полностью с нуля, либо использовать GridView или другой элемент управления, который поддерживает .scroll
Я думаю, что создание настраиваемого элемента управления - лучшее решение, чем вызывание и попытка взлома желаемого поведения.
Я бы создал свой собственный класс, унаследованный от 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);
}
Это не решает проблему. Проблема, с которой я столкнулся, связана с перетаскиванием полосы прокрутки, а не с нажатием кнопок со стрелками.
Я хотел сделать то же самое, и после поиска я нашел здесь ваш код, который помог, но, конечно, не решил проблему. Но поигравшись с этим, я нашел решение.
Ключ пришел, когда я понял, что, поскольку кнопки прокрутки работают, вы можете использовать их для работы ползунка. Другими словами, когда приходит событие 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;
});
Хотя все это кажется чем-то вроде лабиринта, у меня это работает.
Есть ли причина, по которой вы используете ListView вместо GridView?