WPF Printing – some issues resolved

After my recent post I emailed the development team at Microsoft and some issues have been worked out.

When printing a landscape document via the preview control you need to set a print ticket on the FixedDocument before displaying it in preview.

FixedDocument x = new FixedDocument();
PrintTicket pt = new PrintTicket();
pt.PageOrientation = PageOrientation.Landscape;
x.PrintTicket = pt;

You can then set the Document property of the DocumentViewer and when you print from there it should print landscape.

I haven’t tested this in anger because I moved on and found a neater approach (and I didn’t really want a preview of what I was printing I simply wanted to avoid wasting paper and ink).

I’ve since set up a PDF print queue using RedMon (great wee utility) and GhostScript so I’ve been using that to produce PDFs which I can look at.

A much tidier approach to printing involves printing objects that derive from the Visual class (and all FrameworkElement objects in  WPF do). So I created a new Page (although you could also base a new class on UserControl) and called it EntrySheet, set up the content as I wish and then to print I do the following:

PrintDialog pd = new PrintDialog();
pd.PrintTicket.PageOrientation = PageOrientation.Landscape;

Size ps = new Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);

EntrySheet p = new EntrySheet();
p.Width = ps.Width;
p.Height = ps.Height;

p.Measure(ps);
p.Arrange(new Rect(new Point(0, 0), ps));
p.UpdateLayout();

pd.PrintVisual(p, "Entry Sheet");

Because the EntrySheet object is not being displayed prior to printing, you must call Measure and Arrange so that it’s content can be dimensioned and layed out. UpdateLayout isn’t always needed but I found that if your Visual includes a DataGrid then this step is essential.

The issue I haven’t been able to resolve is a DataGrid where the columns have been given star dimensions.

Tags: ,

Trials and Tribulations with WPF Printing

I’m developing a program to do sailing race results for my club. After some deliberation I elected to use WPF 4 as it finally includes the DataGrid control which I’ve always thought was essential. I’ll blog about things I’ve done to make editing and entering results as easy as possible soon as that was an interesting learning curve and what at first appeared to be quite limited is finally working well.

With regards to printing WPF provides some very interesting and very powerful features and overall I’m impressed at how easy it is to produce printed output, though there are a few oddities.

These mostly came to light as I started working on printing race entry sheets which are set out before the race for competitors to enter with a set of boxes for the Race Officer to enter lap finish times. Essentially what I wanted was a single page containing a grid with equal size columns laid out landscape.

First I set up a Page with a DataGrid and fixed columns giving each column a star width. I added 15 empty rows to the grid to get a table. Getting a Page into a FixedDocument (which I will also detail elsewhere), is a bit of a palaver but I’d already worked out how to do that and so I got that done and that for Landscape I needed to change the PageOrientation on FixedDocument’s DocumentPaginator.

First problem, did a preview using a DocumentViewer and things look good, but print and it comes out Portrait mode on the paper and half my grid is missing, and after much searching I found the following:

http://stackoverflow.com/questions/1003585/setting-pageorientation-for-the-wpf-documentviewer-printdialog

I’m already using a custom template for a DocumentViewer to remove the Search box, and the template I’d got from the standard control doesn’t have a print button and so there is no way to intercept the print command that I could see.

Two issues with DocumentViewer in my opinion:

1. Need to be able to turn off the search facility
2. Need simple way to specify print orientation or it should work it out for itself (I’m passing a FixedDocument after all which knows the orientation!).

So I looked at printing without previewing.

The next issue which caused much head scratching, the DataGrid columns weren’t sized equally when printed but instead were given a minimal size despite the grid stretching across the page. I tried calling Measure and Arrange on the Page and on the DataGrid but neither helped. Eventually I discovered I had to call BeginInit and EndInit after Measure and Arrange to do the sizing of the columns.

The dynamic DataGrid column sizing didn’t happen because I wasn’t previewing, i.e. making the document visible.

What I evenually also realised was that I didn’t need the Page at all or the FixedDocument, I could create a UserControl and set it’s size to the page, Measure, Arrange, BeginInit and EndInit and then print directly as a Visual.

Tags: , ,

Poor man’s URL rewrite for IIS

Mod_rewrite is a very handy module that is included with most Apache installations and allows you to achieve some rather nifty stuff with a few lines in the .htaccess file.

IIS 6 doesn’t have a direct mechanism to compete with this but you can still achieve similar results using IIS’s error handling mechanism.

Start off by adding an error handling URL, in IIS Manager open the web site’s properties and select the Custom Errors tab:

