Solved

MFC: CListCtrl with horizontal scroll

Posted on 2011-02-15
25
2,235 Views
Last Modified: 2012-05-11
Dear experts,

I have an application that displays differences between two text files, something similar to comparing functionality of Visual Source Safe.
I am using a CListCtrl with two columns. Some of the lines I display may be very long. Such lines are not displayed in full, but part of them is replaced with "..."
Could you please tell me how I could have horizontal scrollbars in each column?
Also, how could I make both scrollbars work synchronously to make comparison easier, so that dragging one scroll would automatically move another one?
Thanks.
0
Comment
Question by:tantormedia
  • 12
  • 8
  • 3
  • +1
25 Comments
 
LVL 30

Accepted Solution

by:
Zoppo earned 500 total points
ID: 34904269
Hi tantormedia,

I'm not sure if it's possible to implement horizontal scroll for single rows in a view - and, allthough it might be possible I guess it's difficult ...

Maybe a better approach would be to use two CListCtrls and synchronize scrolling between them. You then i.e. could put them into panes of a splitter window. Here you can find a sample about how to synchronize the scrolling between CListCtrls: http://www.codeguru.com/cpp/controls/listview/article.php/c4163/Synchronization-of-scrolling-of-two-list-controls.htm

Hope that helps,

ZOPPO
0
 
LVL 11

Expert Comment

by:DeepuAbrahamK
ID: 34905017
0
 
LVL 11

Expert Comment

by:DeepuAbrahamK
ID: 34905036
I would go for and SDI model with splitter window.
0
 

Author Comment

by:tantormedia
ID: 34906171
DeepuAbrahamK,

I tried this:
ListCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE);
But then the columns have no width at all.
0
 

Author Comment

by:tantormedia
ID: 34906180
ZOPPO,

I don't think I need horizontal scroll for single rows, rather, I need a scroll for each column, but I want these two scrolls synchronized.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34906249
Sorry, that's was my mistake, I meant columns, not rows - but still I think it's not possible to do this. At least not without terrible much effort since simply a column is now window, so no default message processing can be use regarding scrollbars. You would have to simulate scrollbars in a ownerdrawn list control where you handle all relevant messages to simulate scroll bars.

So still I suggest to use two synchronized list controls ...

ZOPPO
0
 

Author Comment

by:tantormedia
ID: 34906278
Then I would need to synchronize both vertically and horizontally... Don't you have any example how that could be done?
Thanks.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34906323
Please take a look at the codeguru link I posted with my first comment - there you find code and a sample demo project which exactly does this, synchronizing horizontal and vertical scrolling between two list controls ...
0
 
LVL 32

Expert Comment

by:sarabande
ID: 34907819
you may try the following code which does vertical synchronizing.

// TwinListCtrl.h : header file

#pragma once

// TwinListCtrl

class TwinListCtrl : public CListCtrl
{
     DECLARE_DYNAMIC(TwinListCtrl)

public:
     TwinListCtrl();
     virtual ~TwinListCtrl();

public:
     afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);

protected:
     DECLARE_MESSAGE_MAP()
};


// TwinListCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "tdiffdlg.h"   // that is my dialog class header
#include "TwinListCtrl.h"


// TwinListCtrl

IMPLEMENT_DYNAMIC(TwinListCtrl, CListCtrl)

TwinListCtrl::TwinListCtrl()
{

}

TwinListCtrl::~TwinListCtrl()
{
}


BEGIN_MESSAGE_MAP(TwinListCtrl, CListCtrl)
    ON_WM_VSCROLL()
END_MESSAGE_MAP()

void TwinListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);

    TDiffDlg * pDlg = ( TDiffDlg *) GetParent();

    if (this->GetDlgCtrlID() == IDC_LIST1)
    {
        OutputDebugString("m_list1.OnVScroll\n");
        pDlg->m_list2.SetScrollPos(SB_VERT, nPos, TRUE);
        // pDlg->m_list2.CListCtrl::OnVScroll(nSBCode, nPos, pDlg->m_list2.GetScrollBarCtrl(SB_VERT));
    }
    else
    {
        OutputDebugString("m_list2.OnVScroll\n");
        pDlg->m_list1.SetScrollPos(SB_VERT, nPos, TRUE);
        // pDlg->m_list1.CListCtrl::OnVScroll(nSBCode, nPos, pDlg->m_list1.GetScrollBarCtrl(SB_VERT));
    }
}

// TwinListCtrl message handlers

Open in new window


you would need to change the class type of the two members m_list1, m_list2 in dialog class by TwinListCtrl.

