 |
|
 |
in ur article, u mention abt these...
str << "Value=" << 12 << .5 << " seconds";
1. how to set decimal place?
2. how to make the formating dynamic? (load format from database)
3. how to load format string from resource? rc localization
4. how to store pointer value in case of logging purpose?
and many...
from,
-= aLbert =-
|
|
|
|
 |
|
 |
I did a similar non-MFC port of CString for my company.
I originally did it to add thread safety and to allow its use in non-MFC projects, but I have since added a few more functions that greatly simplify some common string operations.
Here is a brief list of some of the more useful methods I use.
// Case Insensitive String Compare Operators rock! Death to CompareNoCase()!
BOOL operator ^= (CStr rv) // read "approximately equal". (0==CompareNoCase(rv))
BOOL operator /= (CStr rv) // read "not approximately equal". (0!=CompareNoCase(rv))
// path/filename parsing
CStr GetFPath() const; // return path portion of a full pathname
CStr GetFName() const; // return filename portion, w/ extension
CStr GetFExt() const; // return extension portion
// path/filename editing
CStr RepFPath(CStr strPath) const; // return a string with path portion replaced by strPath
CStr RepFName(CStr strFile) const; // return a string with filename portion replaced by strFile
CStr RepFExt(CStr strExt) const; // return a string with extension portion replaced by strExt
Example usage:
CStr strFile="d:\\test\\FOOBAR.txt";
CStr strOutDir="d:\\out\\";
if (strFile.GetFName() ^= "foobar.txt")
{
CopyFile( strFile, strFile.RepFPath(strOutDir) ); // copy file to strOutDir folder
DeleteFile( strFile.RepFExt("tmp") ); // delete file w/same path/name as strFile, but w/ ".tmp" extension
}
Now try writing that example using plain CString functions! :P
-- modified at 23:21 Monday 14th November, 2005
|
|
|
|
 |
|
 |
dgendreau wrote: if (strFile.GetFName() ^= "foobar.txt")
what did u do with this line? something like compare no case?
dgendreau wrote: CopyFile( strFile, strFile.RepFPath(strOutDir) ); // copy file to strOutDir folder
DeleteFile( strFile.RepFExt("tmp") ); // delete file w/same path/name as strFile, but w/ ".tmp" extension
becareful with these line of code, i face intermittence problem with these code in my project.
after i put a proper casting, it works great. if i remember correctly, i cast it to (LPCSTR).
from,
-= aLbert =-
|
|
|
|
 |
|
 |
Yes, thats a compare no case. I think its a lot easier to type and read than
if (0==strFile.GetFName().CompareNoCase("foobar.txt"))
|
|
|
|
 |
|
 |
I am interested in possibly using this class in my projects and was wondering if any updates were going to be released which fix a couple of issues reported since the last update (along with any other updates the author may have made)
Will any more updates be amde to this class ?
|
|
|
|
 |
|
 |
Not from me unfortunately (I am the author), as I don't use C++ anymore (for a long time already).
If it's possible to hand over for maintainance this project/codeproject entry to any person who is interested I'd gladly do it.
soso
|
|
|
|
 |
|
 |
Hey soso,
I'd be interested in becoming the maintainer for this project. I am currently in the process of improving the class by adding more CString compliance.
How do we go about handing it over ?
|
|
|
|
 |
|
 |
I looked a little around the site but I have no idea how can I hand it over...
Maybe someone who sees these messages can help us??
soso
|
|
|
|
 |
|
 |
I think that the best solution would be to create a new article by defenestration with a reference, credit and a link to your article.
Thank you both
|
|
|
|
 |
|
 |
| Another string class that I found useful particularly in non-MFC applications is found at
|
|
 |
MFC got
BOOL AfxExtractSubstring(CString strDest, CString strToExtractFrom, int nSubstringNumber, CString strSeps)
function, or something like this.
Could it be possible to add ExtractSubstring function to the CStr class?
It may look like follow:
BOOL ExtractSubstring( CStr& strDest, int nSubstringNumber, CStr& strSeps )
BOOL ExtractSubstring( LPCTSTR pszDest, int nSubstringNumber, LPCTSTR pszSeps )
Got source code and simplest test app, would post it to anywhere.
Sincerely,
dandy_andy.
|
|
|
|
 |
