WPF - print during a loop - c#

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?

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.

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.

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

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

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

Related

Refreshing SUDOKU solver

I made a SUDOKU solver. When is solve a number it should be written to the screen but it's happen only when the solver is done. Why only then the screen is refreshing when it is done?
ii is the number, jj is the row, ll is the column
private void MainForm_Load(object sender, EventArgs e)
{
...
Thread tr2 = new Thread(adatbszal);
tr2.Start();
}
private void adatbszal()
{
while (fut)
{
Thread.Sleep(10);
if (adat[jj, ll] != 0)
{
SetText(jj, ll, adat[jj, ll].ToString());
}
else
{
SetText(jj, ll, "");
}
}
}
private void SetText(int i, int j, string adat2)
{
if (adatB[i,j].InvokeRequired)
{
valami d = new valami(SetText);
Invoke(d, new object[] { i, j, adat2 });
}
else
{
adatB[i, j].Text = adat2;
}
}
...
Thread th = new Thread(solver);
th.Start();
full project: https://drive.google.com/file/d/1sZTA4Ledfwl3romBY2UTvUoU9MZfY35g/view?usp=sharing
I would suggest putting a breakpoint on Invoke(d, new object[] { i, j, adat2 }); to verify that it is being reached, and when it is, add a breakpoint to if (adatB[i,j].InvokeRequired) before stepping, then make sure that the same parameters are being received to verify that the Invoke is triggering SetText as expected. (Step Into would be simpler, but I'm not sure that would work on an Invoke Line.)
If all that is working, then check if the display updates then adatB[i, j].Text = adat2; executes in the debugger.
I think your code is updating the display like you want it to, but it's not working properly in other regards. In one of my tests, it is just setting the first cell to "2" over and over again, so you won't see any change. You need to verify that the code is doing the right work before you verify that the display is updating properly. I think you may see many problems from having 3 threads running simultaneously which can all update or read the same global values when other threads are assuming these values (like jj) are not changing between lines. I would suggest not sharing data between threads if you are not very experienced with multi-threaded programming. Perhaps you can re-implement this as a single-threaded program that calls an update function periodically instead.

c# coding a timed operation within a for loop

I am writing a piece of code that takes a user through a guided script. The user will have a few seconds to answer before the answer will be displayed.
So far, my code looks like this:
GuidedExercise3 exercise3 = new GuidedExercise3();
string AntonioAnswer = string.Empty; // expected answer
int upperBound = exercise3.Script.Count - 1; // zero-based counting
for(int i = 0; i < upperBound; i += 2)
{
labelInstructions.Text = exercise3.Script[i].TextToSpeak;
AntonioAnswer = exercise3.Script[i+1].TextToSpeak; // answer
SetTimer(AntonioAnswer, txtAntonio); // set timer sending in the answer and the TextBox object.
sysTimer.Start();
}
The odd lines of a List contain the question and the even lines contain the expected answer. My question is how do I display the question for X seconds and then get the user's answer in this WinForms app and then display the answer when the timer elapses, keeping the user from going to the next step of the script but allowing them to answer the question (which is in a Textbox).
I examined this StackOverflow question, but it doesn't match: Implementing a loop using a timer in C#
Here's how I would handle something like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MoveNextQuestion();
timerAnswer.Interval = 5000;
timerAnswer.Start();
}
private string[] _questionsAndAnswers = new[]
{
"What colour is the sky?",
"Blue",
"What do chickens lay?",
"Eggs",
};
private int _currentIndex = -2;
private void timerAnswer_Tick(object sender, EventArgs e)
{
MoveNextQuestion();
}
private void buttonAnswer_Click(object sender, EventArgs e)
{
MoveNextQuestion();
}
private void MoveNextQuestion()
{
_currentIndex += 2;
if (_currentIndex < _questionsAndAnswers.Length)
{
labelQuestion.Text = _questionsAndAnswers[_currentIndex];
}
else
{
timerAnswer.Stop();
}
}
}
I was able to get this working fairly easily with a BackgroundWorker object. See the following article at MSDN for the exact coding. BackgroundWorker Class. In particular they have two examples in the documentation and the first example is sufficient. The BackgroundWorker class is allowing my UI to continue to accept user input while waiting on a timed answer. It displays the correct answer on the RunWorkerComplete event. So the BackgroundWorker's RunAsync gets called in my for loop.
I've run into the additional issue of the BackgroundWorker not returning control to my loop. I'm looking into that problem separately.