Sara
0
 

Author Comment

by:tantormedia
ID: 34908289
Because of some reason, horizontal scrolls work fine, and the vertical scroll of the right ListCtrl controls the left ListCtrl, but the vertical scroll of the left ListCtrl doesn't control the right ListCtrl.
Thank you anyway.
0
 

Author Comment

by:tantormedia
ID: 34908307
Oh, I was answering Zoppo. :)
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34908359
Hm - strange - does this happen in the demo project or did you try to implement it in your project and it doesn't work there?

I built the demo project and there both controls are synchronized fine, no matter which scrollbar in which control I use ...
0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 

Author Comment

by:tantormedia
ID: 34908385
Yes, it is very strange, as the controls are almost identical, except the left one has additional column on the left for displaying line numbers.
I applied the code in my project, and changed it. Instead of storing in the ListBtrl class pointer to the parent window, I store a pointer to another ListCtrl. Both horizontal scrolls work, which means I set those pointers correctly.
0
 

Author Comment

by:tantormedia
ID: 34908414
Sara, I tried your code, too, as it is so simple. But though the scroll positions are synchronized, the text doesn't move, i.e. the scroll doesn't control ListCtrl.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34908536
Hm - so I guess you maybe made some little mistake within any code related to vertical scroll, maybe mixed up something in 'VertSynchro' or something else.

It would be helpful to see the relevant code you wrote for this ...

ZOPPO


PS: unfortunateley I have to leave office now so I cannot provide further help before tomorrow morning CET - sorry.
0
 

Author Comment

by:tantormedia
ID: 34909312
I added a column with line numbers to the right LstCtrl, too, and surprisingly, now it works. I wonder why it is so...
0
 

Author Comment

by:tantormedia
ID: 34909336
Moreover, when I removed the first column with line numbers from both ListCtrls, vertical synchronization doesn't work at all.
0
 
LVL 32

Expert Comment

by:sarabande
ID: 34909596
i am glad to hear it worked.

but my code should have worked as well. below is the rest of my test program.

Sara




// tdiffDlg.h : header file
//

#pragma once
#include "afxcmn.h"
#include "twinlistctrl.h"


// TDiffDlg dialog
class TDiffDlg : public CDialog
{
// Construction
public:
    TDiffDlg(CWnd* pParent = NULL);	// standard constructor

// Dialog Data
    enum { IDD = IDD_TDIFF_DIALOG };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support


// Implementation
protected:
    HICON m_hIcon;

    // Generated message map functions
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    TwinListCtrl m_list1;
    TwinListCtrl m_list2;
};


// tdiffDlg.cpp : implementation file
//

#include "stdafx.h"
#include "tdiff.h"
#include "tdiffDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    enum { IDD = IDD_ABOUTBOX };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


// TDiffDlg dialog




TDiffDlg::TDiffDlg(CWnd* pParent /*=NULL*/)
    : CDialog(TDiffDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void TDiffDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_list1);
    DDX_Control(pDX, IDC_LIST2, m_list2);
}

BEGIN_MESSAGE_MAP(TDiffDlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


// TDiffDlg message handlers

BOOL TDiffDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);			// Set big icon
    SetIcon(m_hIcon, FALSE);		// Set small icon

    CRect rect1;
    CRect rect2;
    m_list1.GetWindowRect(rect1);
    m_list2.GetWindowRect(rect2);
    m_list1.InsertColumn(1, "Text1", LVCFMT_LEFT, rect1.Width() );
    m_list2.InsertColumn(1, "Text2", LVCFMT_LEFT, rect2.Width() );

    // TODO: Add extra initialization here
    for (int i = 0; i < 200; ++i)
    {
       m_list1.InsertItem(i, CString("line ") + CString('x', i+1));
       m_list2.InsertItem(i, CString("line ") + CString('y', i+1));
    }
    return TRUE;  // return TRUE  unless you set the focus to a control
}

void TDiffDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void TDiffDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR TDiffDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

// tdiff.h : main header file for the PROJECT_NAME application
//

#pragma once

#ifndef __AFXWIN_H__
    #error "include 'stdafx.h' before including this file for PCH"
#endif

#include "resource.h"		// main symbols


// TDiffApp:
// See tdiff.cpp for the implementation of this class
//

class TDiffApp : public CWinApp
{
public:
    TDiffApp();

// Overrides
    public:
    virtual BOOL InitInstance();

// Implementation

    DECLARE_MESSAGE_MAP()
};

extern TDiffApp theApp;

