Simple Tcl/Tk example with crystal

For small GUIs Tcl/Tk works fine with crystal.
-start the wish shell in a subprocess
https://www.tcl.tk/man/tcl/UserCmd/wish.html
-pack your widgets
-get values via subprocess

while mygui.isopen?        # run while window is open
      break if myname.hasdata? # wait for input data from tcl widget
      sleep 1
    end

Full example for a name input widget:

Code:

# written by peter.bauer, pebauer68@gmail.com
# tested on Ubuntu 20.04
# needs tcl wish installed
# see https://www.tcl.tk/man/tcl/UserCmd/wish.html

class Name
  property? hasdata = false
  property firstname
  property lastname

  def initialize(firstname = "", lastname = "")
    @firstname = firstname
    @lastname = lastname
  end
end

class Gui
  property? isopen = false
  property r = IO::FileDescriptor.new(fd = 0)
  property w = IO::FileDescriptor.new(fd = 0)

  # run cmd as a long running sub process in a fiber
  def initialize
    reader, writer = IO.pipe   # writer goes to process input
    reader2, writer2 = IO.pipe # reader2 delivers output
    spawn self.run_long_cmd(reader, writer2)
    @r = reader2
    @w = writer
  end

  def run_long_cmd(proc_stdin, proc_stdout)
      @isopen = true
      Process.run("wish", output: proc_stdout, input: proc_stdin)
      puts "\rWish Process ended"
      @isopen = false
  end

  def close_window(gui)
    close_window = %(wm forget .\n)
    gui.w << close_window
  end

  def set_window_properties(gui)
    properties = {
      "window"          => %(wm resizable . 0 0),
      "title"           => %(wm title . EnterName),
      "label1"          => %(label .l1 -text "Please enter your name"),
      "label2"          => %(label .l2 -text "Name: "),
      "entry1"          => %(entry .e -textvariable name),
      "pack_label1"     => %(pack .l1 -side top -anchor w),
      "pack_entry1"     => %(pack .e -side top -anchor w),
      "pack_label2"     => %(pack .l2 -side left -before .e),
      "bind_send_entry" => %(bind .e <Return> { puts $name }),
    }
    properties.each_value { |prop|
      gui.w << prop << "\n"
    }
  end

  def run_app(mygui, myname, debug = false) # run receive in a fiber
      counter = 0
      while receive = r.gets
        if receive && receive.size > 0 # got some chars
          puts "receive counter: #{counter}" if debug
          l = receive.size
          puts "got: #{l} chars" if debug
          puts receive if l > 0 && debug # write received data from bash to stdout
          if receive.includes?(" ")
            vals = receive.split
            myname.firstname, myname.lastname = vals if vals.size == 2
          else
            myname.firstname = receive
            myname.lastname = ""
          end
          myname.hasdata = true
        end
        sleep 0.1
        counter += 1
      end # end of while
  end
end

class Main
  def self.run
    mygui = Gui.new
    myname = Name.new
    mygui.set_window_properties(mygui)
    spawn mygui.run_app(mygui, myname)
    sleep 0.5

    while mygui.isopen?        # run while window is open
      break if myname.hasdata? # wait for input data from tcl widget
      sleep 1
    end

    puts "GUI closed" unless mygui.isopen?
    mygui.close_window(mygui)
    if myname.hasdata?
      puts "Name entered is: #{myname.firstname} #{myname.lastname} "
    end
  end
end

Main.run
3 Likes

Nice. I would love to see a shard that hides all the calling of wish and provides a native-looking interface to Tk.

1 Like

Thank you for the feedback, I found a way to polish the example and make it 4 lines shorter. Now the example fits into 100 lines of code.
Providing a native-looking oo interface is a good idea but will need a lot of work.