I'm working on a project using Unity 4.6.1 and I'm experiencing a weird issue: I have a bool variable which is initially set to false, and I'm trying to set it to true in the Start() method. First of all, it happens that the function in the same script gets called before the Start() method from another script (which should not be happening?) and secondly, the line ready = true; in Start() seems to be having no effect. Here's the relevant code:
public int ready = 0;
void Start()
{
texts = new List<string>(3);
texts.Add("");
texts.Add("");
texts.Add("");
text1.text = texts[0];
text2.text = texts[1];
text3.text = texts[2];
Debug.Log("Setting ready to true");
ready = true;
}
public void showText(string text)
{
Debug.Log("Ready=" + ready);
if (!ready)
return;
texts.Insert(2, texts[1]);
texts.Insert(1, texts[0]);
texts.Insert(0, text);
}
The log output for a run is:
16:07:03.563: Setting ready to true
16:07:24.831: Ready=0
Why is this happening?!
You made a simple mistake. You declared the ready variable as int instead of bool.
Change public int ready = 0;
to
public bool ready = false;
Also, since ready is public, make sure that it is not set to false in the Editor. If you are NOT sure about this, make ready a private variable. So, replace public with private. My first answer should solve your problem.
Before posting the question I decided to try using an int variable instead of a boolean and I forgot the change it back, which is why the int declaration is a mistake in the question above. It seems the Start() or Awake() method was not called at all because I instantiated the class using the new operator. I made the class extend MonoBehaviour, inserted an empty GameObject as a parent for the Text objects and did the following:
GameObject canvas = GameObject.Find("canvas");
uiController = canvas.AddComponent<UIText>();
The code in Awake() was changed to:
GameObject canvas = GameObject.Find("canvas");
text1 = canvas.transform.FindChild("Text1").GetComponent<Text>();
text2 = canvas.transform.FindChild("Text2").GetComponent<Text>();
text3 = canvas.transform.FindChild("Text3").GetComponent<Text>();
texts = new List<string>(3);
texts.Add("");
texts.Add("");
texts.Add("");
text1.text = texts[0];
text2.text = texts[1];
text3.text = texts[2];
Related
I am a beginner in Unity and I am currently making a simple game. I have a problem managing the flow of my minigame. The minigame is simply finding an object, when I found and tap on the item name below will be shaded or marked or there can be animation just to indicate that the item is found.
What I want to do is to get the name of the objects that need to be found and set them randomly in the three (3) item names below. like every time this minigame opens the names of the items are randomly placed in the 3 texts. And when the item is found the name below will be marked or shaded or anything that will indicate it is found, but for now I will just set it inactive for easier indication. How can I properly do this whole process?
The objects inside the scene are button for them to have onCLick() events
Correction: the term choices are wrong because they just display the name of the items that you need to find, just in case you get confused with the term choice in my minigame. I will fix it.
Here is the visuals for the minigame:
The script I currently have for when the objects was clicked:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
[SerializeField] Button pillowBtn, pcBtn, lampBtn;
// [SerializeField] TextMeshProUGUI choice1, choice2, choice3;
// FROM THE SOLUTION
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] List<TextMeshProUGUI> textBoxes;
// METHOD NAMES IS TEMPORARY
public void pillowClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void desktopClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void lampClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
}
You asked for a lot and I hope I understood your intention.
first, if you want to randomly choose an amount of game objects from your game, I think the best way to do it is by adding the refernece of all the game objects you want to choose from radomly inside a list of game objects and then randomly take a game object of the list and make it a child of another game object I call "fatherGoTranform" on my code like that:
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] float numOfGO = 4;
[SerializeField] Transform fatherGoTranform;
void Start()
{
for(int i=0;i<numOfGO;i++)
{
int index = Random.Range(0, gameObjectWanted.Count-1);
GameObject currentGO = gameObjectWanted[index ];
currentGO.transform.parent = fatherGoTranform;
gameObjectWanted.RemoveAt(index);
}
}
and then to click on a game object and the do with what you want try this:
void Update()
{
//Check for mouse click
if (Input.GetMouseButtonDown(0))
{
RaycastHit raycastHit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out raycastHit, 100f))
{
if (raycastHit.transform != null)
{
//Our custom method.
CurrentClickedGameObject(raycastHit.transform.gameObject);
}
}
}
}
I have not checked the code so if there is an error tell me and I will fix it
This is the solution that worked for my problem. I randomly placed numbers to the list according to childCount and every index indicate the index of the text that I want to put them on which I get as a Transform child.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
// [SerializeField] Button pillowBtn, pcBtn, lampBtn;
[SerializeField] GameObject choices, v1, finishPanel;
// RANDOM NUMBER HOLDER FOR CHECKING
private int randomNumber, foundCount;
// RANDOMIZED NUMBER LIST HOLDER
public List<int> RndmList = new List<int>();
private void Awake()
{
foundCount = 0;
RndmList = new List<int>(new int[v1.transform.childCount]);
for (int i = 0; i < v1.transform.childCount; i++)
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
while (RndmList.Contains(randomNumber))
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
}
RndmList[i] = randomNumber;
// Debug.Log(v1.transform.GetChild(randomNumber-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text = v1.transform.GetChild(randomNumber - 1).name;
}
}
public void objectFound()
{
string objectName = EventSystem.current.currentSelectedGameObject.name;
Debug.Log("you found the " + objectName);
for (int i = 0; i < choices.transform.childCount; i++)
{
if (objectName == choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text)
{
// Debug.Log(i);
// choices.transform.GetChild(i).gameObject.SetActive(false);
// choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
if(RndmList.Contains(i+1))
{
// Debug.Log(i);
// Debug.Log(v1.transform.GetChild(RndmList[i]-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
v1.transform.GetChild(RndmList[i]-1).GetComponent<Button>().enabled = false;
foundCount++;
}
}
}
if(foundCount == v1.transform.childCount)
{
finishPanel.SetActive(true);
}
}
}
i have a class in a cs file:
public class ThreadData
{
private int index;
public ThreadData(int index)
{
this.index = index;
}
public static ThreadDataOutput DoWork(ThreadDataInput input)
{
return new ThreadDataOutput();
}
}
now, i have c++ code that tries to init a new task and to us the above function:
int numOfThread = 2;
array<Task^>^ taskArr = gcnew array<Task^>(numOfThread);
for (int i = 0; i < numOfThread; i++)
{
ThreadData^ td = gcnew ThreadData(i);
ThreadDataInput^ input = gcnew ThreadDataInput(i);
Task<ThreadDataOutput^>^ task = gcnew Task<ThreadDataOutput^>(td->DoWork, input);
taskArr[i] = task;
taskArr[i]->Start();
}
Task::WaitAll(taskArr, 300 * 1000);
the following code return 2 errors at compile time:
can't take address of 'ThreadData::DoWork' unless creating delegate instance
cannot convert argument 1 from 'AmadeusWS::ThreadDataOutput ^(__clrcall *)(AmadeusWS::ThreadDataInput ^)' to 'System::Func ^
i also tried to declare a delegate like this in the cs file:
public static Func<ThreadDataInput, ThreadDataOutput> DoWork2 = delegate(ThreadDataInput taskDataInput)
{
return new ThreadDataOutput();
};
but i don't know how to call it from the c++\cli code
can anyone assist me to understand how to define cli delegate that can take parametr ?
thanks
In order to create a delegate instance in C++/CLI, you need to construct it explicitly, and specify the object that it will be called on separately from the class & method to be called.
gcnew Func<TInput, TOutput>(theObject, &TheClass::MethodToInvoke)
Note that the method to be called is specified in the C++ style.
Substituting that in to your task creation, I believe this statement will work for you:
Task<ThreadDataOutput^>^ task = gcnew Task<ThreadDataOutput^>(
gcnew Func<ThreadDataInput^, ThreadDataOutput^>(td, &ThreadData::DoWork),
input);
Edit
In the code you posted in your comment, you missed the object to invoke the delegate on.
gcnew Func<Object^, Object^>(td, &ThreadData::DoWork)
^^
I am running into some strange behavior in the backgroundworker class that leads me to believe that I don't fully understand how it works. I assumed that the following code sections were more or less equal except for some extra features that the BackgroundWorker implements (like progress reporting, etc.):
section 1:
void StartSeparateThread(){
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
//Execute some code asynchronous to the thread that owns the function
//StartSeparateThread() but synchronous to itself.
var SendCommand = "SomeCommandToSend";
var toWaitFor = new List<string>(){"Various","Possible","Outputs to wait for"};
var SecondsToWait = 30;
//this calls a function that sends the command over the NetworkStream and waits
//for various responses.
var Result=SendAndWaitFor(SendCommand,toWaitFor,SecondsToWait);
}
Section 2:
void StartSeparateThread(){
Thread pollThread = new Thread(new ThreadStart(DoStuff));
pollThread.Start();
}
void DoStuff(object sender, DoWorkEventArgs e)
{
//Execute some code asynchronous to the thread that owns the function
//StartSeparateThread() but synchronous to itself.
var SendCommand = "SomeCommandToSend";
var toWaitFor = new List<string>(){"Various","Possible","Outputs to wait for"};
var SecondsToWait = 30;
//this calls a function that sends the command over the NetworkStream and waits
//for various responses.
var Result=SendAndWaitFor(SendCommand,toWaitFor,SecondsToWait);
}
I was using Section 1 to run some code that sent a string over a networkstream and waited for a desired response string, capturing all output during that time. I wrote a function to do this that would return the networkstream output, the index of the the sent string, as well as the index of the desired response string. I was seeing some strange behavior with this so I changed the function to only return when both the send string and the output string were found, and that the index of the found string was greater than the index of the sent string. It would otherwise loop forever (just for testing). I would find that the function would indeed return but that the index of both strings were -1 and the output string was null or sometimes filled with the expected output of the previous call. If I were to make a guess about what was happening, it would be that external functions called from within the bw_DoWork() function are run asynchronously to the thread that owns the bw_DoWork() function. As a result, since my SendAndWaitFor() function was called multiple times in succession. the second call would be run before the first call finished, overwriting the results of the first call after they were returned but before they could be evaluated. This seems to make sense because the first call would always run correctly and successive calls would show the strange behavior described above but it seems counter intuitive to how the BackgroundWorker class should behave. Also If I were to break within the SendAndWaitFor function, things would behave properly. This again leads me to believe there is some multi-threading going on within the bwDoWork function itself.
When I change the code in the first section above to the code of the second section, things work entirely as expected. So, can anyone who understands the BackgroundWorker class explain what could be going on? Below are some related functions that may be relevant.
Thanks!
public Dictionary<string, string> SendAndWaitFor(string sendString, List<string> toWaitFor, int seconds)
{
var toReturn = new Dictionary<string, string>();
var data = new List<byte>();
var enc = new ASCIIEncoding();
var output = "";
var FoundString = "";
//wait for current buffer to clear
output = this.SynchronousRead();
while(!string.IsNullOrEmpty(output)){
output = SynchronousRead();
}
//output should be null at this point and the buffer should be clear.
//send the desired data
this.write(enc.GetBytes(sendString));
//look for all desired strings until timeout is reached
int sendIndex=-1;
int foundIndex = -1;
output += SynchronousRead();
for (DateTime start = DateTime.Now; DateTime.Now - start < new TimeSpan(0, 0, seconds); )
{
//wait for a short period to allow the buffer to fill with new data
Thread.Sleep(300);
//read the buffer and add it to the output
output += SynchronousRead();
foreach (var s in toWaitFor)
{
sendIndex = output.IndexOf(sendString);
foundIndex = output.LastIndexOf(s);
if (foundIndex>sendIndex)
{
toReturn["sendIndex"] = sendIndex.ToString();
toReturn["foundIndex"] = sendIndex.ToString();
toReturn["Output"] = output;
toReturn["FoundString"] = s;
return toReturn;
}
}
}
//Set this to loop infinitely while debuging to make sure the function was only
//returning above
while(true){
}
toReturn["sendIndex"]="";
toReturn["foundIndex"]="";
toReturn["Output"] =output;
toReturn["FoundString"] = "";
return toReturn;
}
public void write(byte[] toWrite)
{
var enc = new ASCIIEncoding();
var writeString = enc.GetString(toWrite);
var ns = connection.GetStream();
ns.Write(toWrite, 0, toWrite.Length);
}
public string SynchronousRead()
{
string toReturn = "";
ASCIIEncoding enc = new ASCIIEncoding();
var ns = connection.GetStream();
var sb = new StringBuilder();
while (ns.DataAvailable)
{
var buffer = new byte[4096];
var numberOfBytesRead = ns.Read(buffer, 0, buffer.Length);
sb.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, numberOfBytesRead));
toReturn += sb.ToString();
}
return toReturn;
}
All data to be used by a background worker should be passed in through the DoWorkEventArgs and nothing should be pulled off of the class (or GUI interface).
In looking at your code I could not identify where the property(?) connnection was being created. My guess is that connection is created on a different thread, or may be pulling read information, maybe from a GUI(?) and either one of those could cause problems.
I suggest that you create the connection instance in the dowork event and not pull an existing one off of a different thread. Also verify that the data connection works with does not access any info off of a GUI, but its info is passed in as its made.
I discuss an issue with the Background worker on my blog C# WPF: Linq Fails in BackgroundWorker DoWork Event which might show you where the issue lies in your code.
i have a problem with this exception.
I'm trying to make a function to display data from database on a table, every keystroke i made at jtextfield. So its like the table automatically refreshed with new data everytime i type in the jtextfield.
Here's the code :
First i have this variable :
private Statement stmt;
List<Barang> dataSBarang =new LinkedList();
boolean searchBarang=true;
Searching sBarang;
And this is how i call the function :
private void inputkodeTFMouseClicked(java.awt.event.MouseEvent evt){
sBarang = new Searching( stmt, dataSBarang, modelDetail, tabelDetailBarang, inputkodeTF, searchBarang);
sBarang.start();
}
And this is the Searching Object
public class Searching extends Thread{
private Statement stmt;
private List<Barang> dataBarang;
private JTable jTabelBarang;
private JTextField tf;
private boolean cari;
private DefaultTableModel modelBarang;
public Searching(Statement stmt, List<Barang> dataBarang, DefaultTableModel tm, JTable jTabelBarang, JTextField tf, boolean cari){
this.stmt=stmt;
this.dataBarang=dataBarang;
this.modelBarang=tm;
this.jTabelBarang=jTabelBarang;
this.tf=tf;
this.cari=cari;
}
#Override
public void run(){
String temp="";
while(cari==true){
//System.out.println("jalan");
try{
String masukan = tf.getText();
System.out.println(masukan);
if(!masukan.equals("")&&!masukan.equals(temp)){
clearTableBarang();
//System.out.println("Mencari "+ masukan);
ResultSet rs = stmt.executeQuery("select kode_barang, nama_barang, jumlah_stok, " +
"minimal_stok, harga_jual, deskripsi_barang from BARANG WHERE (kode_barang LIKE " +
"'"+masukan+"%')");
System.out.println(rs);
while(rs.next()){
String kode_barang = rs.getString ("kode_barang");
String nama_barang = rs.getString ("nama_barang");
int jumlah_stok = rs.getInt("jumlah_stok");
int minimal_stok = rs.getInt("minimal_stok");
int harga_jual = rs.getInt("harga_jual");
String deskripsi_barang = rs.getString ("deskripsi_barang");
//System.out.println(kode_barang+" "+deskripsi_barang);
dataBarang.add(new Barang(kode_barang,nama_barang,jumlah_stok,minimal_stok,harga_jual,deskripsi_barang));
((DefaultTableModel) jTabelBarang.getModel()).insertRow(jTabelBarang.getRowCount(), new Object[]{kode_barang, nama_barang, jumlah_stok, minimal_stok, harga_jual, deskripsi_barang});
}
temp = masukan;
}
else if(masukan.equals("")&&!masukan.equals(temp)) {
clearTableBarang();
showTableBarang();
temp = masukan;
}
} catch(SQLException s){s.printStackTrace();}
catch(ArrayIndexOutOfBoundsException s){s.printStackTrace();}
try {
sleep(500);
} catch(InterruptedException e){}
}
}
public void clearTableBarang(){
int numrows = modelBarang.getRowCount();
for(int i = numrows - 1; i >=0; i--){
modelBarang.removeRow(i);
}
dataBarang.clear();
}
public void showTableBarang(){
try{
ResultSet rs = stmt.executeQuery("select kode_barang, nama_barang, jumlah_stok, minimal_stok, harga_jual, deskripsi_barang from barang");
while(rs.next()){
String kode_barang = rs.getString ("kode_barang");
String nama_barang = rs.getString ("nama_barang");
int jumlah_stok = rs.getInt("jumlah_stok");
int minimal_stok = rs.getInt("minimal_stok");
int harga_jual = rs.getInt("harga_jual");
String deskripsi_barang = rs.getString ("deskripsi_barang");
//System.out.println(kode_barang+" "+deskripsi_barang);
dataBarang.add(new Barang(kode_barang,nama_barang,jumlah_stok,minimal_stok,harga_jual,deskripsi_barang));
((DefaultTableModel)jTabelBarang.getModel()).insertRow(jTabelBarang.getRowCount(), new Object[]{kode_barang, nama_barang, jumlah_stok, minimal_stok, harga_jual, deskripsi_barang});
}
} catch(SQLException s){s.printStackTrace();}
}
public void delay(){
try {
sleep(1000000000);
} catch(InterruptedException e){}
}
}
This is the error :
Exception in thread "Thread-4" java.lang.NullPointerException
at mypkg.Searching.run(Searching.java:47)
FYI : Line 47 is pointing to
ResultSet rs = stmt.executeQuery("select kode_barang, nama_barang, jumlah_stok, " +
"minimal_stok, harga_jual, deskripsi_barang from BARANG WHERE (kode_barang LIKE " +
"'"+masukan+"%')");
Please help me solve the problem. Thank you very much. :D
NullPointerExceptions are the most easy ones to fix with a debugger. Just place a breakpoint on that line and see what is null.
If the line you posted is correct, you do not even need a debugger since the only thing that can throw the exception is stmt which will be null.
Note:
It is a good thing to run your DB query in a separate Thread to avoid blocking the UI. However, in your case you are updating the UI from that Thread which is not allowed and will cause weird issues. All Swing components must be accessed on the Event Dispatch Thread (EDT). Consult the Swing concurrency tutorial for more information
I do hope you are not starting up a separate Thread on each keystroke in the textfield as you indicated in your question. Looking at the code in the Thread, you remove all elements from the table and then re-add rows. So if a users types in 5 characters at a normal typing speed, you will launch 5 threads which most likely run all at the same time (since a DB connection might not be that fast if your network is lagging). That means that with your current code 5 Threads are, at the same time, removing the table model and adding rows. Even if you put all the Swing code on the EDT (see my first point), you still end up with 5 threads posting runnables on the EDT messing with your table model. I do not know what the resulting table model will be, but probably not what you want
Clearly stmt is null.
You declare it here:
private Statement stmt;
List<Barang> dataSBarang =new LinkedList();
boolean searchBarang=true;
Searching sBarang;
But I don't see any initialization code anywhere, so most likely it remains null for this method call:
private void inputkodeTFMouseClicked(java.awt.event.MouseEvent evt){
sBarang = new Searching( stmt, dataSBarang, modelDetail, tabelDetailBarang, inputkodeTF, searchBarang);
sBarang.start();
}
Using C# 4 in a Windows console application that continually reports progress how can I make the "redraw" of the screen more fluid?
I'd like to do one of the following:
- Have it only "redraw" the part of the screen that's changing (the progress portion) and leave the rest as is.
- "Redraw" the whole screen but not have it flicker.
Currently I re-write all the text (application name, etc.). Like this:
Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0"), total.ToString("N0"), (count / (decimal)total).ToString("P2")));
Which causes a lot of flickering.
Try Console.SetCursorPosition. More details here: How can I update the current line in a C# Windows Console App?
static void Main(string[] args)
{
Console.SetCursorPosition(0, 0);
Console.Write("################################");
for (int row = 1; row < 10; row++)
{
Console.SetCursorPosition(0, row);
Console.Write("# #");
}
Console.SetCursorPosition(0, 10);
Console.Write("################################");
int data = 1;
System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
clock.Start();
while (true)
{
data++;
Console.SetCursorPosition(1, 2);
Console.Write("Current Value: " + data.ToString());
Console.SetCursorPosition(1, 3);
Console.Write("Running Time: " + clock.Elapsed.TotalSeconds.ToString());
Thread.Sleep(1000);
}
Console.ReadKey();
}
I know this question is a bit old but I found if you set Console.CursorVisible = false then the flickering stops as well.
Here's a simple working demo that shows multi-line usage without flickering. It shows the current time and a random string every second.
private static void StatusUpdate()
{
var whiteSpace = new StringBuilder();
whiteSpace.Append(' ', 10);
var random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var randomWord = new string(Enumerable.Repeat(chars, random.Next(10)).Select(s => s[random.Next(s.Length)]).ToArray());
while (true)
{
Console.SetCursorPosition(0, 0);
var sb = new StringBuilder();
sb.AppendLine($"Program Status:{whiteSpace}");
sb.AppendLine("-------------------------------");
sb.AppendLine($"Last Updated: {DateTime.Now}{whiteSpace}");
sb.AppendLine($"Random Word: {randomWord}{whiteSpace}");
sb.AppendLine("-------------------------------");
Console.Write(sb);
Thread.Sleep(1000);
}
}
The above example assumes your console window is blank to start. If not, make sure to use Console.Clear() first.
Technical Note:
SetCursorPosition(0,0) places the cursor back to the top (0,0) so the next call to Console.Write will start from line 0, char 0. Note, it doesn't delete the previous content before writing. As an example, if you write "asdf" over a previous line such as "0123456", you'll end up with something like "asdf456" on that line. For that reason, we use a whiteSpace variable to ensure any lingering characters from the previous line are overwritten with blank spaces. Adjust the length of the whiteSpace variable to meet your needs. You only need the whiteSpace variable for lines that change.
Personal Note:
For my purposes, I wanted to show the applications current status (once a second) along with a bunch of other status information and I wanted to avoid any annoying flickering that can happen when you use Console.Clear(). In my application, I run my status updates behind a separate thread so it constantly provides updates even though I have numerous other threads and long running tasks going at the same time.
Credits:
Thanks to previous posters and dtb for the random string generator used in the demo.
How can I generate random alphanumeric strings in C#?
You could try to hack something together using the core libraries.
Rather than waste your time for sub-standard results, I would check out this C# port of the ncurses library (which is a library used for formatting console output):
Curses Sharp
I think you can use \r in Windows console to return the beginning of a line.
You could also use SetCursorPosition.
I would recommend the following extension methods. They allow you to use a StringBuilder to refresh the console view without any flicker, and also tidies up any residual characters on each line
The Problem: The following demo demonstrates using a standard StringBuilder, where updating lines that are shorter than the previously written line get jumbled up. It does this by writing a short string, then a long string on a loop:
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLine("Short msg");
else
sb.AppendLine("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
The Solution: By using the extension method provided below, the issue is resolved
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLineEx("Short msg");
else
sb.AppendLineEx("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
Extension Methods:
public static class StringBuilderExtensions
{
/// <summary>
/// Allows StrinbBuilder callers to append a line and blank out the remaining characters for the length of the console buffer width
/// </summary>
public static void AppendLineEx(this StringBuilder c, string msg)
{
// Append the actual line
c.Append(msg);
// Add blanking chars for the rest of the buffer
c.Append(' ', Console.BufferWidth - msg.Length - 1);
// Finish the line
c.Append(Environment.NewLine);
}
/// <summary>
/// Combines two StringBuilders using AppendLineEx
/// </summary>
public static void AppendEx(this StringBuilder c, StringBuilder toAdd)
{
foreach (var line in toAdd.ReadLines())
{
c.AppendLineEx(line);
}
}
/// <summary>
/// Hides the console cursor, resets its position and writes out the string builder
/// </summary>
public static void UpdateConsole(this StringBuilder c)
{
// Ensure the cursor is hidden
if (Console.CursorVisible) Console.CursorVisible = false;
// Reset the cursor position to the top of the console and write out the string builder
Console.SetCursorPosition(0, 0);
Console.WriteLine(c);
}
}
I actually had this issue so I made a quick simple method to try and eliminate this.
static void Clear(string text, int x, int y)
{
char[] textChars = text.ToCharArray();
string newText = "";
//Converts the string you just wrote into a blank string
foreach(char c in textChars)
{
text = text.Replace(c, ' ');
}
newText = text;
//Sets the cursor position
Console.SetCursorPosition(x, y);
//Writes the blank string over the old string
Console.WriteLine(newText);
//Resets cursor position
Console.SetCursorPosition(0, 0);
}
It actually worked surprisingly well and I hope it may work for you!
Naive approach but for simple applications is working:
protected string clearBuffer = null; // Clear this if window size changes
protected void ClearConsole()
{
if (clearBuffer == null)
{
var line = "".PadLeft(Console.WindowWidth, ' ');
var lines = new StringBuilder();
for (var i = 0; i < Console.WindowHeight; i++)
{
lines.AppendLine(line);
}
clearBuffer = lines.ToString();
}
Console.SetCursorPosition(0, 0);
Console.Write(clearBuffer);
Console.SetCursorPosition(0, 0);
}
Console.SetCursorPosition(0, 0); //Instead of Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0")