Noctis Noctis - 1 year ago 52
C# Question

Is there an equivalent to SetCursorPosition for a text stream?

when outputting to the console, you can set the specific location of the cursor and write to that (or use other nifty tricks like printing backspaces that will take you back.)

Is there a similar thing that can be done with a stream of text?

Scenario: I need to build a string with

pieces of, text where each might be on a different line and start position (or top and left padding).

Two strings might appear on the same line.

I could build a simple
Dictionary<int, StringBuilder>
and fidget with that, but I'm wondering if there's something like the console functionality for streams of text where you can write to a specific place (row and column).

This is for a text only. No control.

The result might be a string with several new lines, and text appearing at different locations.

Example (where
will be white spaces):

..... txt3....... txt2
................ txt1.

this will be the result of having
at row 3 column (whatever), and
and row 1 with different colum values (where
column <

Answer Source

While waiting for a better answer, here's my solution. Seems to work, been lightly tested, and can be simply pasted into linqpad and run.

void Main()
    m_dict = new SortedDictionary<int, StringBuilder>();

    AddTextAt(1,40, "first");
    AddTextAt(2,40, "xx");
    AddTextAt(0,10, "second");
    AddTextAt(4,5, "third");
    AddTextAt(1,15, "four");


// "global" variable
SortedDictionary<int, StringBuilder> m_dict;

/// <summary>
/// This will emulate writting to the console, where you can set the row/column and put your text there.
/// It's done by having Dictionary(int,StringBuilder) that will use to store our data, and eventually, 
/// when we need the string iterate over it and build our final representation.
/// </summary>
private void AddTextAt(int row, int column, string text)
    StringBuilder sb;

    // NB: The following will initialize the string builder !!
    // Dictionary doesn't have an entry for this row, add it and all the ones before it
    if (!m_dict.TryGetValue(row, out sb))
            int start = m_dict.Keys.Any() ? m_dict.Keys.Last() +1 : 0;
        for (int i = start ; i <= row; i++)
            m_dict.Add(i, null);

    int leftPad = column + text.Length;
    // If dictionary doesn't have a value for this row, just create a StringBuilder with as many
    // columns as left padding, and then the text
    if (sb == null)
        sb = new StringBuilder(text.PadLeft(leftPad));
        m_dict[row] = sb;
    // If it does have a value:
        // If the new string is to be to the "right" of the current text, append with proper padding
        // (column - current string builder length) and the text
        int currrentSbLength = sb.ToString().Length;
        if (column >= currrentSbLength)
            leftPad = column - currrentSbLength + text.Length;
        // otherwise, text goes on the "left", create a new string builder with padding and text, and 
        // append the older one at the end (with proper padding?)
            m_dict[row] = new StringBuilder(  text.PadLeft(leftPad)
                                            + sb.ToString().Substring(leftPad) );

/// <summary>
/// Concatenates all the strings from the private dictionary, to get a representation of the final string.
/// </summary>
private string GetStringFromDictionary()
    var sb = new StringBuilder();
    foreach (var k in m_dict.Keys)
            if (m_dict[k]!=null)
    return sb.ToString();


               four                     first