I'm building this application in Visual Studio 2010 using C#.
Basically there are 2 files, form1.cs (which is the windows form) and program.cs (where all the logic lies).
//form1.cs
public partial class Form1 : Form
{
//runButton_click function
}
//program.cs
class Program
{
static void Main()
{
while(blah-condition)
{
//some calculation
Console.WriteLine("Progress " + percent + "% completed.");
}
}
}
There is a Run button and a blank textbox.
When the user hits the Run button, program.cs will perform some task and constantly printing out the progress using Console.WriteLine() onto the console (command prompt).
Question: How can I print to the textbox on form1 instead of printing into command prompt?
I will need to print the progress constantly without any user action.
Thanks in advance!
By the way, it doesn't have to be a textbox, it can be a label or something else that can take text. I chose textbox because it makes more sense to me.
Start by creating a new TextWriter that is capable of writing to a textbox. It only needs to override the Write method that accepts a char, but that would be ungodly inefficient, so it's better to overwrite at least the method with a string.
public class ControlWriter : TextWriter
{
private Control textbox;
public ControlWriter(Control textbox)
{
this.textbox = textbox;
}
public override void Write(char value)
{
textbox.Text += value;
}
public override void Write(string value)
{
textbox.Text += value;
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
In this case I've had it just accept a Control, which could be a Textbox, a Label, or whatever. If you want to change it to just a Label that would be fine.
Then just set the console output to a new instance of this writer, pointing to some textbox or label:
Console.SetOut(new ControlWriter(textbox1));
If you want the output to be written to the console as well as to the textbox we can use this class to create a writer that will write to several writers:
public class MultiTextWriter : TextWriter
{
private IEnumerable<TextWriter> writers;
public MultiTextWriter(IEnumerable<TextWriter> writers)
{
this.writers = writers.ToList();
}
public MultiTextWriter(params TextWriter[] writers)
{
this.writers = writers;
}
public override void Write(char value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Write(string value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Flush()
{
foreach (var writer in writers)
writer.Flush();
}
public override void Close()
{
foreach (var writer in writers)
writer.Close();
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
Then using this we can do:
Console.SetOut(new MultiTextWriter(new ControlWriter(textbox1), Console.Out));
I use sth like this for a listbox:
public class ListBoxWriter : TextWriter //this class redirects console.writeline to debug listbox
{
private readonly ListBox _list;
private StringBuilder _content = new StringBuilder();
public ListBoxWriter(ListBox list)
{
_list = list;
}
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
public override void Write(char value)
{
base.Write(value);
_content.Append(value);
if (value != '\n') return;
if (_list.InvokeRequired)
{
try
{
_list.Invoke(new MethodInvoker(() => _list.Items.Add(_content.ToString())));
_list.Invoke(new MethodInvoker(() => _list.SelectedIndex = _list.Items.Count - 1));
_list.Invoke(new MethodInvoker(() => _list.SelectedIndex = -1));
}
catch (ObjectDisposedException ex)
{
Console.WriteLine(Resources.Exception_raised + " (" + ex.Message + "): " + ex);
}
}
else
{
_list.Items.Add(_content.ToString());
_list.SelectedIndex = _list.Items.Count - 1;
_list.SelectedIndex = -1;
}
_content = new StringBuilder();
}
}
and in my main application:
_writer = new ListBoxWriter(DebugWin); // DebugWin is the name og my listbox
Console.SetOut(_writer);
Don't know if it will work, but you could try to redirect console output.
Use Console.SetOut() and create derivative of TextWriter which overrides WriteLine() method and simply assign method parameter to your TextBox.Text
Should work.
put textbox on the form ( multiple lines enabled) or text area then you can do in your loop
txtOutput.Text += "Progress " + percent + "% completed." + Environment.NewLine();
Related
I have console application that can turn winform with richtextbox. I want to redirect console and that richtextbox to each other. So whenever I write to them text will copy to another. My problem is when I'm waiting for ReadLine. I would like to react to first ReadLine. This is my code:
class ConsoleFormReDirectWriter : TextWriter
{
TextWriter t;
RichTextBox r;
public ConsoleFormReDirectWriter(TextWriter TextWriter, RichTextBox FormOut)
{
t = TextWriter;
r = FormOut;
}
public override void Write(char value)
{
t.Write(value);
RichTextBoxExtensions.AppendText(r, value +"", Color.White);
}
public override void WriteLine(string line)
{
t.WriteLine(line);
RichTextBoxExtensions.AppendText(r, line+"\n", Color.White);
}
public override Encoding Encoding
{
get { return Encoding.Default; }
}
}
class ConsoleFormReDirectReader : TextReader
{
Queue<string> ReadLineQ = new Queue<string>();
public void AddToReadLineQueue(string s)
{
ReadLineQ.Enqueue(s);
}
public override string ReadLine()
{
string line = "";
while (true)
{
if (ReadLineQ.Count != 0) { line = ReadLineQ.Dequeue(); break; }
}
return line;
}
}
Then im handling press enter event on richtext box and apending queue by currentLine. I dont know how to make something similar with console.
or is there a better method of doing same?
PS: i can make new thread that will be asking console for readline in infinite loop, and when readline return something than i can append the queue. But that seems very unefective.
After a while i found semi solution. I used Console.readline with timeout and
now I have my Readline:
public override string ReadLine()
{
bool toConsole=false, toUI=false;
string line = "";
while (true)
{
if (ReadLineQ.Count != 0)
{ line = ReadLineQ.Dequeue();
toConsole = true;
break;
}
try
{
line = DelayReader.ReadLine(50);
}
catch(TimeoutException) { continue; }
toUI = true;
break;
}
if (toConsole) t.WriteLine(line);
if (toUI) RichTextBoxExtensions.AppendText(r, line + "\n", Color.White);
return line;
}
And Console.Readline with delay i foun here: ReadLine(delay)
I'm building this application in Visual Studio 2010 using C#.
Basically there are 2 files, form1.cs (which is the windows form) and program.cs (where all the logic lies).
//form1.cs
public partial class Form1 : Form
{
//runButton_click function
}
//program.cs
class Program
{
static void Main()
{
while(blah-condition)
{
//some calculation
Console.WriteLine("Progress " + percent + "% completed.");
}
}
}
There is a Run button and a blank textbox.
When the user hits the Run button, program.cs will perform some task and constantly printing out the progress using Console.WriteLine() onto the console (command prompt).
Question: How can I print to the textbox on form1 instead of printing into command prompt?
I will need to print the progress constantly without any user action.
Thanks in advance!
By the way, it doesn't have to be a textbox, it can be a label or something else that can take text. I chose textbox because it makes more sense to me.
Start by creating a new TextWriter that is capable of writing to a textbox. It only needs to override the Write method that accepts a char, but that would be ungodly inefficient, so it's better to overwrite at least the method with a string.
public class ControlWriter : TextWriter
{
private Control textbox;
public ControlWriter(Control textbox)
{
this.textbox = textbox;
}
public override void Write(char value)
{
textbox.Text += value;
}
public override void Write(string value)
{
textbox.Text += value;
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
In this case I've had it just accept a Control, which could be a Textbox, a Label, or whatever. If you want to change it to just a Label that would be fine.
Then just set the console output to a new instance of this writer, pointing to some textbox or label:
Console.SetOut(new ControlWriter(textbox1));
If you want the output to be written to the console as well as to the textbox we can use this class to create a writer that will write to several writers:
public class MultiTextWriter : TextWriter
{
private IEnumerable<TextWriter> writers;
public MultiTextWriter(IEnumerable<TextWriter> writers)
{
this.writers = writers.ToList();
}
public MultiTextWriter(params TextWriter[] writers)
{
this.writers = writers;
}
public override void Write(char value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Write(string value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Flush()
{
foreach (var writer in writers)
writer.Flush();
}
public override void Close()
{
foreach (var writer in writers)
writer.Close();
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
Then using this we can do:
Console.SetOut(new MultiTextWriter(new ControlWriter(textbox1), Console.Out));
I use sth like this for a listbox:
public class ListBoxWriter : TextWriter //this class redirects console.writeline to debug listbox
{
private readonly ListBox _list;
private StringBuilder _content = new StringBuilder();
public ListBoxWriter(ListBox list)
{
_list = list;
}
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
public override void Write(char value)
{
base.Write(value);
_content.Append(value);
if (value != '\n') return;
if (_list.InvokeRequired)
{
try
{
_list.Invoke(new MethodInvoker(() => _list.Items.Add(_content.ToString())));
_list.Invoke(new MethodInvoker(() => _list.SelectedIndex = _list.Items.Count - 1));
_list.Invoke(new MethodInvoker(() => _list.SelectedIndex = -1));
}
catch (ObjectDisposedException ex)
{
Console.WriteLine(Resources.Exception_raised + " (" + ex.Message + "): " + ex);
}
}
else
{
_list.Items.Add(_content.ToString());
_list.SelectedIndex = _list.Items.Count - 1;
_list.SelectedIndex = -1;
}
_content = new StringBuilder();
}
}
and in my main application:
_writer = new ListBoxWriter(DebugWin); // DebugWin is the name og my listbox
Console.SetOut(_writer);
Don't know if it will work, but you could try to redirect console output.
Use Console.SetOut() and create derivative of TextWriter which overrides WriteLine() method and simply assign method parameter to your TextBox.Text
Should work.
put textbox on the form ( multiple lines enabled) or text area then you can do in your loop
txtOutput.Text += "Progress " + percent + "% completed." + Environment.NewLine();
I would monitor data received on a Serial port with my pc and a Arduino.
On the arduino, the sketch send thorugt the USB the string "aabb" evry 300ms.
With pc I want listen, and in real time print the string in a control (Textbox). To do that, I create a new thread which listen in a Loop what arrives in Serial port, and when it happens it write by a Invoke the string in textbox. The procedures works if I deploy in the form's class but if I use a external class it doesn't. To explain better the matter, I paste the code of the class
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
public TextBox textLog;
public string LastString;
public bool thrIsAlive;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[IODescriptionAttribute("ControlInvokeRequiredDescr")]
public bool InvokeRequired { get; private set; }
//DISPOSE
public void Dispose()
{
this.Dispose();
}
//SET Textobox LOG
public void SetLogTxtB (TextBox txt)
{
textLog = txt;
}
//PORTE DISPONIBILI
public string[] Available_Ports()
{
return SerialPort.GetPortNames();
}
//COSTRUTTORI
public SerialPortManager(string portname, int baudrate,bool InitializeConn)
{
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (InitializeConn == true) Serial.Open();
}
public SerialPortManager()
{
}
//SETTA I PARAMETRI E INIZIALIZZA LA CONNESSIONE
public void SetConnectionParam(string portname, int baudrate, bool initializeConn)
{
Serial.Close();
Serial.Dispose();
Serial = new SerialPort();
Serial.BaudRate = baudrate;
Serial.PortName = portname;
if (initializeConn == true) Serial.Open();
}
//ASYNC LISTENER
public void AsyncListener()
{
thrIsAlive = true;
thr = new Thread(ThreadReader);
thr.Start();
}
//PROCEDURA PER APPEND
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
textLog.Text += value;
}
private void Invoke(Action<string> action, params object[] v)
{
throw new NotImplementedException();
}
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
AppendTextBox(LastString + "\n");
}
}
}
In the form I write three rows
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB(textBox1);
PortMan.AsyncListener();
If I try to run program it returns the error " cross-thread operation not allowed". Now, while I posting this ask, I decide to do a last try and change the method AppendTextBox to :
public void AppendTextBox(string value)
{
if (textLog.InvokeRequired)
{
try
{
textLog.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textLog.Text += value;
}
And It Finally works. Now ascertained the power of Stackoverflow that solved the problem before posting, I would know why my code works. Thank you
In SerialPortManager you must use delegate instead windows control.
class SerialPortManager
{
public SerialPort Serial = new SerialPort();
private Thread thr;
private string Log;
//public TextBox textLog;
public Action<string> textLog;
.....
Crete in you form simply method:
public void SetTextBoxText(string value)
{
if (textBox1.InvokeRequired)
{
try
{
textBox1.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
catch (ObjectDisposedException)
{
thrIsAlive = false;
}
}
textBox1.Text += value;
}
Set delegate to PortMan:
SerialPortManager PortMan = new Driver_Arduin.SerialPortManager("COM3", 9600,true);
PortMan.SetLogTxtB=new Action<string>(SetTextBoxText);
PortMan.AsyncListener();
If need output log to TextBox of PortMan call textLog delegate.
void ThreadReader()
{
while (thrIsAlive)
{
string temp = Serial.ReadLine();
LastString = temp;
Log += LastString + "\n";
//AppendTextBox(LastString + "\n");
textLog(LastString + "\n");
}
}
Apart from that your Invoke method in SerialPortManager should throw NotImplementedException the problem is that you define your own InvokeRequired/Invoke.
You need to use these methods provided by a WinForms control such that it knows whether your code is running inside the thread (UI thread) that created the control and how it can change context to this thread.
Actually it seems you may use your SerialPortManager but make use of InvokeRequired/Invoke of textLog like you're already doing in AppendTextBox.
BTW, if (initializeConn == true) is rather useless - if (initializeConn) is sufficient.
I have an external dll written in C# and I studied from the assemblies documentation that it writes its debug messages to the Console using Console.WriteLine.
this DLL writes to console during my interaction with the UI of the Application, so i don't make DLL calls directly, but i would capture all console output , so i think i got to intialize in form load , then get that captured text later.
I would like to redirect all the output to a string variable.
I tried Console.SetOut, but its use to redirect to string is not easy.
As it seems like you want to catch the Console output in realtime, I figured out that you might create your own TextWriter implementation that fires an event whenever a Write or WriteLine happens on the Console.
The writer looks like this:
public class ConsoleWriterEventArgs : EventArgs
{
public string Value { get; private set; }
public ConsoleWriterEventArgs(string value)
{
Value = value;
}
}
public class ConsoleWriter : TextWriter
{
public override Encoding Encoding { get { return Encoding.UTF8; } }
public override void Write(string value)
{
if (WriteEvent != null) WriteEvent(this, new ConsoleWriterEventArgs(value));
base.Write(value);
}
public override void WriteLine(string value)
{
if (WriteLineEvent != null) WriteLineEvent(this, new ConsoleWriterEventArgs(value));
base.WriteLine(value);
}
public event EventHandler<ConsoleWriterEventArgs> WriteEvent;
public event EventHandler<ConsoleWriterEventArgs> WriteLineEvent;
}
If it's a WinForm app, you can setup the writer and consume its events in the Program.cs like this:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (var consoleWriter = new ConsoleWriter())
{
consoleWriter.WriteEvent += consoleWriter_WriteEvent;
consoleWriter.WriteLineEvent += consoleWriter_WriteLineEvent;
Console.SetOut(consoleWriter);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
static void consoleWriter_WriteLineEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "WriteLine");
}
static void consoleWriter_WriteEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "Write");
}
It basically amounts to the following:
var originalConsoleOut = Console.Out; // preserve the original stream
using(var writer = new StringWriter())
{
Console.SetOut(writer);
Console.WriteLine("some stuff"); // or make your DLL calls :)
writer.Flush(); // when you're done, make sure everything is written out
var myString = writer.GetStringBuilder().ToString();
}
Console.SetOut(originalConsoleOut); // restore Console.Out
So in your case you'd set this up before making calls to your third-party DLL.
You can also call SetOut with Console.OpenStandardOutput, this will restore the original output stream:
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
Or you can wrap it up in a helper method that takes some code as an argument run it and returns the string that was printed. Notice how we gracefully handle exceptions.
public string RunCodeReturnConsoleOut(Action code)
{
string result;
var originalConsoleOut = Console.Out;
try
{
using (var writer = new StringWriter())
{
Console.SetOut(writer);
code();
writer.Flush();
result = writer.GetStringBuilder().ToString();
}
return result;
}
finally
{
Console.SetOut(originalConsoleOut);
}
}
Using solutions proposed by #Adam Lear and #Carlo V. Dango I created a helper class:
public sealed class RedirectConsole : IDisposable
{
private readonly Action<string> logFunction;
private readonly TextWriter oldOut = Console.Out;
private readonly StringWriter sw = new StringWriter();
public RedirectConsole(Action<string> logFunction)
{
this.logFunction = logFunction;
Console.SetOut(sw);
}
public void Dispose()
{
Console.SetOut(oldOut);
sw.Flush();
logFunction(sw.ToString());
sw.Dispose();
}
}
which can be used in the following way:
public static void MyWrite(string str)
{
// print console output to Log/Socket/File
}
public static void Main()
{
using(var r = new RedirectConsole(MyWrite)) {
Console.WriteLine("Message 1");
Console.WriteLine("Message 2");
}
// After the using section is finished,
// MyWrite will be called once with a string containing all messages,
// which has been written during the using section,
// separated by new line characters
}
I made a control to log messages from different threads to screen. It uses rich text box to display formatted text.
When there are 20 threads which append their messages every 200-250ms the main UI becomes unresponsive for some time and after the messages waiting are processed the UI starts to response again. When the threads are running the moving of the window is not smooth.
Message writing to rich text box is synchronised with locks.
What can you suggest to improve the performance? I'm planning to run 100 threads.
Here is my code. I redirect the console output(s) to it and it logs everything that's going on and displays in formatted form inside a rich text box.
public void RedirectStandardOutput()
{
Console.SetOut(ConsoleStream);
System.Diagnostics.Debug.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
System.Diagnostics.Debug.AutoFlush = true;
}
After the console is redirected all Console.WriteLine("bla bla"); is written to screen.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CoreLib.Parsers;
namespace ConsoleWidget
{
public class ConsoleStream : System.IO.TextWriter
{
private readonly object _textBoxLock = new object();
public RichTextBox TextBox { get; set; }
public List<TextFormat> TextFormatList { get; set; }
public bool AutoClear { get; set; }
public int AutoClearLength { get; set; }
public bool AutoSave { get; set; }
public string AutoSaveDir { get; set; }
public ConsoleStream()
{
TextFormatList = new List<TextFormat>();
}
public void AppendText(string text)
{
if (TextBox == null) return;
var textLength = TextBox.TextLength;
if (AutoClear && textLength > AutoClearLength)
{
if (AutoSave)
{
var dir = string.Format(#"{0}\{1}\{2}", Environment.CurrentDirectory, AutoSaveDir, CoreLib.Extensions.DateTimeExtensions.DateTimeNowDir);
if (!System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
var path = string.Format(#"{0}\{1}.log", dir, CoreLib.Extensions.DateTimeExtensions.GetDateTimeNowFileName);
TextBox.SaveFile(path);
}
TextBox.Clear();
}
TextBox.AppendText(text);
// Format text.
foreach (var textFormat in TextFormatList)
{
int beginIndex;
int length;
if (textFormat.GetFormatProperties(text, out beginIndex, out length))
{
// RichTextBox counts newline "\r\n" which is double char as single char.
// Causes shifting in selection starts. The lines below count the "\r" chars before the beginIndex.
var leftText = text.Substring(0, beginIndex);
var newLineCount = leftText.Count(c => c == '\r');
TextBox.SelectionStart = textLength + beginIndex - newLineCount;
TextBox.SelectionLength = length;
if (!textFormat.Color.IsEmpty)
TextBox.SelectionColor = textFormat.Color;
if (textFormat.Font != null)
TextBox.SelectionFont = textFormat.Font;
}
}
TextBox.ScrollToCaret();
}
public void Clear()
{
lock (_textBoxLock)
{
TextBox.Clear();
}
}
public int TextLength
{
get
{
lock (_textBoxLock)
{
return TextBox.TextLength;
}
}
}
public void SaveAs(string path)
{
lock (_textBoxLock)
{
TextBox.SaveFile(path);
}
}
public override Encoding Encoding
{
get { return Encoding.Default; }
}
public override void Write(string value)
{
if (TextBox == null) return;
var action = (Action)(() => AppendText(value));
lock (_textBoxLock)
{
if (TextBox.InvokeRequired)
TextBox.BeginInvoke(action);
else
action();
}
}
public override void WriteLine()
{
Write(NewLine);
}
public override void WriteLine(string value)
{
Write(value);
WriteLine();
}
}
}
Have them write to a buffer RichTextBox (one that's not actually part of your form) and only append the buffer to the UI RichTextBox every 250 ms or so.
Have your worker threads add their data to some sort of queue/list and then have the main thread add a batch of new data from the store of new data every second/half second (tune to fit your process).
Something basic like this would probably be fine:
public class DataStore<T>{
private object _lock = new object();
private List<T> _data = new List<T>();
public void Add(T data){
lock (_lock){
_data.Add(data);
}
}
public T[] TakeWork(){
T[] result;
lock (_lock){
result= _data.ToArray();
_data.Clear();
}
return result;
}
}
Just create a DataStore and have your work threads use the Add function to add work to be displayed then do
foreach (var s in _dataStore.TakeWork()){
richTextBox.AppendText(s);
}
in a System.Windows.Forms.Timer event. You will probably want to trim the rich text box text as well though since your app will start to slow down if you just keep pumping data in all day....
Maybe you can try thread pool or task instead to manage your threads better.