65.9K
CodeProject 正在变化。 阅读更多。
Home

行式打印机类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (6投票s)

1999年11月23日

5分钟阅读

viewsIcon

148039

downloadIcon

2278

一种方便的行式打印机写入方式

引言

Windows 真的很有趣,大部分情况下。但是我怀念 UNIX(或 IBM 1401)的一点是它向行打印机写入数据的方式很方便。没有花哨的图形,没有谄媚的“第 n 页,共 m 页”(除非你想编写很多额外的代码),只有数千行坚固的固定宽度 ASCII 字符。Gayle 制造公司(下面所有名称和标签中的 GM)是我工作的钢铁制造公司。我们生成的大多数常规报告都采用行打印机格式,没有什么花哨的——只是数字。你有没有想过为什么没有一种简单的方法可以迭代你的数据并一次打印一行(怀旧的叹息)?也许这太明显了,所以我错过了它。

我正沉浸在一个项目中,该项目正在重新设计一个目前在 UNIX 上运行的系统。大多数报告都很好,所以我需要一种简单的方法来从 Windows 应用程序重新创建它们。我需要一个行打印机!!我希望 printf() 输出到 'stdout',这样我就可以将其管道传输到 'lp'。这有错吗?我们的想法是循环遍历查询中的数据,并为数据的每一行打印一两行,并在适当的地方添加奇数总计。

啊,好吧。情况发生了变化,没有行打印机,所以我猜我不得不自己做一个。我设计了一个 COM 类,它有点像行打印机。它使用 VC++ 5、MFC4.2、ATL 和 STL 编写。由于它是一个双接口 COM 组件,因此可以从 C++ 或 VBA 使用它。它使创建行打印机样式的报告变得非常容易,并且可以在 MTS 下运行。这是另一种无需使用 CView 类即可进行打印的方法示例。它在 UNICODE 下编译。错误检查相当少,一些代码可能是 BFI(蛮力与无知,但如果我无知,我怎么能分辨呢?)。

COM 接口具有以下方法

write(LINE_TYPE, BSTR text)  // to send lines to the print arrays
reset(RESET_TYPE)            // to clear the print arrays
print(PRINT_TYPE)            // to format the print arrays onto the default printer
这些属性
font_size ( GMP_FONT_CPI)       //to set the fixed width font size(10, 12, 15 cpi)
orientation ( GMP_ORIENTATION)  //portrait or landscape
title(BSTR)                     //prints on the lower right corner of each page
print_heading(bool)             // set to false for really plain printing
page_breaks(bool)               //if false don't allow forced page breaks
punch_margin(double )           // Sets the width of the margin for 3 hole punching
以及这些枚举
[helpstring("enum line types")]
typedef enum
{
        GMP_LT_DEFAULT,
        GMP_LT_HEAD,
        GMP_LT_BODY,
        GMP_LT_NEWPAGE
} LINE_TYPE;

[helpstring("enum reset types")]
typedef enum
{
        GMP_RS_NONE,
        GMP_RS_HEAD,
        GMP_RS_BODY,
        GMP_RS_ALL
} RESET_TYPE;

[helpstring("enum print types")]
typedef enum
{
        GMP_DEFAULT,
        GMP_PAGE_RANGE,
        GMP_NO_PAGE_BREAK,
        GMP_NO_HEADING
} PRINT_TYPE;

[helpstring("enum orientation")]
typedef enum
{
        GMP_PORTRAIT,
        GMP_LANDSCAPE
} GMP_ORIENTATION;

[helpstring("enum font cpi selection")]
typedef enum
{
        GMP_FONT_10,
        GMP_FONT_15,
        GMP_FONT_12
} GMP_FONT_CPI;

它的工作原理如下:使用通常的方法获取指向 IGMPrintEZ 实例的指针。我喜欢 #import 方法,所以

        IGMPrintEZPtr p_prt;
        HRESULT hr = p_prt.CreateInstance(__uuidof(GMPrintEZ));

打印机对象最初为空,但为了确保我可以重置它

        p_prt->reset(GMP_RS_ALL);    // clears all the data from the arrays

将标题行写入标题行表

        // the text is a BSTR so use any means of providing one
        BSTR text = SysAllocString(L"HeadingLine 1");
        CString str_blankline = "";
        p_prt->write(GMP_LT_HEAD, text);
        // a blank line
        p_prt->write(GMP_LT_HEAD, str_blankline.AllocSysString());
        p_prt->write(GMP_LT_HEAD,
                SysAllocString(L"Column 1        Column 2  Column3"));

