Question about printing

Z

Zach

Say I have PrintDocument pd = new PrintDocument();
Say I have a file c:\trialk.txt
How do I associate the two in order to privide input for ptinting?

Thanks,
Zach
 
Z

Zach

Peter Duniho said:
What kind of association are you looking for?

If you want to print the text found in the file, you need to do that
yourself. Use the File class, or FileStream, or StreamReader, or whatever
to read the text from the file. Once you have a string or strings from
the file representing the text found in the file, then you need to handle
the Print event for the document and draw the text to the page using the
Graphics.DrawString() method or similar.

Pete

Pete, could you pls look through the code below for me. It works
but that might not mean that it is ok.

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;

namespace InfraStructure
{
public class Print:Form
{
Font printFont;
StreamReader streamToPrint;
public Print(out bool result)
{
result = execute();
}
bool execute()
{
// select file
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "Open a file";
ofd.InitialDirectory = "c:\\";
ofd.Filter = "txt files (*.txt)|*.txt";
if (DialogResult.OK == ofd.ShowDialog(this))
{
streamToPrint = new StreamReader(ofd.FileName);
}
else return false;

// font dialog
FontDialog font = new FontDialog();
if (DialogResult.OK == font.ShowDialog(this))
{
printFont = font.Font;
}
else return false;

//setting margins
PageSetupDialog setup = new PageSetupDialog();
PrintDocument document = new PrintDocument();
setup.Document = document;
setup.ShowDialog();

//select printer
PrintDialog select_printer = new PrintDialog();
select_printer.Document = document;
if (select_printer.ShowDialog() == DialogResult.OK)
{
try
{
document.PrintPage += new
PrintPageEventHandler(document_PrintPage);
document.Print();
streamToPrint.Close();
}
catch
{
return false;
}
}
else return false;

return true;
}

void document_PrintPage(object sender, PrintPageEventArgs ev)
{
float linesPerPage = 0;
float yPos = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
linesPerPage = ev.MarginBounds.Height /
printFont.GetHeight(ev.Graphics);
while (count < linesPerPage && ((line =
streamToPrint.ReadLine()) != null))
{
yPos = topMargin + (count *
printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black,
leftMargin, yPos, new StringFormat());
count++;
}
if (line != null)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
}
}
}
 
Z

Zach

Peter Duniho said:
Well, after a quick glance, I only see one real problem in terms of
obtaining exactly the output you expect, and that one being relatively
minor. That is, it appears to me that the code will potentially print a
blank page at the end. If the number of lines of text in the file is
exactly a multiple of the lines you can print on a page, then your code
will incorrectly report it has additional text to print, when in fact
there's none, causing one last blank page to be printed.

In general, my approach is to create a "print status" class that persists
the state of printing between pages. This would allow you to pre-fetch a
line of text even if you've reached the end of the page, so that you can
detect the end of the stream even in that case. The class would then
contain that line of text that was read, so that it's not lost before you
continue on to the next page.

Such a class could also store pagination information, such as text height
and the font, along with the input stream, etc. (lines per page may still
need to be recalculated, if you are using per-page print settings, but
otherwise that could be cached as well).

You also have several places in your code where you present a dialog and
then return a failure if the user cancels the dialog. Not a problem per
se, but my preference, rather than code that looks like this (as yours
does):

if (dialog.ShowDialog() == DialogResult.OK)
{
// do something
}
else
{
return false;
}

to instead just write it like this:

if (dialog.ShowDialog() != DialogResult.OK)
{
return false;
}

// do something

That is, just make the failure case return, and the success case be the
main flow through the method. IMHO this makes the method more readable.

A somewhat more significant problem is that you are failing to dispose a
number of disposable objects. Most never get disposed at all (dialogs,
and your font), but even the StreamReader is disposed only on success.

You should use "using" statements liberally to address this problem.

Finally, you should not be catching all exceptions around your call to
Print(). You should limit your "catch" clause to only those exceptions
that are expected and don't represent a terminal failure. Catching any
and all exceptions and just ignoring them can lead to very
hard-to-reproduce problems and subtle data corruption bugs.

This sort of problem is very rare, but when it happens it's very hard to
figure out if the code is simply silently catching all exceptions, even
those that were not anticipated at the time the code was written. It's
much better to just write the code more robustly in the first place.

Pete
-------------------------------------------------------------------------------
Hi Pete,

Thank you very much. I think I can make changes to allow for your comment,
however, I might find your "print status" suggestion a difficult one to
realize. I might like to get back on that one, after trying myself first,
if I may.

Many thanks,
Zach.
 
Z

Zach

Thank you very much. I think I can make changes to allow for your comment,
however, I might find your "print status" suggestion a difficult one to
realize. I might like to get back on that one, after trying myself first,
if I may.

Many thanks,
Zach.
-------

Pete,

Thank you for drawing my attention to the missing usings. That was bad. I
don't ususlly use dialogs and had spent my energy on getting them to work.

The change from "==" to "!=" was a learning experience. Thank you.

