I jumped into an old MFC application which has a problem when run under Windows 10 1703/Creators update. It works fine for XP to Windows 10/1607.
After some investigation, it seems that in Windows 10/1703 the app cannot paste metafiles (wmf and emf) from the clipboard into an CRichTextView and save it. The graphics data is not embedded in the rtf file.
Here is a stripped down example:
static void testFn(CRichEditView* View)
{
// Minimal Example
// Init MetaFileDC
CMetaFileDC MetaFileDC;
CClientDC DC(NULL);
MetaFileDC.CreateEnhanced(NULL, NULL, NULL, NULL);
CRect Recht(0, 0, 400, 300);
MetaFileDC.SetAttribDC(DC.m_hDC);
MetaFileDC.SetWindowOrg(0, 0);
MetaFileDC.SetWindowExt(Recht.Size());
// draw : "ABC" and a line
MetaFileDC.TextOutA(0, 0, "ABC");
MetaFileDC.MoveTo(0, 0);
MetaFileDC.LineTo(Recht.right, Recht.bottom);
// to clipboard
View->OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_ENHMETAFILE, MetaFileDC.CloseEnhanced());
CloseClipboard();
// paste from clipboard
View->GetRichEditCtrl().Paste();
// save rtf file
View->GetDocument()->OnSaveDocument("abc.rtf");
}
This example pastes a enhanced metafile mit "ABC" and a line into the CRichTextView and saves the document as "abc.rtf"
From Windows XP to Windows 10/1607 this works fine
with the latest Creators update the file is smaller and the data is not saved
It is probably related to RichEditBox: picture and content after the picture disappear (Windows 10 1703 Creators Update)
Any ideas? Is there a way to get the metafile graphics in the document without the clipboard?
Bitmaps still work.
We had the same problem in our software after creators update. We also get our graphic via the clipboard.
After some research on Google and SO I came up with this:
HENHMETAFILE hMetafile = nullptr;
if(OpenClipboard(AfxGetMainWnd()->m_hWnd))
{
if(EnumClipboardFormats(0) == CF_ENHMETAFILE)
hMetafile = (HENHMETAFILE) GetClipboardData(CF_ENHMETAFILE);
CloseClipboard();
}
Gdiplus::MetafileHeader header;
Gdiplus::Metafile::GetMetafileHeader(hMetafile,&header);
HDC hdc = AfxGetMainWnd()->GetDC()->GetSafeHdc();
UINT bufsize = GetWinMetaFileBits(hMetafile,0,0,MM_ANISOTROPIC,hdc);
BYTE* buffer = new BYTE[bufsize];
GetWinMetaFileBits(hMetafile,bufsize,buffer,MM_ANISOTROPIC,hdc);
std::stringstream ss;
ss << "{\\rtf1{\\pict\\wmetafile8";
ss << "\\picw" << (UINT)((header.Width / header.DpiX) * 2540) << "\\pich" << (UINT)((header.Height / header.DpiY) * 2540);
ss << "\\picwgoal" << (UINT)((header.Width / header.DpiX) * 1440) << "\\pichgoal" << (UINT)((header.Height / header.DpiY) * 1440);
ss << " " << std::endl;
ss << std::hex << std::setfill('0');
for(UINT i = 0;i < bufsize;++i)
ss << std::setw(2) << static_cast<UINT>(buffer[i]);
delete[] buffer;
ss << "}}" << std::endl;
return ss.str().c_str();
We use this now to insert graphics into our document. I have not tried it as a standalone document.
Related
I am experiencing trouble when attempting to output to a region within the top right corner of a console window. I am working within an 80x20 region, and am attempting to update a small region (16x3) in the corner with essential information to a user. However, I keep receiving error code 87 from GetLastError() as the WriteConsoleOutputW() function is returning 0 for some strange reason.
I have ensured that the size of the region's buffer is correct using a COORD object COORD{16, 3}, that the buffer's size is correctly CHAR_INFO diagBoxBuffer[48], and that the SMALL_RECT object is correctly constructed (the upper-left and bottom-right corners of the rectangle to write to) SMALL_RECT{64, 0, 79, 2}.
So, given that these parameters are what I assume to be correct, based on my readings from the Console's API docs, I seem to be unable to decipher the issue here. I have experimented with different assignments to the CHAR_INFO buffer, such as assigning a simple character to the first element and testing output, but no success, only the odd crash or an output of error code 87.
Here is what I am experimenting with (is a separate screen buffer needed? I will try this next):
if(!WriteConsoleOutputW(hStdOut, diagBoxBuffer, diagnosticBoxSize, cursordiagnosticBoxStart, &diagBox))
{
wcout << L"WriteConsoleOutputW failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
And here is some extra data declared earlier:
(diagBoxBuffer[0]).Char.UnicodeChar = L'H';
(diagBoxBuffer[5]).Char.UnicodeChar = L'W';
(diagBoxBuffer[11]).Char.UnicodeChar = L'C';
(diagBoxBuffer[4]).Char.UnicodeChar = L'|';
(diagBoxBuffer[9]).Char.UnicodeChar = L'|';
(diagBoxBuffer[10]).Char.UnicodeChar = L'|';
(diagBoxBuffer[16]).Char.UnicodeChar = L'W';
(diagBoxBuffer[17]).Char.UnicodeChar = L':';
/* Some functionality needs to be implemented here for the behavior of [17] and [18]. */
(diagBoxBuffer[35]).Char.UnicodeChar = L'|';
(diagBoxBuffer[41]).Char.UnicodeChar = L'|';
And globally:
COORD sbSize{80, 20};
/* The screen buffer for consoleRPG should be 80 character cells by 20 character cells. */
COORD diagnosticBoxSize{16, 3};
COORD cursorHome{0, 0};
/* For setting the cursor at its default position. Other positions may become necessary as the program grows. */
COORD cursordiagnosticBoxStart{64, 0};
/* A diagnostic box of 15x3 characters should be plenty, ranging from [65:79) on rows [0:2). */
COORD cursordiagnosticBoxFinish{79, 2};
SMALL_RECT consoleCoords{0, 0, 79, 19}; /* This should supply the main area for console operations. */
SMALL_RECT diagBox{64, 0, 79, 2}; /* This should supply a simple area to write to for input or coordinates, and could serve as map info later. */
hStdOut is assigned to STD_OUTPUT_HANDLE.
I assume that the STD_OUTPUT_HANDLE should be fine for writing to, but I suppose I will have to try another screen buffer to see if that solves the issue.
Here is a reproducible example, at the request of #RetiredNinja:
#define UNICODE
#include <Windows.h>
#include <WinUser.h>
#include <iostream> /* A console application should automatically initialize a console with default handles (STDIN, STDOUT, STDERROR). */
#include <bitset>
#pragma comment(lib, "User32.lib")
using namespace std;
COORD sbSize{80, 20}; /* The screen buffer for consoleRPG should be 80 character cells by 20 character cells. */
COORD diagnosticBoxSize{16, 3};
COORD cursorHome{0, 0}; /* For setting the cursor at its default position. Other positions may become necessary as the program grows. */
COORD cursordiagnosticBoxStart{64, 0}; /* A diagnostic box of 15x3 characters should be plenty, ranging from [65:79) on rows [0:2). */
COORD cursordiagnosticBoxFinish{79, 2};
SMALL_RECT consoleCoords{0, 0, 79, 19}; /* This should supply the main area for console operations. */
SMALL_RECT diagBox{64, 0, 79, 2}; /* This should supply a simple area to write to for input or coordinates, and could serve as map info later. */
CONSOLE_SCREEN_BUFFER_INFO consoleInfo{};
void clearConsole(HANDLE);
void cursorReset();
void writeDiagnostic(HANDLE, const int=0);
/* A value can be supplied here to output input records (default, 0) or error info (bad option, etc.). */
CHAR_INFO outputBuffer[80];
CHAR_INFO inputBuffer[80];
CHAR_INFO diagBoxBuffer[48];
CHAR_INFO diagnosticBoxTextGreen{L' ', FOREGROUND_GREEN};
wchar_t outputBufferW[80];
wchar_t desktop[] = L"consoleRPG";
wchar_t cmdpath[] = L"C:\\Windows\\System32\\cmd.exe";
int main() /* We want to create the basic start for consoleRPG as well as implement a small 15x3 diagnostic box in the top right corner. */
{
HWND consoleWindow = GetConsoleWindow();
HANDLE hNewProcess;
HANDLE hNewThread;
HANDLE hStdOut;
HANDLE hStdIn;
HANDLE hStdErr;
STARTUPINFOW newConsole{
1024,
NULL,
NULL,
NULL,
0,
0,
NULL,
NULL,
NULL,
NULL,
NULL,
STARTF_USEPOSITION | STARTF_USESIZE | STARTF_USECOUNTCHARS | STARTF_USESTDHANDLES,
NULL,
0,
0,
NULL,
NULL,
NULL
};
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
hStdIn = GetStdHandle(STD_INPUT_HANDLE);
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
CONSOLE_CURSOR_INFO StdCursorInfo{100, 1};
SetConsoleCursorInfo(hStdOut, &StdCursorInfo);
PROCESS_INFORMATION newProcess{};
DWORD processID = 0;
DWORD threadID = 0;
if(!CreateProcessW(
cmdpath,
NULL,
NULL,
NULL,
FALSE,
CREATE_UNICODE_ENVIRONMENT, /* dwCreationFlags - this is possibly unneeded. */
NULL,
NULL,
&newConsole,
&newProcess
)
)
{
wcout << L"CreateProcess failed. Error Code: " << GetLastError();
system("pause");
return 0;
}
if(!SetConsoleTitle(desktop))
{
wcout << L"SetConsoleTitle failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
if(!SetConsoleWindowInfo(hStdOut, TRUE, &consoleCoords))
{
wcout << L"SetConsoleWindowInfo failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
if(!SetConsoleScreenBufferSize(hStdOut, sbSize))
{
wcout << L"SetConsoleScreenBufferSize failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
SetLastError(0);
LONG_PTR windowStyle = GetWindowLongPtrW(consoleWindow, GWL_STYLE);
if(windowStyle == 0)
{
wcout << L"GetWindowLongPtrW failed. Error code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
if(SetWindowLongPtrW(consoleWindow, GWL_STYLE, windowStyle & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX) == 0 && GetLastError()!= 0)
{
wcout << L"SetWindowLongPtrW failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
if(!SetWindowPos(consoleWindow, HWND_TOP, 325, 200, 640, 240, SWP_NOSIZE))
/* This successfully makes the window non-adjustable and unable to be maximized. */
{
wcout << L"SetWindowPos failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
system("pause");
if(!GetConsoleScreenBufferInfo(hStdOut, &consoleInfo)) /* stdConsoleInfo will be written to. */
{
wcout << L"GetConsoleScreenBufferInfo failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
}
wcout << L"sizeX: " << consoleInfo.dwSize.X << L" " << L"sizeY: " << consoleInfo.dwSize.Y << L"||" << L"cursorPosX: " <<
consoleInfo.dwCursorPosition.X << L' ' << L"cursorPosY: " << consoleInfo.dwCursorPosition.Y << L'\n' <<
L"charAttributes: " << bitset<8>{consoleInfo.wAttributes} << L'\n' << L'\n' <<
L"upperLeftX: " << consoleInfo.srWindow.Left << L" " << L"upperLeftY: " << consoleInfo.srWindow.Top << L" "
<< L"bottomRightX: " << consoleInfo.srWindow.Right << L" " << L"bottomRightY: " << consoleInfo.srWindow.Bottom << L'\n'
<< L"Rows(0-based): " << abs(consoleInfo.srWindow.Top - consoleInfo.srWindow.Bottom) << L'\t' << L"Columns(0-based): " << abs(consoleInfo.srWindow.Right - consoleInfo.srWindow.Left) << L'\n';
system("pause");
/*
Now, the console is ready for some simple formatting to prepare us for the idea of a game: functions are to be written that will process basic text. Some 48 character cells (16x3) in the upper right should be dedicated to things like the display of Health, Currency, and Location. For now Health will be a simple number.
OOOOOOOOOOOOOOOO <- Health and Weapon and Currency can go on this top row. The important statistics are shown first.
OOOOOOOOOOOOOOOO <- Weapon(?), Cargo(?), FullCargo(?) can go on this middle row.
OOOOOOOOOOOOOOOO <-- Location and time information can go on this last row.
Because space is limited for this simple consoleRPG, we must use abbreviation and symbols to display necessary information.
For the top row we can use three four-letter symbols, separated by a single pipe and a double pipe. For example: H054|W010||C0090
For the second row we will display our weight for now. For example: "W: 3/10" (The large amount of space allows us to take advantage of
great weights.) If a horse, or a carriage, etc. is in one's possession, weight will be displayed as W!.
The third row will display a three-letter symbol for a town or location followed by the time of day and the date. Useful.
Example: "SEN|NIGHT|23 CAL"
So, something we can expect for an adventurer in the middle of the game would be something like this: H054|W036||C0090
W: 3/10
SEN|NIGHT|23 CAL
*/
clearConsole(hStdOut);
/*
Pre set-up has been completed and output, and it is now time to begin outputting the basic game data. We need a way to format the 48 character
buffer that will display essential information.
So, diagBoxBuffer will need to have static characters in place at certain elements:
[0] will need to remain 'H', [5] will need to remain 'W', [11] will need to remain 'C'
[4], [9] and [10] will need to remain '|'
[16] will need to remain 'W', [17] will need to remain ':' or '!' if a horse is owned, allowing [18] to display ':'
[35] and [41] will need to remain '|'
Now, how can I ensure that numbers are displayed correctly after their symbols, i.e. prefixed by a 0? We may have to display blanks instead.
I assume I could use string-to-integer, stoi() to communicate from the diagnostic menu to the game functionality.
stoi(s) simply converts a wstring to an integer. stoi(s,p) will relay the number of characters used in the conversion of s to an integer to the element contained at p.
For communication from the game functionality to the diagnostic menu, to_wstring(x) can be used.
A prefixed 0 or blank can be prefixed to the returned string if it only contains 2 characters. Accommodations must be made to ensure that the string is displayed as characters only, without a 0 suffixed to its end.
*/
(diagBoxBuffer[0]).Char.UnicodeChar = L'H';
(diagBoxBuffer[5]).Char.UnicodeChar = L'W';
(diagBoxBuffer[11]).Char.UnicodeChar = L'C';
(diagBoxBuffer[4]).Char.UnicodeChar = L'|';
(diagBoxBuffer[9]).Char.UnicodeChar = L'|';
(diagBoxBuffer[10]).Char.UnicodeChar = L'|';
(diagBoxBuffer[16]).Char.UnicodeChar = L'W';
(diagBoxBuffer[17]).Char.UnicodeChar = L':'; /* Some functionality needs to be implemented here for the behavior of [17] and [18]. */
(diagBoxBuffer[35]).Char.UnicodeChar = L'|';
(diagBoxBuffer[41]).Char.UnicodeChar = L'|';
for(size_t i{}; i<80; ++i)
{
if((diagBoxBuffer[i]).Char.UnicodeChar == L'H'){continue;}
if((diagBoxBuffer[i]).Char.UnicodeChar == L'W'){continue;}
if((diagBoxBuffer[i]).Char.UnicodeChar == L'C'){continue;}
if((diagBoxBuffer[i]).Char.UnicodeChar == L'|'){continue;}
if((diagBoxBuffer[i]).Char.UnicodeChar == L':'){continue;}
(diagBoxBuffer[i]).Char.UnicodeChar = L' ';
}
wcout << L"diagBoxBuffer written to\n";
system("pause");
clearConsole(hStdOut);
//if(!WriteConsoleOutputW(hStdOut, &diagBoxBuffer[0], diagnosticBoxSize, cursordiagnosticBoxStart, &diagBox))
if(!WriteConsoleOutputW(hStdOut, diagBoxBuffer, diagnosticBoxSize, cursordiagnosticBoxStart, &diagBox))
{
wcout << L"WriteConsoleOutputW failed. Error Code: " << GetLastError() << L'\n';
system("pause");
return 0;
} /* Program crashes here without any error code reported. */
while(1)
{
}
return 0;
}
void clearConsole(HANDLE screen)
{
DWORD cCharsWritten;
CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
DWORD dwConsoleSz;
if(!GetConsoleScreenBufferInfo(screen, &bufferInfo))
{
return;
}
dwConsoleSz = bufferInfo.dwSize.X * bufferInfo.dwSize.Y; /* X (character cells) * Y (character cells) */
/* Now we fill the entire screen with blanks. */
if(!FillConsoleOutputCharacter(
screen,
L' ',
dwConsoleSz,
cursorHome,
&cCharsWritten
)
)
{
return;
}
/* Then we get the current text attribute (maybe unnecessary?) */
if(!GetConsoleScreenBufferInfo(screen, &bufferInfo)) /* Perhaps the ScreenBuffer needs to be set again, see SetConsoleCursorPosition. */
{
return;
}
/* And set the buffer's attributes accordingly. */
if(!FillConsoleOutputAttribute(
screen,
bufferInfo.wAttributes,
dwConsoleSz,
cursorHome,
&cCharsWritten
)
)
{
return;
}
if(!SetConsoleCursorPosition(screen, cursorHome))
/*Research is needed regarding this function as it does not seem to move the cursor. */
{
wcout << L"SetConsoleCursorPosition failed. Error Code: " << GetLastError();
system("pause");
}
return;
}
void cursorReset()
{
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorHome);
return;
}
void cursorDiagBox()
{
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursordiagnosticBoxStart);
return;
}
Edited: The top right corner is where I am attempting to write to.
Edit 2: I have found some success in using WriteConsoleOutputCharacterW, but not WriteConsoleOutputW. It would be more simple to use the latter; for now I return to experimentation.
According to the Doc: WriteConsoleOutput function
A rectangle of the same size is located with its upper-left cell at
the coordinates of the dwBufferCoord parameter in the lpBuffer array.
As far as I'm concerned, dwBufferCoord.Y< dwBufferSize.Y ; dwBufferCoord.X<dwBufferSize.X
cursordiagnosticBoxStart.X<16
cursordiagnosticBoxStart.Y<3
My main goal is to set the console font to Unifont using windows API functions. I can successfully do this by calling AddFontResource with the filename. I would rather use AddFontMemResourceEx because then I can load the font from a Resource.
I tested a variety of calls to load the font, and here are the results:
AddFontResource(filename); //works
AddFontResourceEx(filename, FR_PRIVATE, NULL); //does not work
AddFontResourceEx(filename, FR_NOT_ENUM, NULL); //works
AddFontMemResourceEx(valid_pointer, data_size, NULL, &font_count); //does not work
I tested these with two fonts, Unifont, and Fira Code (the only other font I could find that would display on the terminal).
I wrote this program to cut out possible issues with loading the font as a resource.
#include <windows.h>
#include <iostream>
#include <fstream>
#include <filesystem>
int main() {
std::string filename = "unifont-13.0.04.ttf";
std::wstring fontname = L"Unifont";
auto fileSize = std::filesystem::file_size(filename);
std::ifstream in(filename, std::ifstream::binary);
char* data = new char[fileSize];
in.read(data, fileSize);
in.close();
DWORD loadedFonts = 0;
AddFontMemResourceEx(reinterpret_cast<PVOID>(data), fileSize, NULL, &loadedFonts);
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
cfi.nFont = 0;
cfi.dwFontSize.X = 0;
cfi.dwFontSize.Y = 16;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy_s(cfi.FaceName, fontname.c_str());
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
std::wcout << "I found " << loadedFonts << " fonts.";
std::wcin.ignore();
delete[] data;
}
With unifont, AddFontMemResourceEx will set loadedFonts to two, indicating it did find and load two fonts, and did not fail.
Worth noting, the HANDLE returned by SetCurrentConsoleFontEx is entirely useless for doing anything except calling RemoveFontMemResourceEx, which you don't even need to do in most cases because the fonts will be unloaded when the process ends.
Why does SetCurrentConsoleFontEx work with AddFontResource but not with AddFontMemResourceEx?
From the doc,
This function allows an application to get a font that is embedded in
a document or a webpage. A font that is added by AddFontMemResourceEx
is always private to the process that made the call and is not
enumerable.
For other content, please refer to zett42's answer,
You are calling AddFontResourceEx() with FR_PRIVATE flag, which means
the font is available only to your process.
Unfortunately, the console window is not part of your process
(GetWindowThreadProcessId() lies in this regard!). It is hosted by a
system process ("csrss.exe" before Win 7, "conhost.exe" since then).
I am using Boost 1.54 Logging APIs and trying to change the color of the console log output. Below is the link to the minimum test code I am using (felt the code was a bit long to post directly):
http://ideone.com/o2KGw9
So, line 96 works, which is a std::cout to the console in color:
std::cout << colorSet( h, DARKTEAL )<<" I am coloured!!" << colorSet( h, GRAY ) << std::endl;
But when I try to do the same in boost::log::expressions::stream, in line 77, there is no color in the printed text.
logging::formatter fmtconsole = expr::stream << colorSet( h, BLUE )<< "DEBUG:" << expr::smessage << colorSet( h, GRAY ) << "\n";
Below is a screen capture of the output I get:
I remember reading a post by in the Boost sourceforge forums by Andrey Semashev that it is possible to write a custom formatter that adds color control codes to the output, but AFIK that works only with Linux terminals, which is why I am trying this approach.
Is there a way to achieve this?
Thanks.
UPDATE:
So the way I achieved it is by giving a custom formatter function as Semasheve suggested:
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
HANDLE h = GetStdHandle( STD_OUTPUT_HANDLE );
// Finally, put the record message to the stream
boost::locale::generator gen;
std::locale::global(gen(""));
boost::locale::date_time now;
std::cout.imbue(std::locale());
std::cout << colorSet( h, BLUE ) << boost::locale::as::ftime( "%H:%M:%S" ) << "[" << now << "] ";
std::cout << colorSet( h, RED ) << rec[expr::smessage] << colorSet( h, GRAY );
}
Refer to the main code link to see colorSet definition.
And then set the formatter for the backend like:
consolebackend->set_formatter( &my_formatter );
Unfortunately I am not using the formatting_ostream object, which makes this like a not a great solution. And the reason I couldn't use it was because of the way Boost flushes the stream. So I would get only the last color in the stream. Thus I ended up using std::cout directly.
But I am sure it is possible, somehow.
Though this does the job, I still need a mechanism by which I can get a stream as a temporary object, which could be called from a macro.
Maybe something like:
class TMP_LOG
{
public:
TMP_LOG(const Color& c) : col(c), m_str() { }
~TMP_LOG()
{
HANDLE h = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTextAttribute(h, col);
BOOST_LOG_SEV( slg, normal ) << m_str.str();
SetConsoleTextAttribute(h, GRAY);
}
std::ostringstream& stream() { return m_str; }
private:
std::ostringstream m_str;
Color col;
};
#define LOG_DEBUG TMP_LOG(RED).stream()
#define LOG_WARN TMP_LOG(YELLOW).stream()
You probably need to write something of a "lazy manipulator" (as otherwise it will evaluate immediately, instead of when actually writing to the output stream).
This will be quite tricky.
Instead, you can use a terminal emulator with ANSI capabilities.
My weapon of choice is mintty, but others exist for windows too
#include <Windows.h>
#include <iostream>
#include <vector>
#include <string>
//
// Desired Output: Text in clipboard should be displayed to the screen.
//
int main( void )
{
//
// OLE COM Interface
//
HRESULT hr;
// Get Clipeboard
IDataObject* pcb = 0;
OleGetClipboard(&pcb);
// Get Clipeboard Data Interface
FORMATETC format;
format.cfFormat = CF_TEXT;
format.ptd = NULL;
format.dwAspect = DVASPECT_CONTENT;
format.lindex = -1;
format.tymed = TYMED_ISTREAM;
STGMEDIUM medium;
hr = pcb->GetData(&format, &medium);
if( FAILED(hr) )
return hr;
// Get Stat of returned IStream
IStream* pis = medium.pstm;
STATSTG stat;
ULONG cb = 0;
hr = pis->Stat(&stat,STATFLAG_DEFAULT);
if( SUCCEEDED(hr) )
{
if( stat.pwcsName )
std::wcout << L"Name: " << stat.pwcsName << std::endl;
std::cout << "DataSize: " << stat.cbSize.QuadPart << std::endl;
std::cout << "Type: " << stat.type << std::endl;
cb = stat.cbSize.QuadPart;
}
// Read Data from IStream
std::vector<char> v;
v.resize(cb);
ULONG ret;
hr = pis->Read(v.data(), cb, &ret);
if( FAILED( hr ) )
{
std::cout << "Failed to Read" << std::endl;
}
else
{
std::string out(v.begin(),v.end());
std::cout << "Read " << ret << "chars. Content: {" << out << "}" << std::endl;
}
pis->Release();
//
// My Output when I have 40 characters in Clipboard
//
// DataSize: 40
// Type: 2
// Read 0chars. Content: { }
// The number of characters are correct, but content always appear empty.
}
Hello.
I'm trying to gain access to clipboard through IStream interface.
IStream::Stat seems like giving me correct status of the IStream, but IStream::Read is not really giving me any data.
I have almost zero experience in using COM object and IStream interface.
Please, point out if there is obvious error.
Thank you.
My condolences for having to use COM and C++. It's been 4 or 5 years since I've touched the stuff, but looking at what you have, I'd guess one of two things is a problem:
The IStream pointer starts out at the end of the data. In this case you have to call pis->Seek(0, STREAM_SEEK_SET, NULL) to reset it at the beginning. It may be that the call to pis->Read() returns S_FALSE rather than S_OK; the MSDN docs on Read() say that this can occur if the stream pointer is at the end of the stream.
The Clipboard just doesn't support using IStream. Actually I've never heard of doing this; I thought the usual method was to access Clipboard data as a global memory block. (See this example which is a lot simpler than your code) IStreams are necessary when you get into icky subjects like structured storage which was the old way that MS Office applications stored hierarchical data in one file.
A side note: if you don't have to use C++, and are familiar with other languages that have bindings for Windows clipboard access (C#, VB for "native" .NET access; Java has portable Clipboard access with a subset of the native Windows features, I think Python does also), you don't have to mess with any of those ugly COM features.
After a few days of searching the net on how exactly I can go about printing an arbitrary string to an arbitrary printer on windows, I finally came up with this code.
LPBYTE pPrinterEnum;
DWORD pcbNeeded, pcbReturned;
PRINTER_INFO_2 *piTwo = NULL;
HDC printer;
EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,NULL,0,&pcbNeeded,&pcbReturned);
pPrinterEnum = new BYTE[pcbNeeded];
if (!EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,pPrinterEnum,pcbNeeded,&pcbNeeded,&pcbReturned)) {
qDebug() << "In Print, could not enumerate printers";
} else {
piTwo = ((PRINTER_INFO_2*)pPrinterEnum);
for (int i = 0; i < pcbReturned; i++) {
QString name = QString::fromWCharArray(piTwo[i].pPrinterName);
if (this->m_printer_path == name) {
const WCHAR * driver = L"WINSPOOL\0";
printer = CreateDC(NULL,piTwo[i].pPrinterName,NULL,NULL);
}
}
}
if (printer == 0) {
qDebug() << "No Printer HDC";
return;
} else {
qDebug() << "Printer seems okay!";
}
qDebug() << "Starting Document";
DOCINFO di;
memset( &di, 0, sizeof( di ) );
di.cbSize = sizeof( di );
WCHAR * text = new WCHAR[ba.length()];
QString(ba).toWCharArray(text);
StartDoc(printer,&di);
qDebug() << "Writing text";
TextOut(printer,0, 0, text, ba.length());
qDebug() << "Text Written";
EndPage(printer);
qDebug() << "Page ended";
DeleteDC(printer);
qDebug() << "DC Deleted";
Some basic caveats:
1) I cannot use QPrinter. I need to write raw text, no postscript.
2) I do not know the name of the printer until the user sets it, and I do not know the size of the string to print until the user creates it.
Additional information:
a) The printer works, I can print from Notepad, Chrome, just about everything to the printer that I want.
b) I am willing to implement just about any hack. Ones like write it to a text file and issue the copy command don't seem to work, that is, I get a failed to initialize device error.
This works:
notepad /P Documents/test_print.txt
This does not work:
copy Documents\test_print.txt /D:EPSON_TM_T20
copy Documents\test_print.txt /D \MYCOMPUTER\epson_tm_t20 (leads to access denied, printer is shared)
print Documents\test_print.txt (Unable to initialize device)
I have tried just about every recommended way to print a text file from the command line, just doesn't work. I have installed, reinstalled driver, added printer, mucked with ports and done it all again.
Obviously there is something simple about windows printing that I am missing due to inexperience.
What I want to accomplish is:
1) Best Scenario( Directly write text to the printer)
2) Second best scenario (Write text to a file, then execute some program to print it for me) Notepad adds an annoying amount of space to the bottom of the printout wasting paper.
Since the program is for end users, I have to find a way to do this automagically for them, so I can't expect them to click checkbox a in tab 36 after running command obscure_configuration from a powershell.
Any help would be greatly appreciated.
/Jason
UPDATE
This is the working code, before I go through an spruce it up a bit, which prints the contents of a QByteArray to a thermal printer.
qDebug() << "Executing windows code";
BOOL bStatus = FALSE;
DOC_INFO_1 DocInfo;
DWORD dwJob = 0L;
DWORD dwBytesWritten = 0L;
HANDLE hPrinter;
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
qDebug() << "opening printer";
bStatus = OpenPrinter(name,&hPrinter, NULL);
if (bStatus) {
qDebug() << "Printer opened";
DocInfo.pDocName = L"My Document";
DocInfo.pOutputFile = NULL;
DocInfo.pDatatype = L"RAW";
dwJob = StartDocPrinter( hPrinter, 1, (LPBYTE)&DocInfo );
if (dwJob > 0) {
qDebug() << "Job is set.";
bStatus = StartPagePrinter(hPrinter);
if (bStatus) {
qDebug() << "Writing text to printer";
bStatus = WritePrinter(hPrinter,ba.data(),ba.length(),&dwBytesWritten);
EndPagePrinter(hPrinter);
} else {
qDebug() << "could not start printer";
}
EndDocPrinter(hPrinter);
qDebug() << "closing doc";
} else {
qDebug() << "Couldn't create job";
}
ClosePrinter(hPrinter);
qDebug() << "closing printer";
} else {
qDebug() << "Could not open printer";
}
if (dwBytesWritten != ba.length()) {
qDebug() << "Wrong number of bytes";
} else {
qDebug() << "bytes written is correct " << QString::number(ba.length()) ;
}
Note: I do owe an apology to Skizz, what he wrote was actually helpful in debugging the fundamental issue. The characters in the QByteArray are preformatted specifically for the printer, the problem is, they contain several NULL bytes. When trying to send them to the printer, this causes TextOut to truncate the text, only printing the first few lines. Using WritePrinter, as suggested in the answer ignores null bytes and accepts a void * and a length, and just puts it all there.
Further, his response recommending the use of PrintDlg did work to fectch the correct printer HDC, the issus is that, the user first chooses a printer once, and then doesn't need to choose it each time they print, because they will be printing alot (It's a Point of Sale).
The problem with getting the printer HDC from the string name was due to not adding the all important NULL byte to wchar_* which was solved this way:
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
In the above, m_printer_path is a string representation of the name of the printer taken from Print Manager.
Because the string has all the formatting necessary for the printer, there's no need to worry about new lines, or any formatting.
All three answers to this question were actually very helpful in implementing the final working solution, and I have voted up each answer, and I appreciate the time each person took in responding.
Most modern printers don't perform any form of layout processing of the data they are given. Thus, sending a sequence of characters to the printer would, at best, just print a line of text running off the side of the page in some default font. Carriage returns may work too.
What modern printers usually do is print pages using preprocessed data that the printer understands and defines what to print where and how to print it. All this preprocessing is done on the host PC and the results sent to the printer. This is why you usually install printer drivers - these drivers take the user data (whether it's a simple text file or a DTP page) and converts it into a language the printer understands.
The upshot of this is that sending raw text to the printer probably won't work.
Then you've got the problem of having multiple printers with different properties and languages.
So, in Windows, all this is abstracted into the printer device context object. This has the same interface as a graphics device context but you create it differently.
The Win32 API has a common dialog to let the user choose the printer. Use the PrintDlgEx function to allow the user to choose a printer. Then use the returned DC to draw text to the page.
There are a couple of MSDN articles describing how to send raw data (printer control codes, etc.) to a printer.
How To: Send Data Directly to a GDI Printer
How To: Send Data Directly to an XPS Printer
You have the right idea (though you should have StartPage and EndDoc calls to match up). The problem is that TextOut draws only a line of text. It won't break long strings into multiple lines, etc. You need to do that (or find code to do it).
If you know that the text will always fit on a single page, you could probably replace your TextOut with a DrawTextEx call, which can do basic line breaking, tab expansion, etc.
Why not try QPrint.. it prints raw text using a Generic Text Only driver
QString prn("^XA^FO121,41^A0N,19,15^FDABC DEFGHIJKLMNOPQRSTUVWXYZ^FS^XZ");
QPrinter printer(QPrinterInfo::defaultPrinter()); //the default printer is "Generic / Text Only"
QTextDocument doc(prn);
doc.print(&printer);
MTry the following code in C++:
#include<fstream>
Class PrinterDriver{
Private:
fstream print("PRN")
Public:
Void Print(char a[]){
print >>a;}
Char GetPrinterStatus[](){
char c[];
print<<c;
return c;}};
understand it(key)