Here is a very simple Scala GUI test:
import scala.swing._
object FirstSwingApp extends SimpleGUIApplication {
def top = new MainFrame {
contents = new GridPanel(30, 20) {
contents ++= 1 to 600 map (_ => new Label("test"))
}
}
}
Every contained label is displayed exactly as big as it needs to be:
Now I want to replace Label with a custom type:
contents ++= 1 to 600 map (_ => new Foo)
class Foo extends Panel {
override def minimumSize = {
println("minimumSize")
new java.awt.Dimension(32, 32)
}
override def preferredSize = {
println("preferredSize")
new java.awt.Dimension(32, 32)
}
override def maximumSize = {
println("maximumSize")
new java.awt.Dimension(32, 32)
}
}
But the result is way too small:
Apparently, none of the xxxSize methods gets called, because the program produces no console output. What exactly do I have to change so that each Foo is displayed with a size of 32x32 pixels?
The problem here is that scala.swing is just a wrapper around javax.swing. And Label, in this case, is just an instance, that is wrapped around javax.swing.JLabel.
And when the component is being passed to javax.swing system, only the component is passed, not the wrapper.
Thus, overriding methods on wrappers will do you no good.
But you can override methods on the actual component instance. For example:
import scala.swing._
import javax.swing._
import java.awt.Dimension
import javax.swing.JPanel
object Swg extends SimpleSwingApplication {
class Foo extends Panel {
override lazy val peer: JPanel = new JPanel with SuperMixin {
override def getMinimumSize = new Dimension(32, 32)
override def getPreferredSize = new Dimension(32, 32)
override def getMaximumSize = new Dimension(32, 32)
}
}
def top = new MainFrame {
contents = new GridPanel(30, 20) {
contents ++= List.fill(600)(new Foo)
}
}
}
On my machine, that gives a frame of 640 px wide and about 960 px high - which is probably what you want.
As Rogach said, the problem is you don't change the underlying peer. Another possibility to do that is the following:
class Foo extends Panel {
preferredSize = new Dimension(32, 32)
}
Related
So I have the following simple program in scala:
object CViewerMainWindow extends SimpleSwingApplication {
var i = 0
def top = new MainFrame {
title = "Hello, World!"
preferredSize = new Dimension(320, 240)
// maximize
visible = true
contents = new Label("Here is the contents!")
listenTo(top)
reactions += {
case UIElementResized(source) => println(source.size)
}
}
.
object CViewer {
def main(args: Array[String]): Unit = {
val coreWindow = CViewerMainWindow
coreWindow.top
}
}
I had hoped it would create a plain window. Instead I get this:
You are creating an infinite loop:
def top = new MainFrame {
listenTo(top)
}
That is, top is calling top is calling top... The following should work:
def top = new MainFrame {
listenTo(this)
}
But a better and safer approach is to forbid that the main frame is instantiated more than once:
lazy val top = new MainFrame {
listenTo(this)
}
I am developing an application with a Scala-swing front-end. I have a MainFrame that is populated and working well. I have a dialog that works well too. But when I access the parent frame from the dialog the contents of the frame clear. The MemuBar is still there.
I only need to centre the dialog on the frame, so I just passed a Point (after trying to do it right) and even that causes the problem. I can set a location point created in the dialog I just can't access the frame to do it. And this I really don't get; I create the point in the frame and send it to the dialog, that is fine, but setting the dialog location to it clears the frame.
I am using "org.scala-lang.modules" % "scala-swing_2.11" % "1.0.2"
Any one have any ideas?
Thanks!
On the other hand this demo code works fine, so it isn't that simple
package hack
import scala.swing.{Action, BorderPanel, Button, Dialog, Dimension, FlowPanel, Frame, MainFrame, Menu, MenuBar, MenuItem, SimpleSwingApplication, TabbedPane}
/**
* Created by bday on 3/31/16.<br>
* <br>
* FrameClearingDialog will do something useful I'm sure
*/
class FrameClearingDialog (parent: Frame) {
val dialog = new Dialog
dialog.contents = new FlowPanel() {
preferredSize = new Dimension(500,500)
}
dialog.open()
dialog.setLocationRelativeTo(parent)
}
class Parent extends SimpleSwingApplication {
override def top: Frame = new MainFrame {
title = "Hack "
preferredSize = new Dimension(1000,1000)
menuBar = new MenuBar() {
contents += new Menu("Menu") {
contents += new MenuItem(Action("Show Dialog") {
createAndShowDialog
})
}
}
val panel = new BorderPanel() {
layout(new Button() {text="button"}) = BorderPanel.Position.North
layout(new Button() {text="button"}) = BorderPanel.Position.Center
layout(new Button() {text="button"}) = BorderPanel.Position.South
}
contents = new TabbedPane() {
pages += new TabbedPane.Page("Page", panel)
}
}
def createAndShowDialog = {
new FrameClearingDialog(top)
}
}
object Starter extends App {
val demo = new Parent
demo.main(args)
}
This is not the answer, but it does explain the issue enough to understand and avoid it.
The problem seems to be scope. If the content is created inside the MainFrame constructor it survives the call from the child, if created outside it does not. Swing does some strange things sometimes and I am not going to spend more time on this now.
If you move the creation of "map" into the MainFrame then this example will work correctly.
package hack
import scala.swing.{Action, BorderPanel, BoxPanel, Button, Dialog, Dimension, FlowPanel, Frame, MainFrame, Menu, MenuBar, MenuItem, Orientation, Panel, Point, RichWindow, SimpleSwingApplication, TabbedPane}
/**
* Created by bday on 3/31/16.<br>
* <br>
* Utils will do something useful I'm sure
*/
object Utils {
def findCenter(window: RichWindow) = {
new Point(window.location.x + window.size.width/2, window.location.y + window.size.height/2)
}
def centerMe(parent: RichWindow, child: RichWindow) = {
val parentCenter = findCenter(parent)
val childCenter = findCenter(child)
new Point(parentCenter.x - childCenter.x, parentCenter.y - childCenter.y)
}
}
/**
* Created by bday on 3/31/16.<br>
* <br>
* FrameClearingDialog will do something useful I'm sure
*/
class FrameClearingDialog (parent: Frame) {
val dialog = new Dialog
dialog.contents = new FlowPanel() {
preferredSize = new Dimension(500, 500)
}
dialog.location = Utils.centerMe(parent, dialog)
dialog.open()
}
class Parent extends SimpleSwingApplication {
val map = {
var map = Map.empty[Int, Panel]
for (x <- 1 to 5) {
map += x -> createPanel(x)
}
map
}
override def top: Frame = new MainFrame {
title = "Hack "
preferredSize = new Dimension(1000,1000)
menuBar = new MenuBar() {
contents += new Menu("Menu") {
contents += new MenuItem(Action("Show Dialog") {
createAndShowDialog
})
}
}
contents = new TabbedPane() {
for (x <- 1 to 5) {
pages += new TabbedPane.Page(s"Page $x", map(x))
}
}
}
def createPanel(x: Int) = {
new BoxPanel(Orientation.Vertical) {
contents += new Button() {text=s"button $x"}
}
}
def createAndShowDialog = {
new FrameClearingDialog(top)
}
}
object Starter extends App {
val demo = new Parent
demo.main(args)
}
I am trying to implement a simple application in which I can write an item in Textfield and then entering a button which in turn reacting by inserting that item in a combo box.
However, I am facing a problem that the scala combobox swing is not mutable(I guess)?
Is there any way to make a combo mutable using scala swing?
import scala.swing._
import scala.swing.event._
import scala.swing.BorderPanel.Position._
object ReactiveSwingApp extends SimpleSwingApplication {
def top = new MainFrame {
title = "Reactive Swing App"
val button = new Button {
text = "Add item"
}
var textfield = new TextField {
text = "Hello from a TextField"
}
var items = List("Item 1","Item 2","Item 3","Item 4")
val combo = new ComboBox(items)
contents = new BorderPanel {
layout(new BoxPanel(Orientation.Vertical) {
contents += textfield
contents += button
contents += combo
border = Swing.EmptyBorder(30, 30, 10, 30)
}) = BorderPanel.Center
}
listenTo(button, textfield)
reactions += {
case ButtonClicked(button) =>
// var items1 = textfield.text :: items <- how can a read Item be inserted
}
}
}
You are correct, the Scala-Swing ComboBox wrapper for JComboBox has a static model which does not allow additions or removals. Unfortunately, there are quite a few things in Scala-Swing which are less capable than the underlying Java-Swing components.
The good thing however is that each Scala-Swing component has a Java-Swing peer field, which you can use to patch the missing bits. I think the easiest is to create a thin wrapper for javax.swing.DefaultComboBoxModel, like:
class ComboModel[A] extends javax.swing.DefaultComboBoxModel {
def +=(elem: A) { addElement(elem) }
def ++=(elems: TraversableOnce[A]) { elems.foreach(addElement) }
}
Then your construction becomes
val combo = new ComboBox(List.empty[String])
val model = new ComboModel[String]
model ++= items
combo.peer.setModel(model) // replace default static model
And your reaction
reactions += {
case event.ButtonClicked(button) =>
model += textfield.text
}
At "0___" have you tested your code? It seems to have some Mismatch issues. I'm using scala 2.10.
Actually there is a way to work around the problem without having to handle the ComboBoxModel.
Here is a short example:
class ComboBox(items: Array[String])
extends scala.swing.Component with java.awt.event.ActionListener with Publisher{
override lazy val peer = new JComboBox(items)
def +=(item: String) = peer.addItem(item)
def -=(item: String) = peer.removeItem(item)
def item = peer.getSelectedItem.asInstanceOf[String]
def reset{peer.setSelectedIndex(0)}
//additional methods
//since its peer val is now a JComboBox
peer.addActionListener(this)
def actionPerformed(e: ActionEvent){
//how to react once the selection changes
}
//since its also a scala.swing.Component extender
listenTo(mouse.clicks) //and all the rest of the Publishers
reactions += {
case e: MouseClicked => //do something awesome
}
/* Also one could additionally extend the scala.swing.Publisher Trait
* to be able to publish an event from here.
*/
publish(MyEvent(item))
}
case class MyEvent(item: String) extends Event
I'm trying to set the size of a button to a specific size of pixels with minimumSize, but it looks like it doesn't work.
I even tried to subclass it and do it this way
class SizedButton(text0: String, width0: Int, height0: Int)
extends Button(text0) {
minimumSize = new Dimension(width0, height0)
// also tried preferredSize here ...
}
but that didn't work either.
It's a bit hard to tell what you're really trying to do, but generally sizing depends a lot on the layout manager you are using. Anyway the following fixes the size of a button e.g.:
import swing._
import java.awt.Dimension
val s = new Dimension(100, 100)
val f = new Frame {
contents = new FlowPanel {
contents += new Button("huhu") {
minimumSize = s
maximumSize = s
preferredSize = s
}
}
}
f.pack
f.visible = true
I'm building a GUI with the SimpleSwingApplication trait in scala swing.
What I want to do is to provide a mechanism on close, that asks the user (Yes,No,Cancel) if he didn't save the document yet. If the user hits Cancel the Application shouldn't close. But everything I tried so far with MainFrame.close and closeOperation didn't work.
So how is this done in Scala Swing?
I'm on Scala 2.9.
Thanks in advance.
Slightly different variation from what Howard suggested
import scala.swing._
object GUI extends SimpleGUIApplication {
def top = new Frame {
title="Test"
import javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE
peer.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE)
override def closeOperation() { showCloseDialog() }
private def showCloseDialog() {
Dialog.showConfirmation(parent = null,
title = "Exit",
message = "Are you sure you want to quit?"
) match {
case Dialog.Result.Ok => exit(0)
case _ => ()
}
}
}
}
By using DO_NOTHING_ON_CLOSE you are given a chance to define what should be done when a WindowEvent.WINDOW_CLOSING event is received by the scala frame. When a scala frame receives a WINDOW_CLOSING event it reacts by calling closeOperation. Hence, to display a dialog when the user attempts to close the frame it is enough to override closeOperation and implement the desired behavior.
What about this:
import swing._
import Dialog._
object Test extends SimpleSwingApplication {
def top = new MainFrame {
contents = new Button("Hi")
override def closeOperation {
visible = true
if(showConfirmation(message = "exit?") == Result.Ok) super.closeOperation
}
}
}
I am not really familiar with scala swing but I found this code in some of my old test programs:
object GUI extends SimpleGUIApplication {
def top = new Frame {
title="Test"
peer.setDefaultCloseOperation(0)
reactions += {
case WindowClosing(_) => {
println("Closing it?")
val r = JOptionPane.showConfirmDialog(null, "Exit?")
if (r == 0) sys.exit(0)
}
}
}
}
This does what I wanted to do; calling super.closeOperation didn't close the frame. I would have just said that in a comment, but I am not yet allowed.
object FrameCloseStarter extends App {
val mainWindow = new MainWindow()
mainWindow.main(args)
}
class MainWindow extends SimpleSwingApplication {
def top = new MainFrame {
title = "MainFrame"
preferredSize = new Dimension(500, 500)
pack()
open()
def frame = new Frame {
title = "Frame"
preferredSize = new Dimension(500, 500)
location = new Point(500,500)
pack()
peer.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE)
override def closeOperation() = {
println("Closing")
if (Dialog.showConfirmation(message = "exit?") == Result.Ok) {
peer.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
close()
}
}
}
frame.open()
}
}