ListViews synchronisé en .Net

Je travaille sur un contrôle pour lier ensemble la vue d’un ListView à un autre afin que, lorsque le ListView principal défile, la vue ListView enfant soit mise à jour pour correspondre.

Jusqu’à présent, j’ai pu amener l’enfant ListViews à mettre à jour son affichage lorsque l’utilisateur clique sur les boutons de la barre de défilement principale. Le problème est que, lorsque vous cliquez et faites glisser la barre de défilement elle-même, les enfant ListViews ne sont pas mis à jour. J’ai examiné les messages envoyés à l’aide de Spy ++ et les bons messages ont été envoyés.

Voici mon code actuel:

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 _linkedListViews = new List(); 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); } } } } } 

Sur la base de ce post sur les forums MS Tech, j’ai tenté de capturer et de traiter l’événement 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); } 

Cela mettra à jour l’emplacement de l’enfant ListView ScrollBar mais ne changera pas la vue réelle dans l’enfant.

Donc mes questions sont:

  1. Est-il possible de mettre à jour l’enfant ListViews lorsque le ListView ScrollBar principal est déplacé?
  2. Si c’est le cas, comment?

Je voulais faire la même chose, et après avoir cherché partout, j’ai trouvé votre code ici, ce qui m’a aidé, mais n’a évidemment pas résolu le problème. Mais après avoir joué avec, j’ai trouvé une solution.

La clé est venue quand j’ai réalisé que puisque les boutons de défilement fonctionnent, vous pouvez l’utiliser pour faire fonctionner le curseur. En d’autres termes, lorsque l’événement SB_THUMBTRACK arrive, j’émet des événements SB_LINELEFT et SB_LINERIGHT répétés jusqu’à ce que mon enfant ListView se rapproche de l’endroit où se trouve le maître. Oui, ce n’est pas parfait, mais ça marche assez près.

Dans mon cas, ma liste ListView s’appelle “reportView”, alors que mon fils ListView s’appelle “summaryView”. Voici mon code pertinent:

 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); } } 

Et puis le gestionnaire d’événement lui-même:

 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 (posnewPos && 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); } }); 

Désolé pour le formatage étrange des boucles while, mais c’est comme ça que je préfère coder des choses comme ça.

Le problème suivant consistait à supprimer les barres de défilement de l’enfant ListView. J’ai remarqué que vous aviez une méthode appelée HideScrollBar. Cela n’a pas vraiment fonctionné pour moi. J’ai trouvé une meilleure solution dans mon cas, c’est de laisser la barre de défilement à cet endroit, mais de “couvrir” la place. Je le fais aussi avec l’en-tête de colonne. Je viens de glisser mon contrôle enfant sous le contrôle principal pour couvrir l’en-tête de colonne. Et puis j’étire l’enfant pour qu’il tombe du panneau qui le contient. Et puis pour fournir un peu de bordure le long du bord de mon panneau conteneur, je jette un contrôle qui couvre le bord inférieur visible de mon enfant ListView. Cela finit par avoir l’air plutôt sympa.

J’ai également ajouté un gestionnaire d’événements pour synchroniser les largeurs de colonne changeantes, comme dans:

 reportView.ColumnWidthChanging += new ColumnWidthChangingEventHandler((sender,e) => { summaryView.Columns[e.ColumnIndex].Width = e.NewWidth; }); 

Bien que tout cela semble un peu compliqué, cela fonctionne pour moi.

Ceci est une conjecture juste pour que les idées circulent, alors prenez-le comme vous voulez: Dans le gestionnaire de défilement de la liste principale, pouvez-vous appeler le gestionnaire de défilement de la liste enfant (en transmettant l’expéditeur et eventargs du maître)?

Ajoutez ceci à votre chargement de formulaire:

 masterList.Scroll += new ScrollEventHandler(this.masterList_scroll); 

Quelles références cela:

 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 } 

Je créerais ma propre classe, en héritant de ListView pour exposer les événements de défilement vertical et horizontal.

Ensuite, je créerais des gestionnaires de défilement dans mon formulaire pour synchroniser les deux contrôles.

Ceci est un exemple de code qui devrait permettre à un listview de publier des événements de défilement:

 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); } 

Maintenant, gérez les événements de défilement dans votre formulaire:

Configurez une méthode PInvoke pour pouvoir envoyer un message Windows à un contrôle:

 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int iMsg, int iWParam, int iLParam); 

Configurez vos gestionnaires d’événements (lstMaster et lstChild sont deux zones de liste):

 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); } 

Une solution naïve à votre problème peut être de gérer le message de peinture dans la vue liste parent et de vérifier si les vues liste associées affichent les données correctes. Si ce n’est pas le cas, mettez-les à jour pour afficher les données correctes en appelant la méthode EnsureVisible.