Scrolling data efficiently

So, I have a data file that I am reading in that is to displayed in my main window. Right now, I have it printing at particular x,y coordinates, with headings, column titles, and data below the columns. Obviously, doing it this way makes it difficult to scroll up or down (for now by width is OK). The data is fixed, once the file is read. After reading in the file, should I create a text table of each line (1-maxlines of data). Then, index into the table with the first and last lines of the viewable region, based on scroll bar data, and just do a TextOut for those lines. How do most applications do this?
Last edited on
Only a couple weeks ago I posted a scrolling app here which is very similiar to Charles Petzold's last scrolling demo which is fairly early in his books.....

http://www.cplusplus.com/forum/windows/279717/

What's different in my example from Petzold's code is that the data I'm scrolling is the source code file itself, which I think is kind of cool. Charles used piles of data taken, if I recall correctly, from the output of GetDeviceCaps(). What I do is basically read through the whole source code file to count the number of lines, then allocate a memory buffer large enough to hold a pointer for each line, so the memory block is typed as a wchar_t**. Then, as I read in each line after rewinding the file pointer, I allocate a wchar_t* with enough memory to hold the line plus the terminating null. These of course are placed sequentially in the wchar_t** memory block. This is the data that is scrolled.
The scroll code starts around the Sept. 7 posts in that thread, which is on my page 2. Like 30 posts in or something.

Regarding that code, note there are no global variables in the code. The pointer to the buffers I just decscribed I stored in memory allocated within the WNDCLASSEX::cbWndExtra bytes. It's what creating a member variable or object within another object looks like in C. An acquantance and critic of mine from another programming language, who actually resides in England, termed my usage as I just described it, with typical brittish wit, as 'torture variables'. The painting code is actually efficient. Although on first examination it looks like the entire screen is being painted line by line if even just one line is scrolled, that's actually not what is happening. It's something like the old DOS BIOS int10h interrupt calls where the operating system itself bumped up or down the entire screen buffer except the lone single line at top or bottom. I'm pretty sure I have a debug version of that code where this can be seen.
Last edited on
Good idea. I was thinking of that. It's easy when the font used is symmetrical. You can space it out in each data line. When it's not, the placement of the data gets tricky. The default Windows font is not symmetrical. Which doesn't mean I can't change it.
I think the term is 'Fixed Pitch'. You had mentioned tables I think. You definitely need fixed pitch fonts for that to come out looking like something better than a 'dog's breakfast'. Lucida Console and Courier New are the main ones I use. I suppose there are others. I believe there is something termed System Fixed Font I've used already.
Thanks. I enjoyed your in-depth writeup on SDK. Coming exclusively from the embedded programming world for 30+ years, I find the Windows Programming world actually enjoyable to tackle. It's taken a bit of time to understand how it works, but like anything else, it takes practice, experimentation, and banging your head against the wall. I, too, like to modularize my code to make the code much more readable. Sometimes, too much. It just simplifies things down the road immensely. OK, let me get back to scrolling my data and see if I can get that to work.
@dodhe55. If you're getting into Windows programming, then you should be aware of message crackers. I don't think this is covered in Petzold. They are defined in windowsx.h (note the x). They can help simply the use of messages.

See:
https://www.dreamincode.net/forums/topic/286954-using-windowsxh-for-better-code-organization-and-message-cracking/

https://www.codeproject.com/Articles/4948/Message-Cracker-Wizard-for-Win-SDK-Developers

and others.


OK, let me get back to scrolling my data and see if I can get that to work.

About that Sutton, recall in your other post about device contexts and such I had mentioned that it may not be too good of a thing to get too creative with WM_PAINT handler code? In other words, just follow pretty close Petzold's code? Well, there are certainly in Win32 SDK style coding opportunities to be creative - it's what make us coding junkies tick, I'd submit scroll code might be another example of code to just follow Petzold exactly, to the extent possible. That scroll code of mine in that example at the end of that long thread is really Petzold's code exactly, just worked over by me a good bit to get it out of the switch architecture and into my modularized function pointer setup, and modified a bit further to scroll the Main.cpp source code file instead of Petzold's SysMets data.

Over the years I've struggled mightily with scroll code. I find it very tricky to get just right. What I would do is study Petzold's code, then close the book and see if I could reason through it all myself, so as to make it 'mine', more or less, so I wouldn't just be copying it out of a book. I'm sure you know what I mean. Well, I'd seldom get it just right. It seemed I'd always have a line clipped at the end, or maybe too much white space there, know what I mean? Then I'd look at Petzold's code and go 'aha!' So what I'm saying is study it hard, but in the end I'd just recommend going with Petzold's code exactly, like I did. It's perfect.

So what you really have to do is understand the variables that will change as you adapt the code to scroll your data. The WM_SIZE handler code is critical. The SCROLLINFO struct data changes as the size of the window changes. Note that very soon after WM_CREATE returns your program will get it's first WM_SIZE message, where the SCROLLINFO data is set. But of course it changes constantly as a window is resized.