Changing The Label Text in Button Click - Understanding For Loop

When I clicked button starts for loop from i=0 and I want to see on the label value of i. However I only see last values of i.
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text = i.ToString();
System.Threading.Thread.Sleep(1000);
}
}
}
when I run the my code I only see on the label ; 2 .
I want to see such a like below;
When the started For loop, i = 0, I must see the 0 on the Label.Text. An then when the i = 1, I must see 1 on the label.Text. And when i = 2, I must see 2 on the Label.Text.
I added Thread.Sleep(1000) however, result didn't change.
Where I am make mistake?
Please help me,
if you help me , I will appreciate you.
Thanks,
You need to append the lbl to get all the values. Right now, it finishes the loop and give you the last value in your label
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text + = i.ToString();
}
}
}
Your problem is that you're doing work on the UI thread while expecting the UI thread to update your form.
While you process your loop, the UI thread is actually executing this code. The UI thread is therefore unable to update the form with the intermediate values you are setting within the loop. Once your code completes, the UI thread is then free to update the form. That's why you see the last value only.
You can see this better if you updated your code to loop ten million times instead of 3. Your form will become unresponsive and will appear locked up. That's because Windows knows your UI thread is locked in an intensive process and is unable to update the UI.
The solution is to use a background thread to run your process and synchronize updates with the UI thread. You'll also have to slow your loop down to see the changes, as others have suggested.
To learn more about how the UI thread works, and how to synchronize background threads with it, read this article (it's about WPF, but it covers the general case).
Each loop you re-write the string. Instead of saying
lblForLoopExample.Text = i.ToString();
You need to add to the string on each iteration. I'd create a variable to hold the value, and so something like this:
string myString = string.Empty;
for(i=0;i < 3; i++)
{
myString += i.ToString() + ", ";
}
lblForLoopExample.Text = myString.substring(0, (myString.length - 1));
substring is just so you don't have that trailing comma. It's kind of dirty code, but it will work.
First of all, your code won't compile as you are missing the exact format of the ToString() method in i.ToString along with the ;
lblForLoopExample.Text = i.ToString();
^^^
As per your code, you should try this:
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text += i.ToString() + ", ";
}

Silverlight C# - How to hold loop progress until an event completes?

