Racket GUI tab-panel dynamic content - user-interface

I am looking into GUI development with Racket. I would like to create a tab-panel%, with multiple tabs. The documentation says, that a switch of tab does only call a procedure and does not make a content change happen automatically. I think this is quite clever behavior, but I have a problem implementing an initially empty tab-panel, which only gets content (children) when I select one of the tabs.
This is the code I already have:
#lang racket/gui
(require racket/gui/base)
(define nil '())
(define application-frame
(new frame%
[label "Example"]
[width 400]
[height 300]))
(define menu-bar
(new menu-bar%
[parent application-frame]))
(define file-menu
(new menu%
[label "&File"]
[parent menu-bar]))
(new menu%
[label "&Edit"]
[parent menu-bar])
(new menu%
[label "&Help"]
[parent menu-bar])
(new menu-item%
[label "E&xit"]
[parent file-menu]
[callback
(λ (m event)
(exit nil))])
(define tab-panel
(new tab-panel%
[parent application-frame]
[choices '("&Lookup" "&Training")]
[callback
(λ (tp event)
(case (send tp get-item-label (send tp get-selection))
[("&Lookup")
(send tp change-children
(λ (children)
(list lookup-panel)))]
[("&Training")
(send tp change-children
(λ (children)
(list training-panel)))]))]))
(define get-lookup-panel
(lambda (children)
(let
[(lookup-panel (new panel% [parent tab-panel]))]
[(new message%
[parent lookup-panel]
[label "The content of the lookup panel for the lookup tab."])
lookup-panel])))
(define lookup-panel (new panel% [parent tab-panel]))
(define lookup-panel-content
(new message%
[parent lookup-panel]
[label "The content of the lookup panel for the lookup tab."]))
(define training-panel (new panel% [parent tab-panel]))
(define training-panel-content
(new message%
[parent training-panel]
[label "The content of the training panel for the training tab."]))
(define status-message
(new message%
[parent application-frame]
[label "No events so far..."]
[auto-resize #t]))
(send application-frame show #t)
The problem here is, that initially both children of the tab-panel are visible, although (naturally) only one tab is selected. When I change tab, the behavior is corrected by the lambdas inside the case form.
However, I cannot simply give those panels, which I set as children no parent, because racket will tell me, that I need to specify the required initial argument parent. This means they will be initially added to the tab-panel. Is it necessary to create the panels and then again remove them from the tab-panel? That would seem a bit dirty. I think there is probably a better way.
I already tried to dynamically create the panel, as can be seen in the get-lookup-panel procedure, but I couldn't get that to work in the case form.
What is the correct way to implement it?
edit1
I found a way to define a procedure, which can be used in the way I want to use it:
(define (get-lookup-panel4 children)
(define lookup-panel (new panel% [parent tab-panel]))
(define lookup-panel-message (new message% [parent lookup-panel] [label "LOOKUP"]))
(list lookup-panel))
Which can be used as follows:
(define tab-panel
(new tab-panel%
[parent application-frame]
[choices '("&Lookup" "&Training")]
[callback
(λ (tp event)
(case (send tp get-item-label (send tp get-selection))
[("&Lookup")
(send tp change-children get-lookup-panel4)]
[("&Training")
(send tp change-children
(λ (children)
(list training-panel)))]))]))
But I don't understand what the difference between this procedure and the other one with the let expression is and another problem with this approach is, that I cannot afterwards modify the created panel or message, because their scope is the procedure.

Related

Racket GUI Toolkit: How to embed a link

Say we have a frame
(define my-frame (new frame% [parent #f] [label "test"]))
and a message, that should link to google.
(new message%
[parent my-frame]
[label "https://www.google.com"])
(send my-frame show #t)
The above does not work. How does one embed a link in the Racket GUI toolkit?
You can use the button, but I guess that isn't link-like so you don't want it, the following code shows how to use editor clickback to create a link-like text.
(require net/sendurl)
(define f (new frame% [parent #f]
[label "test"]
[width 300]
[height 300]))
(define editor (new text%))
(new editor-canvas% [parent f]
[editor editor])
(define t "https://www.google.com")
(send editor insert t)
(send editor set-clickback 0 (string-length t)
(λ (text start end)
(send-url t)))
(send* f
[show #t]
[center])

GUI component layout in Racket/Scheme

I am using following code for a small GUI program:
#lang racket/gui
(define ff (new frame%
[label "Adjust widths"]
[height 100]
[width 300]))
(new message% [parent ff][label "testing"])
(new text-field% [parent ff][label "tf1"])
(new text-field% [parent ff][label "tf2- a long prompt"])
(new text-field% [parent ff][label "tf3 "])
(new text-field% [parent ff][label "tf4 "])
(send ff show #t)
However, I am not able to get desired layout:
How can I get above layout. I see text-field and other components have min-width and stretchable-width. Which if these is default and which is actual is not clear to me. How can I fix the width of text-field? Should I use table-panel package for this? Thanks for your help.
Edit: I checked https://docs.racket-lang.org/gui/windowing-overview.html?q=gui and also tried different options such as [min-width 50] and [stretchable width #f] but apparently it is not possible to fix the size of text-field to a particular value.
One possibility is to first set the labels to the one of maximum width, then change them to the desired label. This works for me at least.
#lang racket/gui
(define ff (new frame%
[label "Adjust widths"]
[height 100]
[width 300]))
(new message% [parent ff][label "testing"])
(define strings
'("tf1" "tf2- a long prompt" "tf3" "tf4"))
(define str-max (argmax string-length strings))
(define txt-fields
(for/list ([str (in-list strings)])
(new text-field% [parent ff] [label str-max])))
(for ([tf (in-list txt-fields)]
[str (in-list strings)])
(send tf set-label str))
(send ff show #t)
Since fonts may not have a fixed width, you may want to use underscores for the string of maximum length.
Another possibility is to get rid of the text-field's label altogether by passing #f as argument and make your own using a message% for which you can actually control the width with min-width. You may need to put the message and the text-field in a horizontal-panel%.

Non-resizable frame in Racket

I am trying to create a non-resizable frame to give a message:
#lang racket/gui
(define (non_resizable_frame)
(define myframe (new frame%
[label "MyFrame"]
[style (list 'no-resize-border)] ; does not work.
))
(new message% [parent myframe][label "This is just for testing."])
(new message% [parent myframe][label "I am trying to create a non-resizable frame."])
(send myframe show #t))
(non_resizable_frame)
However, the frame still is resizable. Where is the problem and how can I solve this?
Set the stretchable-width and stretchable-height init arguments to #f when constructing the frame.

Text not centering on message% in Racket

I have following simple GUI code:
#lang racket/gui
(define myfr (new frame% [label ""] [width 200] [height 100]))
(define mymsg (new message% [label "See text position here."][parent myfr]))
(define mytf (new text-field% [label "Enter some text here."][parent myfr]))
(define bt (new button% [parent myfr] [label "Reset text"]
[callback (lambda (b e)
(send mymsg set-label
(send mytf get-value)))]))
(send myfr show #t)
Initially, the text position on message% is well centralized. However, when I change the text, it is no more well-centered. Why is this happening and how can this be corrected so that the new text is also well centered?
Add [auto-resize #t] to your message:
(define mymsg (new message% [label "See text position here."] [parent myfr] [auto-resize #t]))

how to align racket GUI text fields and buttons

I am creating a GUI for a racket program where a user inputs a title and a blog and then submits it. This is my code for those fields so far:
(define blogPost%
(class horizontal-panel%
(super-new)
(define titleoutput (new text-field% (label " title")
(min-height 20)
(min-width 200)
(vert-margin 20)
(horiz-margin 10)
(parent this)))
(define output (new text-field% (label "blog")
(style '(multiple))
(min-height 20)
(vert-margin 20)
(min-width 400)
(parent this)))
(define (callback button event)
(define title-new-value (send titleoutput get-value))
(define new-value (send output get-value))
(save title-new-value new-value)
(send output set-value "")
(send titleoutput set-value "")
(send howisit show #t))
(define button (new button% (label "Submit")
(vert-margin 0)
(horiz-margin 10)
(parent this)
(callback callback)))
))
It is currently aligned like this:
But I would like the title text box to be above the blog field and the submit button to be centered at the bottom.
I'm assuming that you're running this code in the same way as for your previous question. In that, you used your class like this:
(define f (new frame% [label "blog post GUI"] [min-width 400] [min-height 500]))
(define tib (new blogPost%
[parent f]))
(send f show #t)
Now since you defined blogPost% as a subclass of horizontal-panel%, it also inherits all of the initialization arguments of horizontal-panel%, including the alignment argument. So you can pass the [alignment '(left top)] initialization argument to your blogPost% class:
(define f (new frame% [label "blog post GUI"] [min-width 400] [min-height 500]))
(define tib (new blogPost%
[parent f]
[alignment '(left top)]))
(send f show #t)
If you want to build this default into your blogPost% class, you could add it to the (super-new) form instead:
(define blogPost%
(class horizontal-panel%
(super-new [alignment '(left top)])
...))
However, if you happen to have a (new blogPost% ... [alignment '(left top)] ...) around somewhere else, I believe this will break that code.
So to avoid that, it would probably be best to make the blogPost% class it's own class, so that instead of being a horizontal-panel%, it would have a horizontal-panel%, in the same way that it already has two text-fields and a button.
This is better for the long term because after this change, code that uses blogPost% won't break if you change which initialization arguments you passed to horizontal-panel% (which was implicit in the super-new previously).
(define blogPost%
(class object% ; object% instead of horizontal-panel%
; This argument is explicit now.
; If other code relies on other arguments, specify them here.
(init parent)
(super-new)
(define panel
(new horizontal-panel% ; this new call is explicit now
[parent parent] ; you can later add more arguments
[alignment '(left top)])) ; and it won't break things
(define titleoutput
(new text-field%
[label " title"]
[min-height 20]
[min-width 200]
[vert-margin 20]
[horiz-margin 10]
[parent panel])) ; panel instead of this
(define output
(new text-field%
[label "blog"]
[style '(multiple)]
[min-height 20]
[vert-margin 20]
[min-width 400]
[parent panel])) ; panel instead of this
(define (callback button event)
(define title-new-value (send titleoutput get-value))
(define new-value (send output get-value))
(save title-new-value new-value)
(send output set-value "")
(send titleoutput set-value "")
(send howisit show #t))
(define button
(new button%
[label "Submit"]
[vert-margin 0]
[horiz-margin 10]
[parent panel] ; panel instead of this
[callback callback]))
))
(define f (new frame% [label "blog post GUI"] [min-width 400] [min-height 500]))
(define tib (new blogPost%
[parent f]))
(send f show #t)
Of course, with this method, you won't be able to use the methods defined for horizontal-panel% on instances of your blogPost% class, but in the long run that's a good thing as well. If you ever, in the future, wanted to change the implementation to use something other than a horizontal-panel%, you could.

Resources