I'm doing a school project in which I need to change text colour frequently. Project target is Console app currently for Windows only. Using Codeblocks with MinGW for Debugging. I'm not a noob, but Intermediate-ish.
So using this everywhere in the code was ugly:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), __col._colour_code);
Even if I wrapped it in a function, it still is cumbersome and ugly because you cannot continue your cout chain. You have break the chain as you must call the SetColour
in a new statement, e.g.:
SetColour(GRAY); cout << setcol(PURPLE) << " ID:[";
SetColour(AQUA); cout << song.GetID();
SetColour(GRAY); cout << "]" << " ";
SetColour(GREEN); cout << song.GetTitle();
SetColour(WHITE); cout << " by ";
SetColour(BRIGHT); cout << song.GetArtist() << "\n";
What I wanted was a functionality like that of setw
, setprecision
, etc. So I opened the iomainp.h
and looked for some hints:
struct _Setw { int _M_n; };
inline _Setw
setw(int __n)
{ return { __n }; }
template<typename _CharT, typename _Traits>
inline basic_istream<_CharT, _Traits>&
operator>>(basic_istream<_CharT, _Traits>& __is, _Setw __f)
{
__is.width(__f._M_n);
return __is;
}
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, _Setw __f)
{
__os.width(__f._M_n);
return __os;
}
So I created a new function of my own in a 100% analogous way:
enum Colour { BLACK=0x00, DARK_GREEN=0x02, WHITE=0x07, GRAY,
BLUE=0x09, GREEN, AQUA, RED, PURPLE, YELLOW, BRIGHT };
struct _colour_struct
{
uint8_t _colour_code;
};
inline _colour_struct setcolour (Colour colour_foregrnd, Colour colour_backgrnd =BLACK)
{
uint8_t colour_code = colour_backgrnd;
colour_code <<= 4;
colour_code |= colour_foregrnd;
return { colour_code };
}
namespace std
{
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, _colour_struct __col)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), __col._colour_code);
return __os;
}
}
SURPRISE! (for me) Its working!! e.g.:
cout << setcolour(GRAY) << " ID:[" << setcolour(AQUA) << song.GetID() << setcolour(GRAY) << "]" << " "
<< setcolour(GREEN) << song.GetTitle()
<< setcolour(WHITE) << " by "<< setcolour(BRIGHT) << song.GetArtist() << "\n";
But consider the output of this code:
std::cout << std::setw(20) << setcolour(AQUA) << "1st time" << "\n";
std::cout << "2nd time" << "\n";
std::cout << "3rd time" << "\n";
Notice that setw
DIDN'T stick, it was Reset in the second line. How ??
(Debugging showed no extra call being executed to reset it.)
But my setcolour
DID stick for rest of the program. Why ??
(Although its 100% analogous to setw
).
How can I make setcolour
just like setw
??? I need this functionality to make my program more clean and logical.
Also I found this: Which iomanip manipulators are sticky
But the answers and comments just there only confused me. Apperently, setw
calls cout.width(0), but debugging showed no such call, nor such a line of code was found in iomanip.h
. Also didn't understand the answer there. Please explain.
EDIT
Maybe I wasn't direct in asking the question.
Like cout.width(0)
(in context of setw
) gets called each time,
How can I make my SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0)
(in context of setcolour
) get called each time ???
How should I approach this problem ??
The width is handled specially: all of the built-in operators will reset the width after outputting an object. The fact that you don't see a corresponding call to width(0)
in the code or the debugger doesn't mean it isn't there! It may be inlined and not hit a breakpoint, for exampke. With respect to the code you'd need to look, e.g., in the implementation of std::num_put<...>
: the actual output operator doesn't include the code but the do_put()
functions do.
To reset other formatting flags you'd need to hook into some operation called after each output operation. There isn't much opportunity, though: the streams din't support generic customization after each object. Below are things which can be done but I would recomment leaving formatting flags sticky. It is arguably a mistake to make the width special:
The numeric formatting is done via the do_put()
virtual functions in std::num_put<cT>
. These functiins can be overridden and, e.g., do the normal formatting by delegating to the base class implementation, followed by something else.
The key problem with this approach in your setting is that it does not work with non-numeric output. For example, outputting strings wouldn't reset anything.
It is possible to set the formatting flag std::unitbuf
causing a flush after each [correctly written] output operator. The flush is translated into a call of pubsync()
and eventually sync()
on the streams std::basic_streambuf<cT>
. The sync()
function can be overridden. That is, the approach would be installing a custom stream buffer and the flag std::ios_base::unitbuf
when setting up sone flags which sends output to the original stream and on call to sync()
resets flags.
Aside from being a bit contrived it has the problem that you can't distinguish a genuine flush from the automatic flush (the primary purpose of srd::ios_base::unitbuf
is to get std::cerr
flushed). Also, the reset happens on the first std::ostream::sentry
being destroyed. For composite values that is most likely after the first portion being formatted.
The colour formatter uses a temporary object anyway. The dstructor of this object could be used to reset some fornatting flags. The output operator would set the necessary stream information on the corresponding object when it get "formatted" (probably using a mutable
member). Of course, this means that setting the format and output need to be done from the same statement. Also, all output on the same statement receives the same format.
I'm not aware of any other approach to deal with formatting after some output. None of the approaches works particular well. I'd rather use a guard-like approach to set/unset flags than trying to be clever.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With