I'm trying to loop through the text in a textbox by word in order to spellcheck it. I've split the contents of the textbox into an array, and loop through each word in the array and run it through the spellchecker. When a misspelling is found, I have a popup with a listbox inside it display so that you can choose the correction.
The issue that I'm having, is that it just loops through the whole array and only ends up showing the last correction that needs to be done.
How do I pause the loop so that it waits for a selection to be made and then resume?
Here's the code for the loop:
foreach(string checkedWord in articleWords)
{
bool success = _spellChecker.CheckWord(checkedWord);
List<string> suggest;
if (!success)
{
suggest = _spellChecker.GetSuggestions(checkedWord);
SpellChecklistBox.Items.Clear();
foreach (string s in suggest)
{
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = s });
}
SpellCheckerPopup.IsOpen = true;
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = " ----------------------" });
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = "Ignore" });
}
}
When the SpellCheckerPopup displays, I have an event trigger in the listbox on SelectionChange.
Basically, I need to pause the loop somehow, and then when the SelectionChange event does it's thing, have the loop resume.
Thanks in advance!
-Sootah
If I'm not misunderstanding, currently you are going to:
(1) Check each word in the loop
(2) Pause the loop when an error is found and pop up a suggestion window
(3) User select a suggestion word and resume the loop
I think it's better and easier if the solution is:
(1) Check the word from the first one
(2) Quit the check method with an error flag, and store the position in a variable, pop up a suggestion window
(3) User selects a suggestion word and when User has confirmed the suggestion(e.g. pressing OK on the suggestion window), start the CheckWordMethod again from the stored position
(4) Until step (2) quits with no error flag, which means all words are correct now (but make sure in the whole progress, users can only modify the words by your suggestion window)
#The Smartest: Your answer lead me in the correct direction; actually ended up learning a new datatype out of it! Never used a Queue before. (Which made it a HELL of a lot simpler than having to track where in the array I was at, as I first figured I thought I'd have to.. :)
Anyway, I'll accept your answer, but here's the code I ended up doing: (The actual replacing of the word in the textbox I've not implemented yet.)
private void btnSpelling_Click(object sender, RoutedEventArgs e)
{
SpellChecklistBox.Items.Clear();
string[] articleWordsArray = txtArticle.Text.Split(' ');
foreach (string word in articleWordsArray)
{
articleWords.Enqueue(word);
}
CorrectWord();
}
private void SpellChecklistBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SpellCheckerPopup.IsOpen = false;
}
private void SpellCheckerPopup_Closed(object sender, EventArgs e)
{
CorrectWord();
SpellChecklistBox.Items.Clear();
}
Queue<string> articleWords = new Queue<string>();
private void CorrectWord()
{
if (articleWords.Count() > 0)
{
string checkedWord = articleWords.Dequeue();
bool success = _spellChecker.CheckWord(checkedWord);
List<string> suggest;
if (!success)
{
suggest = _spellChecker.GetSuggestions(checkedWord);
foreach (string s in suggest)
{
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = s });
}
SpellCheckerPopup.IsOpen = true;
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = " ----------------------" });
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = "Ignore" });
SpellCheckerPopup.IsOpen = true;
}
}
}
It's all pretty straight forward courtesy of the Queue datatype. When the spelling button gets clicked it loads the TextBox into an array, and then I loop through the array to enqueue the items into the articleWords queue, after which it calls CorrectWord(). CorrectWord() then loads the relevant list after dequeueing from articleWords, and on the PopUp. Closed event it clears the ListBox and calls CorrectWord() which will keep bringing back the PopUp until there are no more words to be corrected. :)

Slide object using For Loop (C#)

I did quite a bit of searching around and didn't find anything of much help.
Is it possible to "slide" or "move" using C#, an object from one Location to another using a simple For loop?
Thank you
I would suggest you rather use a Timer. There are other options, but this will be the simplist if you want to avoid threading issues etc.
Using a straight for loop will require that you pump the message queue using Application.DoEvents() to ensure that windows has the opportunity to actually render the updated control otherwise the for loop would run to completion without updating the UI and the control will appear to jump from the source location to the target location.
Here is a QAD sample for animating a button in the Y direction when clicked. This code assumes you put a timer control on the form called animationTimer.
private void button1_Click(object sender, EventArgs e)
{
if (!animationTimer.Enabled)
{
animationTimer.Interval = 10;
animationTimer.Start();
}
}
private int _animateDirection = 1;
private void animationTimer_Tick(object sender, EventArgs e)
{
button1.Location = new Point(button1.Location.X, button1.Location.Y + _animateDirection);
if (button1.Location.Y == 0 || button1.Location.Y == 100)
{
animationTimer.Stop();
_animateDirection *= -1; // reverse the direction
}
}
Assuming that the object you're talking about is some kind of Control you could just change the Location property of it.
So something like this:
for(int i = 0; i < 100; i++)
{
ctrl.Location.X += i;
}
Should work I think.

Categories

Resources