What I do not understand is the possibility of printing a blanc page.
Doesn't line = streamToPrint.ReadLine() = null stop that form happening? If
I am missing the cause of blanc pages being printed, wouldn't a way out be
to count the lines before I go in and check the counter value?

Zach.
 
Z

Zach

Peter Duniho said:
Zach said:
[...]
What I do not understand is the possibility of printing a blanc page.
Doesn't line = streamToPrint.ReadLine() = null stop that form happening?

If the last line of the file winds up being printed as the very last line
possible on a page, then in this statement:

while (count < linesPerPage && ((line = streamToPrint.ReadLine()) !=
null))

The second half of the condition will never execute, because count <
linesPerPage is true. Then you'll set "ev.HasMorePages" to true, because
"line" is not null. Then the next time your PrintPage event handler is
called, it will finally call ReadLine() only to find that there is no more
data. But by that time, the page is already being printed. So you'll get
a blank page.

You can't fix the problem simply by using the non-short-circuiting &
operator instead of &&, because then you'll read a line at the end of each
page that will never get printed, because the string returned by
ReadLine() will be lost when the method returns.

To fix the problem, you should change the code so that the "line" variable
is not a local, but instead is in an instance of an object (either the
form itself along with the other stuff, or preferably in a
printing-specific object for that purpose along with the other
print-related data). Then you can always attempt to read the next line,
but only drawing the string if there's room, moving on to the next page
otherwise.
If I am missing the cause of blanc pages being printed, wouldn't a way
out be to count the lines before I go in and check the counter value?

You can read the file twice, counting lines first, and then reading it
again. Or you can read the file into an array of strings, one string per
line, before you start to print anything. Or any number of other
alternatives. But since it's possible to solve the problem storing just
one string between pages, it seems to me that's the most efficient
approach.

Pete

------------------------------------------------------------------------

Pete, I am impressed by your comprehensive answers and grateful for them. I
will study your recent post carefully. Having applied nested "usings" to the
code in question, like you suggested, has made the code much more elegant
(and less bugged). I had already applied a counter, reading the .txt twice,
but will endeavour to improve that solution along the lines you suggested.
You didn't like the catch all, but I am unaware of the mishaps that might be
caught. So I haven't known what to do about that.

Thank you again.

Regards,

Zach.
 
Z

Zach

Zach said:
Peter Duniho said:

Pete, how about this solution?
Zach.

for (int count = 0; count < linesPerPage; count++ )
{
if ((line = streamToPrint.ReadLine()) != null)
{
yPos = topMargin + (count *
printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black,
leftMargin, yPos, new StringFormat());
}
}

if (!streamToPrint.EndOfStream)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
 
Z

Zach

Peter Duniho said:
Zach said:
Pete, how about this solution? [...]
---------------
". . . If the idea of maintaining the most-recently-read line
from the StreamReader seems too hard to understand . . ."

Pete,
My cranium can manage that,
even at 73 :) Yes my frend.

---------------

private void document_PrintPage(object sender, PrintPageEventArgs
ev)
{
float linesPerPage = 0;
float yPos = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
int count = 0;
string query = null;
linesPerPage = ev.MarginBounds.Height /
printFont.GetHeight(ev.Graphics);
while (count < linesPerPage && ((line =
streamToPrint.ReadLine()) != null))
{
yPos = topMargin + (count *
printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black,
leftMargin, yPos, new StringFormat());
sr.ReadLine( );
count++;
}
if (line != null && (sr.ReadLine( ) != null))
ev.HasMorePages = true;
else
ev.HasMorePages = false;
}
 
Z

Zach

Pete, it would appeear, on the bais of trials, that your stemenet is
incorrect.

if(!streamToPrint.EnfOfStream)
ev. HasMmorePages = true
etc . . . . .

Does stop a blank page form being prined. And no lines are lost either.
 
Z

Zach

Pete,
Hopefully, "sr" is not the same StreamReader that "streamToPrint" refers
to.

* * * * * * No of course not. But I thought the solution, although
effective, was clumsy.
* * * * * * I have a print class in a number of my applications and wanted
to create a "standard" print class
* * * * * * to use when required, which is as perfect as I can get it.
I meant no offense with my comment about one approach to fixing the code
being "too hard to understand"

* * * * * * Thank you Pete, for your above remark. I much appreciate a
gentleman.
As I said, the bugs manifest themselves only with very specific input. You
can't just test the code with any text file you happen to have lying
around. It will depend on the combination of the file contents, the font
you're using, and the paper size.

I created a test file (so I did not use any old file, because that wouldn't
have been useful, given that the number of lines on the page is critical to
the bug appearing, so I changed the font size so as to vary the test
conditions, followed by retesting. That didn't bring any bugs to the fore.
Here in Holland (where I live and deveop) only A4 is used, in the context of
my administrative applications, not like in Anglo Saxon countries, with full
scap, etc.

If problems should still transpire, I will reinstate opening the file twice
and ascertaining if there is a line left to print for the "hasMorePages" to
potentially fire.

Zach
 
Z

Zach

Pete,

You were right about end of stream not being sufficient to stop a blank page
from being printed. Sorry I was so stubborn.

Zach.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top