|
 |
Why is all this so windows-centric? I do not have windows.h on my system..
|
|
|
|
 |
|
 |
http://codeguru.earthweb.com/string/faststring.shtml
Kochise
In Cod we trust !
|
|
|
|
 |
|
 |
http://codeguru.earthweb.com/string/HuangString.shtml
Harsh ? Noooon, just difficult to find an unique name
Kochise
In Cod we trust !
|
|
|
|
 |
|
 |
When using CString I can assign a character value to the string:
CString MyString = 'A';
This will leave MyString with "A".
I haven't tried it, but I suspect that the integer assignment in CStr will cause it to work a little differently:
CStr MyStr = 'A';
I suspect this will leave MyStr with "65".
|
|
|
|
 |
|
 |
Hi,
Yes you're right.
In the next update I'll add a method to deal with
TCHAR unfortunately it cannot be done to automatically
use a TCHAR from = and << operators and get the
'right' results, because of the many numeric
overloads.
soso
soso
|
|
|
|
 |
|
 |
I'm a embedded coder, and I came to the world of Windows coding. I encountered many messy practices, and above all, an incredible waste of time and power, especialy while using the MFC...
This set of classes, developped by MicroSoft, are VERY easy to use, but Hell ! Sooooo sloooow ! Using replacement routines based on TCHAR are easily 100 times faster, no tricks at all, just brute power...
In embedded, while coding with CPU running at 8, 16 or 32 MHz, with less than 256 KB of RAM and 512 KB of ROM, you have to code very efficiently. What it's obviously not in the goal of MFC, allowing coder to release a program in days, but loosing time at each run !
Now the interrest I find in this class is not to chain operations, what I agree with most of you is an incredible stupidity. But 'soso_pub' never stated to chain thousands of operations.
As an assembly coder first, the development orientation should be 'vertical', one instruction per line. So that in case of breakpoint, you don't have to look which instruction in the line caused the break...
The PRIMAL interrest I have in this class is it's construction over a clean TCHAR* coding, I can improve in case of needs, and perhaps port on Pocket PC, which plateform needs some efficient coding, even running at 400 MHz...
SOooo, thanks alot dear 'soso_pub', I'm saving your component in my 'Essential' folder
Kochise
In Cod we trust !
|
|
|
|
 |
|
 |