IIS Custom Errors

Scroll down and find the 404 HTTP error entry, and then click on the Edit button.

IIS Custom Error Edit

Select URL from the message type drop down and enter your URL in the File: box, say “/404handler.asp”. NB the leading forward slash is required.

Click OK to save this and click OK in the properties dialog to apply the change to the website.

Now you need to create your 404 handler.

The first thing to note is that that in Classic ASP, Response.QueryString will hold a value which looks like the following:

404;/url-that-doesnt-match-a-file?x=2

Which means you wont be able to use Response.QueryString(“x”) to read the value 2, so you may want to refrain from using query strings, which is normally one of the reasons you are looking at implementing a URL rewrite scheme.

So at the start of the handler the first thing you will need to do is look at Response.QueryString and check that the first four characters are “404;”

If that’s the case then use the remainder of the string as the URL to be rewritten.

How you do this is really up to you, a simple scheme I’ve used is a look up table in the database to find the name of a script and then use System.Transfer to switch control to it. Remember that no variables declared and assigned in the first script are available in the script called by System.Transfer, but the Request object is still valid so that isn’t really a problem.

You can also use the same table to issue redirect to your SEO friendly URL if someone accesses it directly.

Related material:

Tags: , ,

Mask default home page in IIS

Recently a SEO consultant noted that on a site I was working on the home page was listed on two urls:

http://www.example.com/

http://www.example.com/default.asp

He suggested that any attempt to visit /default.asp directly should 301 redirect to /.

The obvious approach seemed to be to use Request.ServerVariables(“url”) to see what had been requested, and here I found a problem in IIS 6 (but not in IIS 7). IIS 6 rewrites the URL “/” to “/default.asp” so there was no way to tell which the user had requested and any attempt to redirect based on the server variable would cause a 301 infinite loop.

My solution was to change the default document setting in IIS to a document that did not exist and then register a 403;14 error handler.

Now when the user requests “/” IIS 6 cannot find the default document and instead tries to list the contents of the root directory. Make sure that Directory Browsing is disabled. This will cause a 403;14 error and our default.asp page will be called.

At the top of the default.asp page I add the following code:

dim arrqs

'
' If we are being called as an error handler then querystring will look like:
'
' 403;http://www.example.com:80/
'
if instr(request.querystring, ";") > 0 then
  '
  ' If there is a semi-colon, split the string and check that the first
  ' substring is 403. If not you need to think about what you would like
  ' to do, out of laziness here I just put a 301 redirect to "/".
  '
  arrqs = split(request.querystring,";")
  if arrqs(0) <> "403" then
    response.status="301 Moved Permanently"
    response.addheader "Location","/"
    response.end
  end if
end if

'
' Next if query string is empty I'm redirecting to "/" because that
' means someone is trying something other than "/".
'
if request.querystring = "" then
  response.status="301 Moved Permanently"
  response.addheader "Location","/"
  response.end
end if

Fairly simply though not elegant.

IIS 7 doesn’t rewrite the URL server variable so it’s simpler. No need to have the default document set to a non existant page, and the start of default.asp is:

if Request.ServerVariables("URL") = "/default.asp" then
  response.status="301 Moved Permanently"
  response.addheader "Location","/"
  response.end
end if

Related posts:

Tags: , , ,

jQuery datepicker widget with drop downs

A simple approach to a date selector drop downs working with jQuery’s datepicker. This uses three selects to get the month, day and year.

Example

The first step is the html.

<div><input id="a"
  style="position: absolute; z-index: -1;"
  type="text" />
<select id="a_month">
<option value="">Month</option>
<option value="1">Jan</option>
...
</select>
<select id="a_day">
<option value="">Day</option>
<option value="1">1</option>
...
</select>
<select id="a_year">
<option value="">Year</option>
</select>
<a href="javascript:showDatepicker('a')">
  <img img="calendar.png">
</a>
</div>

I’ve left the year drop down with no years in it since I’ll auto populate it using javascript when the page is loaded.

Note the div tag around everything and that I’ve put a text input first which wont be seen (it’s z-index hides it under the current layer). The position style means that the selects will lie on top of it.

Also the id of the text input is the same as the beginning of each select. This allows me to have multiple select groups and use the same javascript functions for display and callback.

The date picker is intended to be attached to an input or a div. If it’s attached to a div then it will replace the div with a date selector which would not be ideal here.

So we are going to attach our datepicker to the hidden input. In this case the datepicker will be overlayed on the page and should line up with the bottom of the input (which should be where the bottom of the selects are).

