0

I would like to put the text from a certain line into a variable. Would I be able to do this using Console.CursorLeft and Console.CursorTop somehow? Is it even possible for a program to know what letter the cursor is currently on? I have a program in which you can use the arrow keys to highlight a certain line, but it requires you to already know what's on the line and repeat it.

This is the program so far which works just so you can get an idea of what I'm trying to do.

open System

Console.BackgroundColor <- ConsoleColor.White
Console.ForegroundColor <- ConsoleColor.Black
printfn "OPTION 1"
Console.BackgroundColor <- ConsoleColor.Black
Console.ForegroundColor <- ConsoleColor.White
printfn "Option 2"
printfn "Option 3"

Console.SetCursorPosition(0, 0)

let mutable exit = false

while not exit do
    let mutable key = Console.ReadKey()
    if key.Key.Equals(ConsoleKey.UpArrow) then
        Console.SetCursorPosition(0, Console.CursorTop - 1)
    if key.Key.Equals(ConsoleKey.DownArrow) then
        Console.SetCursorPosition(0, Console.CursorTop + 1)
    if key.Key.Equals(ConsoleKey.Enter) then
        let selected = Console.CursorTop + 1
        Console.SetCursorPosition(0, 3)
        printfn "You selected %i" (selected)
        exit <- true
    let mutable test = Console.CursorTop
    Console.SetCursorPosition(0, 0)
    printfn "Option 1"
    printfn "Option 2"
    printfn "Option 3"
    if test = 0 then
        Console.SetCursorPosition(0, 0)
        Console.BackgroundColor <- ConsoleColor.White
        Console.ForegroundColor <- ConsoleColor.Black
        printfn "OPTION 1"
    if test = 1 then
        Console.SetCursorPosition(0, 1)
        Console.BackgroundColor <- ConsoleColor.White
        Console.ForegroundColor <- ConsoleColor.Black
        printfn "OPTION 2"
    if test = 2 then
        Console.SetCursorPosition(0, 2)
        Console.BackgroundColor <- ConsoleColor.White
        Console.ForegroundColor <- ConsoleColor.Black
        printfn "OPTION 3"
    Console.BackgroundColor <- ConsoleColor.Black
    Console.ForegroundColor <- ConsoleColor.White
    Console.SetCursorPosition(0, test)

Console.Read() |> ignore
  • Maybe use a custom stream for output that both writes to the console and keeps track of everything written in something like a `StringBuilder`, then process keystrokes against your custom stream's version of the data? – Aaron M. Eshbach Jul 26 '18 at 20:33
  • @AaronM.Eshbach Thats was my first though, but do you think that is the only way to reduce this code? I was really hoping there'd be a way to read what is on the console. –  Jul 26 '18 at 20:34
  • If you're running as Administrator, you could have your process actually spawn a separate process with redirected input/output streams. – Aaron M. Eshbach Jul 26 '18 at 20:37
  • I wouldn't know how to do that, if you want you can put it in an answer. Otherwise I think I'll just do your first suggestion. –  Jul 26 '18 at 20:40
  • I can write it up as an answer, but it will be a couple hours, I'm not at my computer right now. – Aaron M. Eshbach Jul 26 '18 at 21:03
  • @AaronM.Eshbach Thanks, please do. I'll check back later. –  Jul 26 '18 at 21:04
  • Possible duplicate of [Read from location on console C#](https://stackoverflow.com/questions/12355378/read-from-location-on-console-c-sharp) – Jwosty Jul 26 '18 at 21:24
  • @Jwosty Thanks for the link but I'll wait to see Aaron's answer as it is a different solution. –  Jul 26 '18 at 21:33
  • 1
    @Alanay Out of curiosity, why do you want to read the text from a console, rather than maintaining your own data structure that keeps track of what you want to put on the screen? Then you can use this data structure to both print things and to read things. (The only reason I can think of is that you are working with some code you cannot control, but if that's not the case, I would absolutely use a data structure rather than reading from a console.) – Tomas Petricek Jul 27 '18 at 09:22
  • @TomasPetricek I thought it would be a lot easier and less code if I can just read what is on the console. But it looks like a data structure is my only option. –  Jul 27 '18 at 10:14

1 Answers1

1

Given the clarification in the comments, I think there is absolutely no reason for writing the application in a way that would require reading text printed to the console.

If you did that, you would be essentially relying on the console text for maintaining a state of your application. In other words, you are keeping your state in a limited size 2D character array!

A much better way is to use F# types to represent the state. For example, you could define a record that keeps your menu options, selection and a flag whether a selection has been made:

type State = 
  { Options : string list
    Selection : int
    Completed : bool }

Now you can define a function that prints the state:

let printMenu menu = 
  Console.SetCursorPosition(0, 0)
  for i, option in Seq.indexed menu.Options do
    if i = menu.Selection then
      Console.BackgroundColor <- ConsoleColor.White
      Console.ForegroundColor <- ConsoleColor.Black
    printfn "%s" option
    Console.BackgroundColor <- ConsoleColor.Black
    Console.ForegroundColor <- ConsoleColor.Gray

And a function that computes a new state when a key is pressed:

let handleKey (key:ConsoleKeyInfo) state = 
  match key.Key with
  | ConsoleKey.UpArrow -> { state with Selection = state.Selection-1 }
  | ConsoleKey.DownArrow -> { state with Selection = state.Selection+1 }
  | ConsoleKey.Enter -> { state with Completed = true }
  | _ -> state

Finally, you can define an initial state with your menu options. I'll keep your while loop and so this will be a mutable variable:

let mutable state = 
  { Options = ["Option 1"; "Option 2"; "Option 3"] 
    Completed = false
    Selection = 0 }      

The main application logic is now super simple. Just keep looping while state.Completed is false, print the menu, read a key and update the state:

while not state.Completed do
  printMenu state
  let key = Console.ReadKey()
  state <- handleKey key state

Console.SetCursorPosition(0, 3)
printfn "You selected %i" state.Selection

This way of structuring the application is much clearer, because it keeps the state in an explicit data type and also separate from the various functions that operate on it.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553