user377419
user377419

Reputation: 4989

Deleting previously written lines in Console

Is there anyway to delete certain parts of a console window using the (Left,Top) coordinates used with Console.SetCursorPosition()?

Could you make a custom method for it?

Upvotes: 6

Views: 4690

Answers (3)

howdoicode
howdoicode

Reputation: 961

I want to add something for anyone using .NET 9 (C# 13) or later.

You can use ANSI Escape Codes, particularly the Erase Function -- ESC[0K = erase from cursor to end of line -- within a loop

Since C# 13, you can use the \e as a character literal escape sequence

Example (sexy) function:

static void ClearLines(int begin, int end) {

    for (int i = begin; i < end; i++) {

        Console.SetCursorPosition(0, i);
        // \e = new ESCAPE character in C# 13 (.NET 9):
        //     https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#new-escape-sequence
        // ESC[0K = erase from cursor to end of line:
        //    https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#erase-functions
        Console.Write("\e[0K");

    }

}

Example usage

// Save cursor position somewhere in your code
(int leftBegin, int topBegin) = Console.GetCursorPosition();

// Your code here

// Left ending cursor position is not needed, so use a discard variable
(int _, int topEnd) = Console.GetCursorPosition();

// The sexy function
ClearLines(topBegin, topEnd);

// Reset cursor position to the desired saved position
Console.SetCursorPosition(leftBegin, topBegin);

Upvotes: 0

Chris Taylor
Chris Taylor

Reputation: 53699

If performance is a concern you can directly manipulate the console buffer. Unfortuantely this will require some interop, here is an example that I have adapted from a previous answer I gave. This will clear an area of the console buffer very quickly. It is a bit lengthy because of the interop, but if you wrap this nicely into a console helper class you can extend it to do all kinds of high performance console output.

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication1
{
  class Program
  {
    [STAThread]
    static void Main(string[] args)
    {
      Console.SetCursorPosition(0, 0);
      for (int x = 0; x < 80 * 25; ++x)
      {
        Console.Write("A");
      }
      Console.SetCursorPosition(0, 0);

      Console.ReadKey(true);

      ClearArea(1, 1, 78, 23);

      Console.ReadKey();
    }

    static void ClearArea(short left, short top, short width, short height)
    {
      ClearArea(left, top, width, height, new CharInfo() { Char = new CharUnion() { AsciiChar = 32 } });
    }

    static void ClearArea(short left, short top, short width, short height, CharInfo charAttr)
    {
      using (SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
      {
        if (!h.IsInvalid)
        {
          CharInfo[] buf = new CharInfo[width * height];
          for (int i = 0; i < buf.Length; ++i)
          {
            buf[i] = charAttr;
          }

          SmallRect rect = new SmallRect() { Left = left, Top = top, Right = (short)(left + width), Bottom = (short)(top + height) };
          WriteConsoleOutput(h, buf,
            new Coord() { X = width, Y = height },
            new Coord() { X = 0, Y = 0 },
            ref rect);
        }
      }
    }

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern SafeFileHandle CreateFile(
        string fileName,
        [MarshalAs(UnmanagedType.U4)] uint fileAccess,
        [MarshalAs(UnmanagedType.U4)] uint fileShare,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        [MarshalAs(UnmanagedType.U4)] int flags,
        IntPtr template);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteConsoleOutput(
      SafeFileHandle hConsoleOutput,
      CharInfo[] lpBuffer,
      Coord dwBufferSize,
      Coord dwBufferCoord,
      ref SmallRect lpWriteRegion);

    [StructLayout(LayoutKind.Sequential)]
    public struct Coord
    {
      public short X;
      public short Y;

      public Coord(short X, short Y)
      {
        this.X = X;
        this.Y = Y;
      }
    };

    [StructLayout(LayoutKind.Explicit)]
    public struct CharUnion
    {
      [FieldOffset(0)]
      public char UnicodeChar;
      [FieldOffset(0)]
      public byte AsciiChar;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct CharInfo
    {
      [FieldOffset(0)]
      public CharUnion Char;
      [FieldOffset(2)]
      public short Attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SmallRect
    {
      public short Left;
      public short Top;
      public short Right;
      public short Bottom;
    }   
  }
}

Upvotes: 5

Jon Skeet
Jon Skeet

Reputation: 1499770

Silky's comment is the right answer:

  • Set an appropriate background colour
  • Loop for each line you wish to clear part of:
    • Set the cursor position to left hand side
    • Write out a string of spaces of the right width

For example:

public static void ClearArea(int top, int left, int height, int width) 
{
    ConsoleColor colorBefore = Console.BackgroundColor;
    try
    {
        Console.BackgroundColor = ConsoleColor.Black;
        string spaces = new string(' ', width);
        for (int i = 0; i < height; i++)
        {
            Console.SetCursorPosition(left, top + i);
            Console.Write(spaces);
        }
    }
    finally
    {
        Console.BackgroundColor = colorBefore;
    }
}

Note that this will restore the background colour, but not the previous cursor location.

Upvotes: 7

Related Questions