First use the ready event on the document to set the drop downs to todays date, and populate the year with last year through to next year.

$(document).ready(function() {
  //
  // Get today's date
  //
  var d = new Date();

  //
  // Set month drop down to this month (NB getMonth is 0 based so
  // we need to add one to the return value)
  //
  $('#a_month').val(d.getMonth()+1);

  //
  // Set day drop down to today's date.
  //
  $('#a_day').val(d.getDate());

  //
  // Get the year drop down and populate it with
  // last year, this year and next year
  // so we have some choices.
  //
  var y = $('#a_year').get(0);
  for (i = d.getFullYear()-1; i < d.getFullYear()+2; i++)
    y.options[y.options.length] = new Option(i, i);

  //
  // set selected year
  //
  $('#a_year').val(d.getFullYear())

  //
  // next initialise the datepicker, setting up min and max dates that
  // correspond to the date range we can select using the drop downs.
  //
  var minDate = new Date(y.options[1].value, 0, 1);
  var maxDate = new Date(y.options[y.options.length-1].value, 11, 31);
  $('#a').datepicker({dateFormat: 'd/m/yy',
      minDate: minDate,
      maxDate: maxDate,
      onSelect: setDate});
});

Next the function which will display the datepicker

function showDatepicker(id)
{
  //
  // Set the text input to the selected date in the drop downs
  // in the format we set for the datepicker.
  //
  $('#' + id).val($('#' + id + '_day').val() + '/' +
    $('#' + id + '_month').val() + '/' +
    $('#' + id + '_year').val());

  //
  // Display the date picker!
  //
  $('#' + id).datepicker('show');
}

And finally the callback

function setDate(text, control)
{
  //
  // Text is the value selected by the user in the form d/m/yyyy
  // Control is the html input element, so we can use
  //the id value to select the drop downs.
  //
  var dt = text.split('/');
  $('#' + control.id + '_day').val(dt[0]);
  $('#' + control.id + '_month').val(dt[1]);
  $('#' + control.id + '_year').val(dt[2]);
}

And that’s it. If you wanted to remove the limits on years you can not set the maxDate and minDate options. You would need to add options to the year select in the callback setDate to display the user’s selection.

Related posts:

Read the rest of this entry »

Tags: ,

Classic ASP TextStream and unicode

I had the following error from a piece of code which had been working:

Microsoft VBScript runtime error '800a0005'
Invalid procedure call or argument

A snippet of what was throwing this:

set os = fs.createtextfile(fn, true)
os.write textmsg
os.close

The error was on the os.write call. The error I eventually realised was caused by form input, the value that had been output to the browser contained &#8206; i.e.:

<input type="text" name="val" value="&#8206;">

Which on submission was encoded as a char whose ascii value was 1. The TextStream  Write method is only intended to handle ascii out. Simple solution write a bit of code to strip all non printable characters from the input.

  function NonPrintStrip(s)
    dim r, i
    for i = 1 to len(s)
      if asc(mid(s, i, 1)) >= 32 and asc(mid(s, i, 1)) < 128 then
        r = r & mid(s, i, 1)
      end if
    next
    NonPrintStrip = r
  end function

Tags: ,

In the beginning

My initial claim to internet fame is as the author of WsArchie and WsArch32, which were Archie clients.  What is an Archie client I hear you ask…

At the start of 1993 I started accessing the internet via Demon.  The Internet as we know it today did not exist, in particular the web hadn’t really started and when you did get connected to the internet the resources you had access to were:

  • Anonymous FTP servers: These contained tons of free software you could download and use.  If you could find it.
  • Gopher servers: This was the one of the first search tools which for finding stuff on the internet which amalgamated results from other search engines.
  • Archie servers: These catalogued the anonymous ftp servers, so you could either telnet in run a query and get more responses that you could handle or you could use gopher or an archie client.
  • Email: You could send email to all your mates, except none of them had email addresses.

And that was about it.  Initially much access was via command line clients unless you had access to a X terminal.  If you had a PC with Windows 3.1 you were almost certainly connecting to the internet using command line tools.

About this time Trumpet Winsock became available which was the first widely used TCP/IP layer for Windows 3.0.  It was a real revolution and suddenly meant there was a relatively user friendly method of connecting windows PCs to the internet via modems.

I had noticed that there was an X-Windows Archie client and thought it was a pretty cool idea so I decided to write one and WsArchie was born.

Tags: ,

Trojan Archer