C #에서 Windows 콘솔 앱을 빌드 할 때 현재 줄을 연장하거나 새 줄로 갈 필요없이 콘솔에 쓸 수 있습니까? 예를 들어, 프로세스가 완료되는 정도를 나타내는 백분율을 표시하려면 커서와 동일한 행의 값을 업데이트하고 각 백분율을 새 행에 넣지 않아도됩니다.
"표준"C # 콘솔 앱으로이 작업을 수행 할 수 있습니까?
C #에서 Windows 콘솔 앱을 빌드 할 때 현재 줄을 연장하거나 새 줄로 갈 필요없이 콘솔에 쓸 수 있습니까? 예를 들어, 프로세스가 완료되는 정도를 나타내는 백분율을 표시하려면 커서와 동일한 행의 값을 업데이트하고 각 백분율을 새 행에 넣지 않아도됩니다.
"표준"C # 콘솔 앱으로이 작업을 수행 할 수 있습니까?
답변:
"\r"
콘솔 에만 인쇄 하면 커서가 현재 줄의 시작 부분으로 돌아가서 다시 쓸 수 있습니다. 트릭을 수행해야합니다.
for(int i = 0; i < 100; ++i)
{
Console.Write("\r{0}% ", i);
}
숫자 뒤에 약간의 공백이있어 이전에 있던 것이 지워 졌는지 확인하십시오.
또한 줄 끝에 "\ n"을 추가하고 싶지 않기 때문에 Write()
대신 사용 WriteLine()
하십시오.
PadRight
(패딩되지 않은 문자열 또는 길이를 먼저 저장하십시오).
Console.SetCursorPosition
커서의 위치를 설정 한 다음 현재 위치에 쓸 수 있습니다 .
static void Main(string[] args)
{
var spin = new ConsoleSpinner();
Console.Write("Working....");
while (true)
{
spin.Turn();
}
}
public class ConsoleSpinner
{
int counter;
public void Turn()
{
counter++;
switch (counter % 4)
{
case 0: Console.Write("/"); counter = 0; break;
case 1: Console.Write("-"); break;
case 2: Console.Write("\\"); break;
case 3: Console.Write("|"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
}
}
기존 출력을 새 출력 또는 공백으로 덮어 써야합니다.
업데이트 :이 예제는 커서를 한 문자 만 뒤로 이동시키는 것으로 비판되었으므로 설명을 위해 추가합니다 SetCursorPosition
. 콘솔 창에서 커서를 원하는 위치로 설정할 수 있습니다.
Console.SetCursorPosition(0, Console.CursorTop);
커서를 현재 줄의 시작 부분으로 설정하거나 Console.CursorLeft = 0
직접 사용할 수 있습니다 .
SetCursorPosition
(또는 CursorLeft
)를 사용하면 줄의 시작 부분에 쓰지 않고 창에서 위로 움직이는 등의 유연성이 향상되므로 출력과 같은 일반적인 접근 방식입니다. 사용자 정의 진행률 표시 줄 또는 ASCII 그래픽.
지금까지이 작업을 수행하는 방법에 대한 세 가지 경쟁 대안이 있습니다.
Console.Write("\r{0} ", value); // Option 1: carriage return
Console.Write("\b\b\b\b\b{0}", value); // Option 2: backspace
{ // Option 3 in two parts:
Console.SetCursorPosition(0, Console.CursorTop); // - Move cursor
Console.Write(value); // - Rewrite
}
나는 항상 Console.CursorLeft = 0
세 번째 옵션의 변형 인을 사용 했으므로 테스트를하기로 결정했습니다. 내가 사용한 코드는 다음과 같습니다.
public static void CursorTest()
{
int testsize = 1000000;
Console.WriteLine("Testing cursor position");
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < testsize; i++)
{
Console.Write("\rCounting: {0} ", i);
}
sw.Stop();
Console.WriteLine("\nTime using \\r: {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
int top = Console.CursorTop;
for (int i = 0; i < testsize; i++)
{
Console.SetCursorPosition(0, top);
Console.Write("Counting: {0} ", i);
}
sw.Stop();
Console.WriteLine("\nTime using CursorLeft: {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
Console.Write("Counting: ");
for (int i = 0; i < testsize; i++)
{
Console.Write("\b\b\b\b\b\b\b\b{0,8}", i);
}
sw.Stop();
Console.WriteLine("\nTime using \\b: {0}", sw.ElapsedMilliseconds);
}
내 컴퓨터에서 다음과 같은 결과가 나타납니다.
또한 SetCursorPosition
대안 중 하나에서 관찰하지 않은 눈에 띄는 깜박임이 발생했습니다. 그래서, 도덕적이다 사용 백 스페이스 또는 캐리지 리턴이 가능 하고, 나에게 가르쳐 주셔서 감사 SO,이 작업을 수행하는 빠른 방법!
업데이트 : 의견에서 Joel은 SetCursorPosition은 이동 거리에 대해 일정하지만 다른 방법은 선형이라고 제안합니다. 추가 테스트에서이 경우에 해당하는지 확인 하지만, 일정한 시간과 느린 속도는 여전히 느립니다. 필자의 테스트에서 긴 백 스페이스 문자열을 콘솔에 쓰는 것이 60 자 정도가 될 때까지 SetCursorPosition보다 빠릅니다. 백 스페이스 빠르게 라인의보다 짧은 60 자 (정도) 부분을 교체하고, 그래서 그리고 그것은 내가 \ 연구를 통해 \ B의 내 최초의 승인에 의해 서가는거야, 그래서 깜박임을하지 않습니다 SetCursorPosition
.
당신은 사용할 수 있습니다 \ B 현재 행에 백업 문자의 특정 번호를 (백 스페이스) 이스케이프 시퀀스를. 이것은 현재 위치 만 이동 시키며 문자를 제거하지는 않습니다.
예를 들면 다음과 같습니다.
string line="";
for(int i=0; i<100; i++)
{
string backup=new string('\b',line.Length);
Console.Write(backup);
line=string.Format("{0}%",i);
Console.Write(line);
}
여기서 line 은 콘솔에 쓸 백분율 줄입니다. 트릭은 이전 출력에 대해 올바른 수의 \ b 문자 를 생성하는 것 입니다.
\ r 접근 방식에 비해이 방법 의 장점은 백분율 출력이 줄의 시작 부분에없는 경우에도 작동한다는 것입니다.
\r
이러한 시나리오에 사용됩니다.
\r
는 캐리지 리턴을 나타냅니다. 즉 커서가 줄의 시작으로 돌아갑니다.
이것이 Windows가 \n\r
새로운 라인 마커로 사용 하는 이유 입니다.
\n
줄 아래로 이동하고 줄 \r
의 시작 부분으로 돌아갑니다.
나는 단지 divo의 ConsoleSpinner
수업 과 놀아야했다 . 내 간결한 곳은 어디에도 없지만 그 클래스의 사용자가 자신의 while(true)
루프 를 작성해야한다는 것은 나와 함께 잘 앉아 있지 않았습니다 . 나는 다음과 같은 경험을 위해 촬영 중이다.
static void Main(string[] args)
{
Console.Write("Working....");
ConsoleSpinner spin = new ConsoleSpinner();
spin.Start();
// Do some work...
spin.Stop();
}
그리고 나는 아래 코드로 그것을 깨달았습니다. 내 Start()
메소드가 차단되는 것을 원하지 않기 때문에 사용자가 while(spinFlag)
유사 루프를 작성하는 것에 대해 걱정할 필요가 없으며 여러 스피너를 동시에 처리하기 위해 별도의 스레드를 생성해야합니다. 제사. 그리고 이것은 코드가 훨씬 더 복잡해야 함을 의미합니다.
또한, 나는 그렇게 많은 멀티 스레딩을하지 않았으므로 미묘한 버그 또는 세 가지를 남겼을 가능성이 있습니다 (아마도). 그러나 지금까지는 잘 작동하는 것 같습니다.
public class ConsoleSpinner : IDisposable
{
public ConsoleSpinner()
{
CursorLeft = Console.CursorLeft;
CursorTop = Console.CursorTop;
}
public ConsoleSpinner(bool start)
: this()
{
if (start) Start();
}
public void Start()
{
// prevent two conflicting Start() calls ot the same instance
lock (instanceLocker)
{
if (!running )
{
running = true;
turner = new Thread(Turn);
turner.Start();
}
}
}
public void StartHere()
{
SetPosition();
Start();
}
public void Stop()
{
lock (instanceLocker)
{
if (!running) return;
running = false;
if (! turner.Join(250))
turner.Abort();
}
}
public void SetPosition()
{
SetPosition(Console.CursorLeft, Console.CursorTop);
}
public void SetPosition(int left, int top)
{
bool wasRunning;
//prevent other start/stops during move
lock (instanceLocker)
{
wasRunning = running;
Stop();
CursorLeft = left;
CursorTop = top;
if (wasRunning) Start();
}
}
public bool IsSpinning { get { return running;} }
/* --- PRIVATE --- */
private int counter=-1;
private Thread turner;
private bool running = false;
private int rate = 100;
private int CursorLeft;
private int CursorTop;
private Object instanceLocker = new Object();
private static Object console = new Object();
private void Turn()
{
while (running)
{
counter++;
// prevent two instances from overlapping cursor position updates
// weird things can still happen if the main ui thread moves the cursor during an update and context switch
lock (console)
{
int OldLeft = Console.CursorLeft;
int OldTop = Console.CursorTop;
Console.SetCursorPosition(CursorLeft, CursorTop);
switch (counter)
{
case 0: Console.Write("/"); break;
case 1: Console.Write("-"); break;
case 2: Console.Write("\\"); break;
case 3: Console.Write("|"); counter = -1; break;
}
Console.SetCursorPosition(OldLeft, OldTop);
}
Thread.Sleep(rate);
}
lock (console)
{ // clean up
int OldLeft = Console.CursorLeft;
int OldTop = Console.CursorTop;
Console.SetCursorPosition(CursorLeft, CursorTop);
Console.Write(' ');
Console.SetCursorPosition(OldLeft, OldTop);
}
}
public void Dispose()
{
Stop();
}
}
끝에 새 줄 (\ n)을 사용하는 대신 (암시 적 또는 명시 적으로) 줄의 시작 부분에 Carrage Return (\ r)을 명시 적으로 사용하면 원하는 것을 얻을 수 있습니다. 예를 들면 다음과 같습니다.
void demoPercentDone() {
for(int i = 0; i < 100; i++) {
System.Console.Write( "\rProcessing {0}%...", i );
System.Threading.Thread.Sleep( 1000 );
}
System.Console.WriteLine();
}
MSDN의 콘솔 문서에서 :
Out 또는 Error 속성의 TextWriter.NewLine 속성을 다른 줄 종료 문자열로 설정하여이 문제를 해결할 수 있습니다. 예를 들어 C # 문인 Console.Error.NewLine = "\ r \ n \ r \ n";은 표준 오류 출력 스트림에 대한 줄 종결 문자열을 두 개의 캐리지 리턴 및 줄 바꿈 시퀀스로 설정합니다. 그런 다음 C # 문에서와 같이 오류 출력 스트림 개체의 WriteLine 메서드를 명시 적으로 호출 할 수 있습니다. Console.Error.WriteLine ();
그래서-나는 이것을했다 :
Console.Out.Newline = String.Empty;
그런 다음 출력을 직접 제어 할 수 있습니다.
Console.WriteLine("Starting item 1:");
Item1();
Console.WriteLine("OK.\nStarting Item2:");
거기에 도착하는 또 다른 방법.
파일 생성을 멋지게 보이게하려면이 기능이 작동합니다.
int num = 1;
var spin = new ConsoleSpinner();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("");
while (true)
{
spin.Turn();
Console.Write("\r{0} Generating Files ", num);
num++;
}
그리고 이것은 아래의 답변에서 얻은 방법이며 수정했습니다.
public class ConsoleSpinner
{
int counter;
public void Turn()
{
counter++;
switch (counter % 4)
{
case 0: Console.Write("."); counter = 0; break;
case 1: Console.Write(".."); break;
case 2: Console.Write("..."); break;
case 3: Console.Write("...."); break;
case 4: Console.Write("\r"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(23, Console.CursorTop);
}
}
한 줄을 업데이트하려고하지만 정보가 너무 길어서 한 줄에 표시 할 수없는 경우 새 줄이 필요할 수 있습니다. 이 문제가 발생했으며 아래에서이 문제를 해결할 수 있습니다.
public class DumpOutPutInforInSameLine
{
//content show in how many lines
int TotalLine = 0;
//start cursor line
int cursorTop = 0;
// use to set character number show in one line
int OneLineCharNum = 75;
public void DumpInformation(string content)
{
OutPutInSameLine(content);
SetBackSpace();
}
static void backspace(int n)
{
for (var i = 0; i < n; ++i)
Console.Write("\b \b");
}
public void SetBackSpace()
{
if (TotalLine == 0)
{
backspace(OneLineCharNum);
}
else
{
TotalLine--;
while (TotalLine >= 0)
{
backspace(OneLineCharNum);
TotalLine--;
if (TotalLine >= 0)
{
Console.SetCursorPosition(OneLineCharNum, cursorTop + TotalLine);
}
}
}
}
private void OutPutInSameLine(string content)
{
//Console.WriteLine(TotalNum);
cursorTop = Console.CursorTop;
TotalLine = content.Length / OneLineCharNum;
if (content.Length % OneLineCharNum > 0)
{
TotalLine++;
}
if (TotalLine == 0)
{
Console.Write("{0}", content);
return;
}
int i = 0;
while (i < TotalLine)
{
int cNum = i * OneLineCharNum;
if (i < TotalLine - 1)
{
Console.WriteLine("{0}", content.Substring(cNum, OneLineCharNum));
}
else
{
Console.Write("{0}", content.Substring(cNum, content.Length - cNum));
}
i++;
}
}
}
class Program
{
static void Main(string[] args)
{
DumpOutPutInforInSameLine outPutInSameLine = new DumpOutPutInforInSameLine();
outPutInSameLine.DumpInformation("");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
//need several lines
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbb");
}
}
나는 vb.net에서 동일한 솔루션을 찾고 있었고 이것을 찾았고 훌륭합니다.
그러나 @JohnOdom은 이전 공백이 현재 공백보다 큰 경우 공백을 처리하는 더 좋은 방법을 제안했습니다.
vb.net에서 함수를 만들고 누군가 도움을받을 수 있다고 생각했습니다.
여기 내 코드가 있습니다 :
Private Sub sPrintStatus(strTextToPrint As String, Optional boolIsNewLine As Boolean = False)
REM intLastLength is declared as public variable on global scope like below
REM intLastLength As Integer
If boolIsNewLine = True Then
intLastLength = 0
End If
If intLastLength > strTextToPrint.Length Then
Console.Write(Convert.ToChar(13) & strTextToPrint.PadRight(strTextToPrint.Length + (intLastLength - strTextToPrint.Length), Convert.ToChar(" ")))
Else
Console.Write(Convert.ToChar(13) & strTextToPrint)
End If
intLastLength = strTextToPrint.Length
End Sub
Static intLastLength As Integer
.
필자가 작성한 솔루션이 속도에 맞게 최적화 될 수 있는지 확인하기 위해 이것을 검색하고있었습니다. 내가 원하는 것은 현재 줄을 업데이트하는 것이 아니라 카운트 다운 타이머였습니다. 여기에 내가 생각해 낸 것이 있습니다. 누군가에게 유용 할 수 있습니다
int sleepTime = 5 * 60; // 5 minutes
for (int secondsRemaining = sleepTime; secondsRemaining > 0; secondsRemaining --)
{
double minutesPrecise = secondsRemaining / 60;
double minutesRounded = Math.Round(minutesPrecise, 0);
int seconds = Convert.ToInt32((minutesRounded * 60) - secondsRemaining);
Console.Write($"\rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
Thread.Sleep(1000);
}
Console.WriteLine("");
@ E.Lahu Solution에서 영감을 받아 백분율로 막대 진행률을 구현합니다.
public class ConsoleSpinner
{
private int _counter;
public void Turn(Color color, int max, string prefix = "Completed", string symbol = "■",int position = 0)
{
Console.SetCursorPosition(0, position);
Console.Write($"{prefix} {ComputeSpinner(_counter, max, symbol)}", color);
_counter = _counter == max ? 0 : _counter + 1;
}
public string ComputeSpinner(int nmb, int max, string symbol)
{
var spinner = new StringBuilder();
if (nmb == 0)
return "\r ";
spinner.Append($"[{nmb}%] [");
for (var i = 0; i < max; i++)
{
spinner.Append(i < nmb ? symbol : ".");
}
spinner.Append("]");
return spinner.ToString();
}
}
public static void Main(string[] args)
{
var progressBar= new ConsoleSpinner();
for (int i = 0; i < 1000; i++)
{
progressBar.Turn(Color.Aqua,100);
Thread.Sleep(1000);
}
}
다음은 s soosh와 0xA3의 답변입니다. 스피너를 업데이트하는 동안 사용자 메시지로 콘솔을 업데이트 할 수 있으며 경과 시간 표시기도 있습니다.
public class ConsoleSpiner : IDisposable
{
private static readonly string INDICATOR = "/-\\|";
private static readonly string MASK = "\r{0} {1:c} {2}";
int counter;
Timer timer;
string message;
public ConsoleSpiner() {
counter = 0;
timer = new Timer(200);
timer.Elapsed += TimerTick;
}
public void Start() {
timer.Start();
}
public void Stop() {
timer.Stop();
counter = 0;
}
public string Message {
get { return message; }
set { message = value; }
}
private void TimerTick(object sender, ElapsedEventArgs e) {
Turn();
}
private void Turn() {
counter++;
var elapsed = TimeSpan.FromMilliseconds(counter * 200);
Console.Write(MASK, INDICATOR[counter % 4], elapsed, this.Message);
}
public void Dispose() {
Stop();
timer.Elapsed -= TimerTick;
this.timer.Dispose();
}
}
사용법은 다음과 같습니다.
class Program
{
static void Main(string[] args)
{
using (var spinner = new ConsoleSpiner())
{
spinner.Start();
spinner.Message = "About to do some heavy staff :-)"
DoWork();
spinner.Message = "Now processing other staff".
OtherWork();
spinner.Stop();
}
Console.WriteLine("COMPLETED!!!!!\nPress any key to exit.");
}
}