// tdiff.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "tdiff.h"
#include "tdiffDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// TDiffApp

BEGIN_MESSAGE_MAP(TDiffApp, CWinApp)
    ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()


// TDiffApp construction

TDiffApp::TDiffApp()
{
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
}


// The one and only TDiffApp object

TDiffApp theApp;


// TDiffApp initialization

BOOL TDiffApp::InitInstance()
{
    // InitCommonControlsEx() is required on Windows XP if an application
    // manifest specifies use of ComCtl32.dll version 6 or later to enable
    // visual styles.  Otherwise, any window creation will fail.
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    // Set this to include all the common control classes you want to use
    // in your application.
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();

    AfxEnableControlContainer();

    // Standard initialization
    // If you are not using these features and wish to reduce the size
    // of your final executable, you should remove from the following
    // the specific initialization routines you do not need
    // Change the registry key under which our settings are stored
    // TODO: You should modify this string to be something appropriate
    // such as the name of your company or organization
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));

    TDiffDlg dlg;
    m_pMainWnd = &dlg;
    INT_PTR nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO: Place code here to handle when the dialog is
        //  dismissed with Cancel
    }

    // Since the dialog has been closed, return FALSE so that we exit the
    //  application, rather than start the application's message pump.
    return FALSE;
}

Open in new window

0
 

Author Comment

by:tantormedia
ID: 34909648
I don't know why it didn't work for me. In fact, I don't understand many things... E.g. why the solution I have works only with two columns in each ListCtrl and not with one columns. Or why I can select a record by clicking only on the first columns, but not the second. And some other weird things.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34913895
Hi,

sorry, I don't know why it doesn't work in your app with one column per list control - in the demo project it works with one column too. Maybe there's still a little bug in your code.

> why I can select a record by clicking only on the first columns, but not the second
This is controlled by a list box extended style LVS_EX_FULLROWSELECT - this has to be set using CListCtrl::SetExtendedStyle. Here two ways how to do this:

1. Do it in the parent window's class after controls are created (i.e. in OnInitDialog if your app is a dialog app or OnInitialUpdate if you use a view with a MDI/SDI app) - i.e. in the demo project you can add these two lines to CSyncScrollView::OnInitialUpdate like this:

void CSynchScrollView::OnInitialUpdate()
{
      CFormView::OnInitialUpdate();

      // add these two lines
      m_ctrlFirstList.SetExtendedStyle( m_ctrlFirstList.GetExtendedStyle() | LVS_EX_FULLROWSELECT );
      m_ctrlSecondList.SetExtendedStyle( m_ctrlSecondList.GetExtendedStyle() | LVS_EX_FULLROWSELECT );
...

2. You can add a 'PreSubclassWindow' handler to the list control's class - i.e. in the demo project use class wizard to add a 'PreSubclassWindow' in the 'CListCtrlEx' class and implement it like this:

void CListCtrlEx::PreSubclassWindow()
{
      SetExtendedStyle( GetExtendedStyle() | LVS_EX_FULLROWSELECT );
      CListCtrl::PreSubclassWindow();
}

ZOPPO
0
 

Author Comment

by:tantormedia
ID: 34915368
Thank you very much. I am said to say that after all this effort I cannot use the CListCtrl at all as it has limitation on the size of strings it can display, which is 260 characters, and nothing can be done about it according to my research. So I am moving to using two synchronized list boxes. :(
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34915432
Hm - I guess synchronization of scroll position might work similar with any kind of controls, not only with CListCtrl.

BTW, maybe another good idea could be to use two multiline edit controls - this i.e. could give a user to select and copy parts of the text to the clipboard ...
0
 

Author Comment

by:tantormedia
ID: 34915458
No, I think CListBox will work better, as I need to strictly identify each line, give random lines appropriate colors etc. Also, I don't need to copy text, and if I did, I could implement it with CLisbBox.
Thank you for your help.
0
 
LVL 30

Expert Comment

by:Zoppo
ID: 34915504
ok - no problem, you're welcome ...
0
 
LVL 32

Expert Comment

by:sarabande
ID: 34915832
you are right. 260 char is the maximum. i even tried with supplying longer texts via dispinfo notification but failed.

listbox probably is better. you can scroll by calling m_list.SetTopIndex(n) where n is the line number that shows as top line,

Sara
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

Templates For Beginners Or How To Encourage The Compiler To Work For You Introduction This tutorial is targeted at the reader who is, perhaps, familiar with the basics of C++ but would prefer a little slower introduction to the more ad…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now