Some of my most enjoyable times coding were in working with Microsoft Windows CE in coding applications for the handheld data collectors my organization used to collect field data. I used MS's eMbedded Visual C++ for that work. In the late 90's and early 2000s I mostly did C code, then I moved to C++ after teaching myself that. But I always did Win32 SDK style.
Last edited on
Another thing to consider is leveraging already created WinAPI controls for displaying/editing text/data that can be multi-line and have automatic scrolling when needed.

https://docs.microsoft.com/en-us/windows/win32/controls/about-edit-controls

I find Rich Edit controls very useful, more so than regular edit controls.

https://docs.microsoft.com/en-us/windows/win32/controls/about-rich-edit-controls
Last edited on
Other easy option besides what Furry Guy said is just shell to Notepad and display the file there. Of course, one could use COM to open MS Word, and write the report to Word. And being the kind of COM guy I am, I've been patiently waiting for over 20 years for someone to ask how to automate Word! Excel - all the time, several times a year. But Word? Never. :)
Ooooh, oooooh! Mistah Kottah! Automate Word!

(not really)....
Yeah. Scrolling is difficult to master. I'm having to think outside the box, since I have different fonts and different spacing that makes the WM_PAINT portion tricky. Petzold's code is a great starting point, however.
Yes, at this point you've had a few days to agonize over it. It's tricky. But really, I don't think the font part should cause any problems. Looking at my (Petzold's) code, you should see that the measurements of the chosen font are obtained by the call to GetTextMetrics() in the WM_CREATE handler code, and those metrics are loaded into my ScrollData object, which is persisted in program memory for the duration of the program. Then they are fed into the Api SCROLLINFO struct, and the code there should automatically adjust the scrolling to work out perfectly. I gotta run now, but later I'll see what effect changing the font has on all that, but I think I recall doing all that testing years ago (like 10) when I was working on that code.

You know, regarding your other question about fonts, I seem to recall like 30 years back when I was learning all this, I had used that SYSTEM_FIXED_FONT returned by GetStockObject() to get a font where my tables were not all messed up. Since then though I've always created my fonts using either CreateFont() or CreateFontIndirect(). I know CreateFont() looks terribly complicated, but the truth of the matter is that it really isn't. All those crazy parameters have default values, and can actually be set to zero. I really think the only ones really needed are the nHeight 1st parameter, and the string typeface name. Maybe those aren't even necessary. I'm really not sure that function ever fails. There's all kinds of complicated code running behind it (Font Mapper) that always returns something. It may not be what you want if you screw up the parameters, but it'll give you something.
Just tested everything using quite a few different font sizes with both Lucida Console and Courier New, and everything works perfectly. What I did change in the code are I added #defines for the font size and font type face name. Good idea to do that because there are two places in the code where if one changes something there, it must be changed in the other place too. The two places where the font is created with CreateFont() are in fnWndProc_OnCreate(), and fnWndProc_OnPaint(). Here is the updated code....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//Main.cpp;
//cl Main.cpp /O1 /Os /W3 /FeScrollWindow.exe kernel32.lib user32.lib gdi32.lib                      110,592 bytes, VC19, x64 LIBCMT
//cl Main.cpp /O1 /Os /W3 /GS- /FeScrollWindow.exe /link TCLib.lib kernel32.lib user32.lib gdi32.lib   8,192 bytes, VC19, x64 TCLib 
#define TCLib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
   #include "string.h"
   #include "stdio.h"
#else
   #include <string.h>
   #include <cstdio>
#endif
#pragma warning(disable:4996)
#define dim(x) (sizeof(x) / sizeof(x[0]))
#define TYPEFACE_NAME     L"Lucida Console"
#define FONT_SIZE         17

struct WndEventArgs
{
 HWND                            hWnd;
 WPARAM                          wParam;
 LPARAM                          lParam;
 HINSTANCE                       hIns;
};

LRESULT fnWndProc_OnCreate       (WndEventArgs& Wea);
LRESULT fnWndProc_OnSize         (WndEventArgs& Wea);
LRESULT fnWndProc_OnVScroll      (WndEventArgs& Wea);
LRESULT fnWndProc_OnHScroll      (WndEventArgs& Wea);
LRESULT fnWndProc_OnMouseWheel   (WndEventArgs& Wea);
LRESULT fnWndProc_OnPaint        (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
 unsigned int                    iMsg;
 LRESULT                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
 {WM_CREATE,                     fnWndProc_OnCreate},
 {WM_SIZE,                       fnWndProc_OnSize},
 {WM_VSCROLL,                    fnWndProc_OnVScroll},
 {WM_HSCROLL,                    fnWndProc_OnHScroll},
 {WM_MOUSEWHEEL,                 fnWndProc_OnMouseWheel},
 {WM_PAINT,                      fnWndProc_OnPaint},
 {WM_DESTROY,                    fnWndProc_OnDestroy}
};

struct ScrollData
{
 wchar_t**                       pPtrs;
 int                             iNumLines;
 int                             cxChar;
 int                             cxCaps;
 int                             cyChar;
 int                             cxClient;
 int                             cyClient;
 int                             iMaxWidth;
};


LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 wchar_t szBuffer[512];
 wchar_t* cRet=NULL;
 HANDLE hHeap=NULL;
 HFONT hFont=NULL;
 FILE* fp1=NULL;
 TEXTMETRIC tm;
 int iLen=0,i;
 HDC hdc;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
 if(!pScrDta)
    return -1;
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
 hdc = GetDC(Wea.hWnd);
 hFont=CreateFont
 (
   -1*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)TYPEFACE_NAME
 );
 if(!hFont)
    return -1;
 HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
 GetTextMetrics(hdc, &tm);
 pScrDta->cxChar = tm.tmAveCharWidth;
 pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
 pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
 DeleteObject(SelectObject(hdc,hTmp));
 ReleaseDC(Wea.hWnd, hdc);
 fp1=_wfopen(L"Main.cpp",L"r");
 if(fp1)
 {
    do
    {
      cRet=fgetws(szBuffer,512,fp1);
      if(!cRet)
         break;
      else
         pScrDta->iNumLines++;
    } while(1);
    rewind(fp1);
    if(pScrDta->iNumLines)
    {
       pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(wchar_t*) * pScrDta->iNumLines);
       if(pScrDta->pPtrs)
       {
          for(i=0; i<pScrDta->iNumLines; i++)
          {
              fgetws(szBuffer,512,fp1);
              iLen=(int)wcslen(szBuffer);
              if(iLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iLen;
              pScrDta->pPtrs[i]=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY, sizeof(wchar_t)*iLen+1*sizeof(wchar_t));
              wcscpy(pScrDta->pPtrs[i],szBuffer);
          }
          pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
       }
    }
    fclose(fp1);
 }

 return 0;
}


LRESULT fnWndProc_OnSize(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
 }

 return 0;
}


LRESULT fnWndProc_OnVScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof(si) ;// Get all the vertial scroll bar information
    si.fMask  = SIF_ALL ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos; // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin ;
           break ;
      case SB_BOTTOM:
           si.nPos = si.nMax ;
           break ;
      case SB_LINEUP:
           si.nPos -= 1 ;
           break ;
      case SB_LINEDOWN:
           si.nPos += 1 ;
           break ;
      case SB_PAGEUP:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGEDOWN:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos ;
           break ;
      default:
           break ;
    }
    si.fMask = SIF_POS ;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
 }

 return 0;
}


continued....
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
LRESULT fnWndProc_OnHScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof (si);// Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si) ;// Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1 ;
           break ;
      case SB_LINERIGHT:
           si.nPos += 1 ;
           break ;
      case SB_PAGELEFT:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGERIGHT:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:              // case SB_THUMBPOSITION:
           si.nPos = si.nTrackPos ;
           break ;
      default :
           break ;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
 }

 return 0;
}


LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)
{
 int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
 ScrollData* pScrDta=NULL;
 HFONT hFont=NULL;
 PAINTSTRUCT ps;
 SCROLLINFO si;
 HDC hdc;

 hdc = BeginPaint(Wea.hWnd, &ps);
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    hFont=CreateFont
    (
     -1*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)TYPEFACE_NAME
    );
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si) ;// Get vertical scroll bar position
    si.fMask  = SIF_POS ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        x = pScrDta->cxChar * (1 - iHorzPos);
        y = pScrDta->cyChar * (i - iVertPos);
        TextOut(hdc, x, y, pScrDta->pPtrs[i], (int)wcslen(pScrDta->pPtrs[i]));
    }
    DeleteObject(SelectObject(hdc,hTmp));
 }
 EndPaint(Wea.hWnd, &ps);

 return 0;
}


LRESULT fnWndProc_OnMouseWheel(WndEventArgs& Wea)
{
 int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
 if(zdelta>0)
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
 }
 else
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
 }

 return 0;
}


LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 HANDLE hHeap=NULL;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta->pPtrs)
 {
    for(int i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
 }
 PostQuitMessage(0);

 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(size_t i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"ScrollWindow";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName = szClassName,          wc.lpfnWndProc  = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX),   wc.style        = CS_HREDRAW|CS_VREDRAW;
 wc.hInstance     = hInstance,            wc.lpszMenuName = NULL;
 wc.cbWndExtra    = sizeof(void*),        wc.cbClsExtra   = 0;
 wc.hIconSm       = (HICON)LoadImage(NULL,IDI_APPLICATION,IMAGE_ICON,0,0,LR_DEFAULTCOLOR); 
 wc.hCursor       = (HCURSOR)LoadImage(NULL,IDC_ARROW,IMAGE_CURSOR,0,0,LR_SHARED);
 wc.hIcon         = (HICON)LoadImage(NULL,IDI_APPLICATION,IMAGE_ICON,0,0,LR_DEFAULTCOLOR);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);                                                              
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,50,50,1275,900,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return (int)messages.wParam;
}

Registered users can post here. Sign in or register to post.