Question

Je travaille sur un contrôle qui relie 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 le ScrollBar lui-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<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);
                }
            }
        }
    }
}

Basé sur cet article sur les forums MS Tech J'ai essayé 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);
    }

Ceci mettra à jour l'emplacement de l'enfant ListView ScrollBar mais ne modifiera pas l'affichage réel dans l'enfant.

Mes questions sont donc les suivantes:

  1. Est-il possible de mettre à jour l'enfant ListViews lorsque le ListView ScrollBar principal est déplacé?
  2. Si oui, comment?
Était-ce utile?

La solution

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 bien sûr pas résolu le problème. Mais après avoir joué avec, j'ai trouvé une solution.

La clé est arrivée 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 enfant 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 ensuite le gestionnaire d'événements 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      (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);
        }
    });

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. Dans mon cas, j'ai trouvé une meilleure solution en laissant la barre de défilement à cet endroit, mais en "couvrant". à 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 modifiées, comme dans:

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

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

Autres conseils

C’est une conjecture juste pour faire couler le jus mental, 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 des enfants (en transmettant l'expéditeur et les arguments du maître)?

Ajoutez ceci à votre chargement de formulaire:

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

Laquelle référence à ceci:

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

Il s'agit d'un exemple de code qui devrait permettre à une liste de diffusion 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); 
    }

Traitez maintenant 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top