Kochise wrote:
Using replacement routines based on TCHAR are easily 100 times faster, no tricks at all, just brute power...
And this speed gain is totally wasted, because the limiting factor is the user pushing a mouse slowly across his mousepad to click the next button.
As an embedded coder, you probably are acustomed to program machines with a computing power more like the keyboard processor of a normal PC. In this environment savings of 32 byte or 12 processor cycles may matter.
In application development, most of the times the development costs (through application price!) are much more important than the price to replace your old 500Mhz PIII with a new Multi Ghz PC.
Here ease of use of a framework does matter much more(and MFC is abysmally bad here - or we won't need all this CP stuff), together with experience of the developers.
If your application proves too slow, you profile it and revise only the parts or algorithms that form the bottleneck. That way you may waste even more resources, but your application does what it is suposed to do in a cost-efficient way.
My opinions may have changed, but not the fact that I am right.
|
|
|
|
 |
|
 |
...if the code was using only once the CStr !
Now for instance, use a modifed version of SuperGrid ( http://www.codeproject.com/listctrl/supergrid.asp ) in order to use CStr instead of CString, works on 100+ MB text files (I have such DXF files), and you'll understand what I mean !
But obviously you're not working on heavy-duty projects, just dialog based ones Naaah, pulling your leg
Get nice habits, code efficently, don't just do it on purposes !
Kochise
PS : For instance, even little CImageList::Draw(...) can be speed up ( http://www.codeproject.com/useritems/CSkinProgress.asp )
In Cod we trust !
|
|
|
|
 |
|
 |
Kochise wrote:
In Cod we trust !
Isn't that the fish in fish-n-chips? And you trust in something like THAT?
My application involves operation on spectra, and these can get amazingly big: a few hundred MB are not unusual.
But even with that, only a small portion of the application is really speed sensitive, and most of that are numeric algorithms totally without MFC. Using a design which tries not to copy the data does more speedup than optimizing the algorithms (which I don't understand anyway )
Within the GUI, it does not matter if writing an XML file could be an order of magnitude faster using char* and legacy C string-routines.
The writing goes to a disk file, and that forms the bottleneck.
With your SuperGrid example, I would say that using a callback approach instead of copying the data into the grid would improve speed sufficiently. Most of the time using different algorithms is much easier than optimizing existing ones.
My opinions may have changed, but not the fact that I am right.
|
|
|
|
 |
|
 |
Add this part of code at the end of [BOOL CTestApp::InitInstance()] in 'test.cpp' :
AfxMessageBox( _T("All tests succeeded!") );
CString* oStrCStringTest;
CStr* oStrCStrTest;
CString* oStrCStringPointer;
CStr* oStrCStrPointer;
int nLoop;
int nDichoMin;
int nDichoMed;
int nDichoMax;
int nDichoDlt;
CPtrArray aCString;
CPtrArray aCStr;
SYSTEMTIME sSysTimeStartCString;
SYSTEMTIME sSysTimeStartCStr;
SYSTEMTIME sSysTimeEndCString;
SYSTEMTIME sSysTimeEndCStr;
FILETIME sTimeFileStart;
FILETIME sTimeFileEnd;
ULARGE_INTEGER uLargeStart;
ULARGE_INTEGER uLargeEnd;
GetLocalTime(&sSysTimeStartCString);
for(
nLoop = 0;
nLoop < 100000;
nLoop += 1
)
{
oStrCStringTest = new CString();
oStrCStringTest->Format("%X", (rand() << 16) | rand());
nDichoMin = 0;
nDichoDlt = 0;
nDichoMax = aCString.GetSize();
if(nDichoMax > 0)
{
do
{
nDichoMed = (nDichoMin + nDichoMax) / 2;
oStrCStringPointer = (CString*) aCString.GetAt(nDichoMed);
if(oStrCStringTest->Compare(*oStrCStringPointer) > 0)
{
nDichoMin = nDichoMed;
nDichoDlt = 1;
}
else if(oStrCStringTest->Compare(*oStrCStringPointer) < 0)
{
nDichoMax = nDichoMed;
}
else
{
break;
}
}
while(nDichoMax > (nDichoMin + 1));
nDichoMed = (nDichoMin + nDichoMax) / 2;
aCString.InsertAt(nDichoMed + nDichoDlt, oStrCStringTest);
}
else
{
aCString.Add(oStrCStringTest);
}
}
GetLocalTime(&sSysTimeEndCString);
for(
nLoop = 0;
nLoop < aCString.GetSize();
nLoop += 1
)
{
delete aCString.GetAt(nLoop);
}
aCString.RemoveAll();
GetLocalTime(&sSysTimeStartCStr);
for(
nLoop = 0;
nLoop < 100000;
nLoop += 1
)
{
oStrCStrTest = new CStr();
oStrCStrTest->Format("%X", (rand() << 16) | rand());
nDichoMin = 0;
nDichoDlt = 0;
nDichoMax = aCString.GetSize();
if(nDichoMax > 0)
{
do
{
nDichoMed = (nDichoMin + nDichoMax) / 2;
oStrCStrPointer = (CStr*) aCStr.GetAt(nDichoMed);
if(oStrCStrTest->Compare(*oStrCStrPointer) > 0)
{
nDichoMin = nDichoMed;
nDichoDlt = 1;
}
else if(oStrCStrTest->Compare(*oStrCStrPointer) < 0)
{
nDichoMax = nDichoMed;
}
else
{
break;
}
}
while(nDichoMax > (nDichoMin + 1));
nDichoMed = (nDichoMin + nDichoMax) / 2;
aCStr.InsertAt(nDichoMed + nDichoDlt, oStrCStrTest);
}
else
{
aCStr.Add(oStrCStrTest);
}
}
GetLocalTime(&sSysTimeEndCStr);
for(
nLoop = 0;
nLoop < aCStr.GetSize();
nLoop += 1
)
{
delete aCStr.GetAt(nLoop);
}
aCStr.RemoveAll();
SystemTimeToFileTime(&sSysTimeStartCString, &sTimeFileStart);
uLargeStart.QuadPart = sTimeFileStart.dwHighDateTime;
uLargeStart.QuadPart <<= 32;
uLargeStart.QuadPart |= sTimeFileStart.dwLowDateTime;
SystemTimeToFileTime(&sSysTimeEndCString, &sTimeFileEnd);
uLargeEnd.QuadPart = sTimeFileEnd.dwHighDateTime;
uLargeEnd.QuadPart <<= 32;
uLargeEnd.QuadPart |= sTimeFileEnd.dwLowDateTime;
s.Format("CString : %d ms\n", (int) (uLargeEnd.QuadPart - uLargeStart.QuadPart) / 10000);
SystemTimeToFileTime(&sSysTimeStartCStr, &sTimeFileStart);
uLargeStart.QuadPart = sTimeFileStart.dwHighDateTime;
uLargeStart.QuadPart <<= 32;
uLargeStart.QuadPart |= sTimeFileStart.dwLowDateTime;
SystemTimeToFileTime(&sSysTimeEndCStr, &sTimeFileEnd);
uLargeEnd.QuadPart = sTimeFileEnd.dwHighDateTime;
uLargeEnd.QuadPart <<= 32;
uLargeEnd.QuadPart |= sTimeFileEnd.dwLowDateTime;
s2.Format("CStr : %d ms\n", (int) (uLargeEnd.QuadPart - uLargeStart.QuadPart) / 10000);
CString oCStrOperatorMissing1;
CString oCStrOperatorMissing2;
oCStrOperatorMissing1 = s;
oCStrOperatorMissing2 = s2;
AfxMessageBox( _T(oCStrOperatorMissing1 + oCStrOperatorMissing2) );
return TRUE;
}
Run the test, then after the "All tests succeeded!" message box, wait 30 seconds without touching to your computer, and get such a result :
CString : 28125 ms (28 seconds)
CStr : 3328 ms (3 seconds)
The process is to sort 100 thousands random strings ! The set of strings is not the same (get with [rand()])... To avoid to test the copy routines (what you seems to hate), I use pointers !
So CStr is there almost 9 times faster than CString. The 100 times faster was obtained in very particular cases, when there wasn't any call to others functions (such [rand()])
So, please believe in 'soso_pub'
Kochise
PS : Hey 'soso_pub', there is some operator missing, as HeavyHenke said
In Cod we trust !
|
|
|
|
 |
|
 |
Kochise wrote:
CString : 28125000 ms (28 seconds)
CStr : 3328000 ms (3 seconds)
Wow... I have to admit that I wasn't aware at all about the performance difference between CString and CStr. Thanks for the comparision, great!
Kochise wrote:
PS : Hey 'soso_pub', there is some operator missing, as HeavyHenke said
I will make a new update in a few days I think, I'll add some more operators plus some fixes and enhancements that are currently on my computer.
soso
|
|
|
|
 |
|
 |
But not in the same stress some people awaited for the 4th release of Harry Potter
Kochise
PS : I'm myself about to release my update of CSkinProgress. And I'm sure everyone will fall in love with
In Cod we trust !
|
|
|
|
 |
|
 |
Well here my results with VS 2005:
---------------------------
StringTest
---------------------------
CString : 4859 ms
CStr : 15891 ms
---------------------------
OK
---------------------------
|
|
|
|
 |
|
 |
There is a major bug with your test app in the code which tests CStr.
nDichoMax = aCString.GetSize(); is incorrect, and should be nDichoMax = aCStr.GetSize();
When this is corrected, you will find that CString is faster than CStr (at least it was on my machine when compiled with VC++ 2005 SP1).
There is another flaw with the test app in that the same random strings are not used for testing each class. I have revised your code to address the bug and design flaw, and have added a new test which uses std:string. I ran three tests changing the order is which the string classes were tested and my results are shown below:
CString 13119
CStr 14340
std::string 12068
std::string 9494
CString 10916
CStr 10385
CStr 14121
std::string 12578
CString 10715
I know there are six possible combinations, but I could only be bothered to run the three above
Conclusion: use std::string or CString!
AfxMessageBox( _T("All tests succeeded!") );
// Time process testing
CString* oStrCStringTest;
CStr* oStrCStrTest;
CString* oStrCStringPointer;
CStr* oStrCStrPointer;
std::string* oStrSTLStringTest;
std::string* oStrSTLStringPointer;
int nLoop;
int nDichoMin;
int nDichoMed;
int nDichoMax;
int nDichoDlt;
CPtrArray aCString;
CPtrArray aCStr;
CPtrArray aSTLString;
SYSTEMTIME sSysTimeStartCString;
SYSTEMTIME sSysTimeStartCStr;
SYSTEMTIME sSysTimeEndCString;
SYSTEMTIME sSysTimeEndCStr;
SYSTEMTIME sSysTimeStartSTLString;
SYSTEMTIME sSysTimeEndSTLString;
FILETIME sTimeFileStart;
FILETIME sTimeFileEnd;
ULARGE_INTEGER uLargeStart;
ULARGE_INTEGER uLargeEnd;
// Create vector of random strings to be used when filling CStr and CString
std::vector vRandomStrings;
// std::string sRandomString;
std::ostringstream osRandomString;
// int x;
vRandomStrings.reserve(100000);
for (nLoop = 0; nLoop < 100000; ++nLoop)
{
//x = (rand() << 16) | rand();
osRandomString << std::hex << std::uppercase << ((rand() << 16) | rand()); // formatted as uppercase unsigned hexadecimal integer
//sRandomString = osRandomString.str();
vRandomStrings.push_back(osRandomString.str());
osRandomString.seekp(0);
}
//AfxMessageBox( _T("100,000 Random Strings generated!") );
// CStr
GetLocalTime(&sSysTimeStartCStr);
for(
nLoop = 0;
nLoop < 100000;
++nLoop
)
{
oStrCStrTest = new CStr(vRandomStrings[nLoop].c_str());
//oStrCStrTest->Format("%X", vRandomStrings[nLoop].c_str());
nDichoMin = 0;
nDichoDlt = 0;
nDichoMax = aCStr.GetSize();
if(nDichoMax > 0)
{
do
{
nDichoMed = (nDichoMin + nDichoMax) / 2;
oStrCStrPointer = (CStr*) aCStr.GetAt(nDichoMed);
if(oStrCStrTest->Compare(*oStrCStrPointer) > 0)
{
nDichoMin = nDichoMed;
nDichoDlt = 1;
}
else if(oStrCStrTest->Compare(*oStrCStrPointer) < 0)
{
nDichoMax = nDichoMed;
}
else
{
break;
}
}
while(nDichoMax > (nDichoMin + 1));
nDichoMed = (nDichoMin + nDichoMax) / 2;
aCStr.InsertAt(nDichoMed + nDichoDlt, oStrCStrTest);
}
else
{
aCStr.Add(oStrCStrTest);
}
}
GetLocalTime(&sSysTimeEndCStr);
for(
nLoop = 0;
nLoop < aCStr.GetSize();
++nLoop
)
{
delete aCStr.GetAt(nLoop);
}
aCStr.RemoveAll();
// std::string
GetLocalTime(&sSysTimeStartSTLString);
for(
nLoop = 0;
nLoop < 100000;
++nLoop
)
{
oStrSTLStringTest = new std::string(vRandomStrings[nLoop].c_str());
//oStrCStrTest->Format("%X", vRandomStrings[nLoop].c_str());
nDichoMin = 0;
nDichoDlt = 0;
nDichoMax = aSTLString.GetSize();
if(nDichoMax > 0)
{
do
{
nDichoMed = (nDichoMin + nDichoMax) / 2;
oStrSTLStringPointer = (std::string*) aSTLString.GetAt(nDichoMed);
// if(*oStrSTLStringTest > *oStrSTLStringPointer)
if(oStrSTLStringTest->compare(*oStrSTLStringPointer) > 0)
{
nDichoMin = nDichoMed;
nDichoDlt = 1;
}
// else if(*oStrSTLStringTest < *oStrSTLStringPointer)
else if(oStrSTLStringTest->compare(*oStrSTLStringPointer) < 0)
{
nDichoMax = nDichoMed;
}
else
{
break;
}
}
while(nDichoMax > (nDichoMin + 1));
nDichoMed = (nDichoMin + nDichoMax) / 2;
aSTLString.InsertAt(nDichoMed + nDichoDlt, oStrSTLStringTest);
}
else
{
aSTLString.Add(oStrSTLStringTest);
}
}
GetLocalTime(&sSysTimeEndSTLString);
for(
nLoop = 0;
nLoop < aSTLString.GetSize();
++nLoop
)
{
delete aSTLString.GetAt(nLoop);
}
aSTLString.RemoveAll();
// CString
GetLocalTime(&sSysTimeStartCString);
for(
nLoop = 0;
nLoop < 100000;
++nLoop
)
{
oStrCStringTest = new CString(vRandomStrings[nLoop].c_str());
//oStrCStringTest->Format("%X", (rand() << 16) | rand());
nDichoMin = 0;
nDichoDlt = 0;
nDichoMax = aCString.GetSize();
if(nDichoMax > 0)
{
do
{
nDichoMed = (nDichoMin + nDichoMax) / 2;
oStrCStringPointer = (CString*) aCString.GetAt(nDichoMed);
if(oStrCStringTest->Compare(*oStrCStringPointer) > 0)
{
nDichoMin = nDichoMed;
nDichoDlt = 1;
}
else if(oStrCStringTest->Compare(*oStrCStringPointer) < 0)
{
nDichoMax = nDichoMed;
}
else
{
break;
}
}
while(nDichoMax > (nDichoMin + 1));
nDichoMed = (nDichoMin + nDichoMax) / 2;
aCString.InsertAt(nDichoMed + nDichoDlt, oStrCStringTest);
}
else
{
aCString.Add(oStrCStringTest);
}
}
GetLocalTime(&sSysTimeEndCString);
for(
nLoop = 0;
nLoop < aCString.GetSize();
++nLoop
)
{
delete aCString.GetAt(nLoop);
}
aCString.RemoveAll();
//
SystemTimeToFileTime(&sSysTimeStartCString, &sTimeFileStart);
uLargeStart.QuadPart = sTimeFileStart.dwHighDateTime;
uLargeStart.QuadPart <<= 32;
uLargeStart.QuadPart |= sTimeFileStart.dwLowDateTime;
SystemTimeToFileTime(&sSysTimeEndCString, &sTimeFileEnd);
uLargeEnd.QuadPart = sTimeFileEnd.dwHighDateTime;
uLargeEnd.QuadPart <<= 32;
uLargeEnd.QuadPart |= sTimeFileEnd.dwLowDateTime;
s.Format("CString : %d ms\n", (int) (uLargeEnd.QuadPart - uLargeStart.QuadPart) / 10000);
SystemTimeToFileTime(&sSysTimeStartCStr, &sTimeFileStart);
uLargeStart.QuadPart = sTimeFileStart.dwHighDateTime;
uLargeStart.QuadPart <<= 32;
uLargeStart.QuadPart |= sTimeFileStart.dwLowDateTime;
SystemTimeToFileTime(&sSysTimeEndCStr, &sTimeFileEnd);
uLargeEnd.QuadPart = sTimeFileEnd.dwHighDateTime;
uLargeEnd.QuadPart <<= 32;
uLargeEnd.QuadPart |= sTimeFileEnd.dwLowDateTime;
s2.Format("CStr : %d ms\n", (int) (uLargeEnd.QuadPart - uLargeStart.QuadPart) / 10000);
CStr s3;
SystemTimeToFileTime(&sSysTimeStartSTLString, &sTimeFileStart);
uLargeStart.QuadPart = sTimeFileStart.dwHighDateTime;
uLargeStart.QuadPart <<= 32;
uLargeStart.QuadPart |= sTimeFileStart.dwLowDateTime;
SystemTimeToFileTime(&sSysTimeEndSTLString, &sTimeFileEnd);
uLargeEnd.QuadPart = sTimeFileEnd.dwHighDateTime;
uLargeEnd.QuadPart <<= 32;
uLargeEnd.QuadPart |= sTimeFileEnd.dwLowDateTime;
s3.Format("STLString : %d ms\n", (int) (uLargeEnd.QuadPart - uLargeStart.QuadPart) / 10000);
CString oCStrOperatorMissing1;
CString oCStrOperatorMissing2;
CString oCStrOperatorMissing3;
oCStrOperatorMissing1 = s;
oCStrOperatorMissing2 = s2;
oCStrOperatorMissing3 = s3;
AfxMessageBox( _T(oCStrOperatorMissing1 + oCStrOperatorMissing2 + oCStrOperatorMissing3) );
// End of time process
return TRUE;
}
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
| |