1

I have a simple issue, but the solution appears to be tricky. I want to print using the WPF control canvas during a loop; but for each iteration, I want to udpate the canvas control.

If I want to print a canvas control in WPF, I can simply call

PrintDialog dialog = new PrintDialog();
dialog.PrintVisual(this.canvas, "");

And it prints as expected to my default printer. Wonderful.

However, if I want to perform this multiple times in a loop and make an update to the canvas during each iteration, only the final iteration of the loop is printed.

    private void methodName()
    {
        for (int i = 0; i < 2; i++)
        {
            updateTextBox(i.ToString());
            PrintDialog dialog = new PrintDialog();
            dialog.PrintVisual(this.canvas, "");
        }
    }

    private void updateTextBox(string text)
    {
        txtTextBox.Text = text;
    }

Any idea what I need to do to ensure that I get 2 print outs, the first with the txtTextBox.Text value of 0, the second time it has the value of 1?

JSW189
  • 6,267
  • 11
  • 44
  • 72
Dave
  • 8,163
  • 11
  • 67
  • 103
  • Is `PrintVisual` an asynchronous function? The MSDN doesn't mention it. – Vlad Jun 14 '12 at 14:30
  • http://msdn.microsoft.com/en-us/library/system.windows.controls.printdialog.printvisual.aspx The print is the last command to be executed, so it only 'picks up' the final iteration as that is the last thing to be performed. This is not desirable for my application. I did try to play with the DispatcherOperation class to change the priority but this didn't help. – Dave Jun 14 '12 at 14:33
  • Well, I fail to see the information about asynchronous behaviour in the documentation. Are you sure this is not a bug? – Vlad Jun 14 '12 at 14:36
  • Forgive me for asking, a bug in my application or within the control? – Dave Jun 14 '12 at 14:37
  • Well, a bug in `PrintVisual`: perhaps it should finish grabbing the information before returning to the caller (your) code. If the asynchronous behaviour is by design, this ought to be mentioned in the documentation. – Vlad Jun 14 '12 at 14:39
  • Try using UpdateLayout or dispatch each iteration of the loop with a priority lower than render. – dowhilefor Jun 14 '12 at 14:44
  • @dowhilefor: although this _may_ help, is there some documentation that _guarantees_ that this will work? Playing with priorities looks always suspicious to me. – Vlad Jun 14 '12 at 14:49
  • @Vlad No there is not, it was just my first idea. I had this issue when building offscreen controls and updating them, so maybe it could help on printing aswell :) But its just an idea, thats why i made it a comment, its worth a try. – dowhilefor Jun 14 '12 at 14:52

5 Answers5

1

I am about to implement something similar in my application and found out that my previous answer wasn't good enough. The problem for me was that although the canvas is updated in each iteration, it has not yet rendered itself before being sent to PrintVisual. It surprises me that you get your final iteration printed, I only got the first one. Anyway, this is how I made it work, basically queueing the print command after the already pending render operation:

for (int i = 0; i < 2; i++)
{
  updateTextBox(i.ToString());
  this.canvas.InvalidateVisual(); // Maybe not needed in your case

  PrintDialog dialog = new PrintDialog();

  this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, (Action)delegate()
  {
     dialog.PrintVisual(this.canvas, "" + i);
  });
}

Yes it's somewhat similar (but not identical) to SalGad's answer and the post you're referring to, but I'm not able to comment that answer, so please try this out, it works for me.

I also had problems with disappearing prints when using empty description for the print jobs, thus the + i. Don't know if that is a generic problem, or just something with my printer setup.

I got the idea from this post, which also mentions an alternative solution using ViewBox.

Community
  • 1
  • 1
ekholm
  • 2,543
  • 18
  • 18
  • I have done something which fixes it but I think your answer may be more elegant. I will test this when I finish work today - I will still publish my answer as I'm interested in others. I really can't thank you enough for your help. – Dave Jun 15 '12 at 07:45
1

OK

I solved it.

I removed all the dispatcher object methods so it runs on a single thread.

To update the canvas, I used the canvas.UpdateLayout() method.

I also ensured that the print had finished before updating the next canvas (the next iteration).

private void methodName()
{
    for (int i = 0; i < 2; i++)
    {
        updateTextBox(i.ToString());

        this.canvas.UpdateLayout();

        PrintDialog dialog = new PrintDialog();
        dialog.PrintVisual(this.canvas, "ABC");

        dialog.PrintQueue.Refresh();

        while (dialog.PrintQueue.NumberOfJobs != 0)
        {
            bool isQueued = false;
            foreach (var job in dialog.PrintQueue.GetPrintJobInfoCollection())
            {
                if (job.Name == "ABC")
                    isQueued = true;
            }

            if (!isQueued)
                break;

            Thread.Sleep(500);
            dialog.PrintQueue.Refresh();
        }
    }
}

private void updateTextBox(string text)
{
    txtTextBox.Text = text;
}

I also could have just done thread.sleep(3000) - this worked as it was enough time to ensure the print job had completed, but it was also a little bit 'hopeful' and I wanted something more secure.

Thank you to everyone for your suggestions.

Dave
  • 8,163
  • 11
  • 67
  • 103
0

If you are going to call PrintVisual multiple times you have to look into PrintDocument and DocumentPaginator.

Sharun
  • 2,030
  • 4
  • 22
  • 36
  • I didn't think I can use PrintVisual and PrintDocument together... Did you mean I need to use PrintDocument instead (sorry if this question is picky but I feel I'm out of my depth here) – Dave Jun 14 '12 at 14:47
  • I can be done and we have a few suggestions on the page - whether it's a neat elegant solution I guess that is up to the programmer but if this works, then it works! – Dave Jun 15 '12 at 07:46
0

Just a guess, but it might be worth trying RenderTargetBitmap to force rendering the canvas in each iteration, and then create an Image with that source, which then can be sent to PrintVisual. See this post for code example:

Printing viewport

Community
  • 1
  • 1
ekholm
  • 2,543
  • 18
  • 18
0

Just taking a shot here, but can you try refreshing the WPF canvas controls at the start of every for loop iteration? Here is the code snippet:

// declare this
public static class ExtensionMethods
{
   private static Action EmptyDelegate = delegate() { };

   public static void Refresh(this UIElement uiElement)
   {
      uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
   }
}

// the loop
for (int i = 0; i < 2; i++)
    {
        updateTextBox(i.ToString());
        PrintDialog dialog = new PrintDialog();
        dialog.PrintVisual(this.canvas, "");
    }
}

// update this method
private void updateTextBox(string text)
{
    txtTextBox.Text = text;
    txtTextBox.Refresh();
    Thread.Sleep(500);
}

I am sourcing this idea from here

SalGad
  • 2,991
  • 2
  • 18
  • 25
  • Hi, no this didn't help - I actually tried simialr - see my original question (which seems to not gotten answered) - http://stackoverflow.com/questions/10902791/c-sharp-printing-with-wpf Thank you for your help though. – Dave Jun 14 '12 at 15:29