你可以在处理过程中的任何时间编写标题行。标题行和正文行分别使用单独的数组。驱动程序可以在处理完所有正文行之后再写入标题行,以便总计显示在每一页的标题中。这打开了许多可能性。事实上,标题、方向和字体都可以在正文行写入正文行数组之后确定,并在实际打印之前设置。

另一个操作是写入正文行

        LINE_TYPE     lt = GMP_LT_BODY;
        while(! data.IsEOF())
        {
          CString data;
          data.Format("%25s %10d", data.item, data.quantity);
          p_prt->write(lt, data.AllocSysString());
          data.MoveNext();
        }

在打印之前,我们将标题设置为横向格式,字体设置为每英寸 12 个字符

       p_prt->title = _T("Test Job");
        p_prt->orientation = GMP_LANDSCAPE;
        p_prt->font_size = GMP_FONT_12;

然后我们打印

        p_prt->print(GMP_DEFAULT);

打印对象遍历 BODY 行数组,并为数组中的每个条目打印一行。标题和页脚由水平线与正文分开。日期和时间打印在左下角,页码打印在中间,标题打印在右下角。

程序的主要元素是一对向量(vector),一个用于标题行,一个用于正文行,以及一个打印循环。打印循环代码如下所示

bool GMLinePrintpage_loop()
{
// loop thru the body lines and print to the page
        try
        {
        CPrintDialog pd(FALSE);
        DOCINFO di;     // must have DOCINFO for CDCStartDoc(DOCINFO)
        m_line = 0;
        m_max_lines = 20;
        m_last_body_line = m_max_lines;
        m_line_height = LINEHEIGHT_10;

                // if the body lines don't contain anything just return now
                if(0 == body_lines->size())
                return true;

        memset(di, 0, sizeof(DOCINFO)); // make a clean start
        di.cbSize = sizeof(DOCINFO);
        di.lpszDocName = m_title;


        // lazy way of getting the default printer
        // just get all the printer defaults - no  display
        // so this COM object can run from MTS

        pd.GetDefaults();

        DEVMODE *dm = pd.GetDevMode();

        // set orientation
        // print landscape or portrait?
        dm->dmOrientation = m_orientation + 1;
        // signify te presence of orientation data
        dm->dmFields |= DM_ORIENTATION;

        // set punch margin
        switch(m_orientation)
        {
        case GMP_PORTRAIT
                m_top_offset = 0;
                m_left_offset = INCH * m_punch_margin;
                break;
        case GMP_LANDSCAPE
                m_top_offset = INCH * m_punch_margin;
                m_left_offset = 0;
                break;
        }

        // create the printer device context by getting values from the
        // printdialog and the dm structure
        CDC dc;
        if(! dc.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
                pd.GetPortName(), dm))
        {
                AfxMessageBox(_T("Can't create DC in print_loop"));
                return false;
        }


        dc.StartDoc();

        // obtain the page dimensions from the Device Context
        m_page_height = dc.GetDeviceCaps(VERTSIZE) * MM_TO_INCH;
        m_page_width = dc.GetDeviceCaps(HORZSIZE) * MM_TO_INCH;

        CFont *oldfont;

        // select font and set line height
        switch(m_font)
        {
        case GMP_FONT_12
                oldfont = dc.SelectObject(C12);
                m_line_height = LINEHEIGHT_12;
                m_cpi = 12;
                break;
        case GMP_FONT_15
                oldfont = dc.SelectObject(C15);
                m_line_height = LINEHEIGHT_15;
                m_cpi = 15;
                break;
        case GMP_FONT_10
        default
                oldfont = dc.SelectObject(C10);
                m_line_height = LINEHEIGHT_10;
                m_cpi = 10;
                break;
        }


        // compute the lines per page
        m_max_lines = -(( m_page_height - m_top_offset) /
                                m_line_height);

        // compute the last body line
        if(m_b_print_head)
        {
                m_last_body_line = m_max_lines - FOOTER_LINES -
                head_lines->size() - HEADER_SEPARATOR;
        }
        else
        {
                m_last_body_line = m_max_lines;
        }

        // the print loop
        // I like to use the Standard Library collections when I can.
        vectoriterator itext;
        m_page = 0;
        for(itext = body_lines->begin();
                itext < body_lines->end();
                ++itext)
        {
                // look for a pagebreak
                if("@@FF" == itext->Left(4))
                {
                        if(m_b_use_page_breaks)
                        {
                        m_line = 0;
                        dc.EndPage();
                        new_page(&dc);
                        }
                        // otherwise just ignore the page_break token
                }
                else
                {
                        if (m_line >= m_last_body_line)
                        {
                        m_line = 0;
                        dc.EndPage();
                        }
                        if (m_line == 0)
                        new_page(&dc);

                dc.TextOut(
                                m_left_offset,
                                (m_line++ * m_line_height) - m_top_offset,
                                *itext
                                );
                }

        }

        dc.EndPage();
        dc.EndDoc();
        dc.DeleteDC();
        return true;
        }
        catch(...)
        {
                return false;
        }
}

此对象没有用户界面(这是一个特性!!),因此没有打印预览或打印机对话框。这使得可以在 MTS 上安装此 COM 类,并从远程系统的进程运行打印机。本文由 Richard Warg 贡献。

在许多应用程序中,我并不真的想使用通常的 Windows 分页打印。相反,我希望输出直接使用标准打印 I/O 发送到打印机。这实际上是一个很难在任何 Windows 书籍中找到的主题,至少我从未找到过关于它的任何内容。但令我惊讶的是,我最近了解到标准(DOS/UNIX)打印在 Windows 下仍然运行良好。我们只需要打开一个打印机端口并打印到它即可。

如果打印机直接连接到计算机,则很简单。当打印机在网络上时,获取打印机端口的方法也不难。

下面的示例显示了如何使用 Windows NET USE 命令将 LPT1 重定向到 NT 服务器上的共享打印机。对于 Novell 网络,使用略微不同的语法,也适用相同的技术。

尝试创建一个新的基于 MFC 窗体的项目来测试这个。在窗体上放置一个按钮,并将此代码附加到按钮上。你实际上只需要三行代码就可以打印

                FILE *fp = fopen("LPT1", "w");
                fprintf(fp,"What's up, Doc?\n");
                fclose(fp);

即时打印满足感!!

程序打开时会占用打印机端口。在我的车间,这不是问题,但要注意它对你的 Windows 暂存输出的影响。

        *********************************************************
                        THE CODE
        *********************************************************

// the headers for the conventional i/o routines
#include
#include
#include

using namespace std;      // makes string and ofstream
                        // work without std:: qualifier

void CLineprtView::OnButton1()
{
        // I could have used a CString instead of the buff[]
        // but I wanted to show how this is used with lightweight
        // ATL code and STD library

        char buff[MAX_BUFF_SIZE];

        // My printer is located on another server so I must re-direct the
        // printer port.  If the printer is directly attached this extra step
        // is not needed.
        // on my network the printer is published as \\GREEN\hp5annex
        // All those back-slashes escape the backslash in the path name

        if (PRINTER_IS_REMOTE)
        {
                system("NET USE LPT1 /d");  // free up the port
                system("net use lpt1 \\\\green\\hp5annex");
        }

        // old fashioned file handle with
        // old fashioned open of the printer port
        FILE *ptr = fopen("LPT1","w");

        // laser printer setup string
        sprintf(buff,"\033E\033(s0p4102t1b16.66H\033&l1O");
        fprintf(ptr,buff);

        // old fashioned print
        fprintf(ptr,"Who of late doth make a thimble.\n");
        fprintf(ptr,"Is a lower bunk a status symbol??\n");

        // old fashioned close
        fclose(ptr);

        // now the same thing with stream io
        ofstream optr("LPT1", ios::out);

        string str_text = "Hey Doc, Ain't this a print test from windows\n";
        str_text += "with more lines to follow?\n";

        optr << str_text << endl;
        optr << "Quiet, wabbit. I'm conversing with my muse!!\n";

        optr << "That's all folks." << "\f" << flush;     // add a formfeed

        // the printer connection is still open so close it
        optr.close();

        // drop the network link
        if (PRINTER_IS_REMOTE)
        {
                system("net use lpt1 /d");
        }
}

在实践中,我从每台机器的注册表中获取打印机路径信息,因此实际的代码比这个示例稍微复杂一些,但差别不大。

© . All rights reserved.