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)
}
Related
I am creating a application in scala. I want to open a frame on button click. I am new to scala. Here is the code
import scala.swing._;
import java.io._;
import scala.swing.event._;
import javax.swing.ImageIcon;
object Try1 extends SimpleSwingApplication {
def top = new MainFrame {
title = "First Swing App";
val button = new Button {
text = "Proceed"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += button
border = Swing.EmptyBorder(30, 30, 30, 30)
}
val obj = new Try2();
listenTo(button)
reactions += {
case ButtonClicked(button) =>
//here 2nd frame must be open
}
}
The code for window to be opened goes like this
import javax.swing.ImageIcon
import scala.swing._
class Try2 extends SimpleSwingApplication {
def top = new MainFrame {
title = "Second Swing App";
val button = new Button {
text = "Proceed"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += button
border = Swing.EmptyBorder(30, 30, 30, 30)
}
}
}
How can I open new window. Please help
you could do something like this as shown below. Here intentionally I am creating a new instance of Try2 instead of obj created earlier since this is a cleaner approach.
reactions += {
case ButtonClicked(x: Button) if x.text == "Proceed" =>
new Try2().top.visible = true
}
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 have a BoxPanel of buttons in my scala swing app that looks ugly to me because the buttons are all different sizes. I had changed it to a GridPanel but then they filled the panel vertically aswell which I found uglier. How can I have all buttons fill the width of the BoxPanel but stay their perferred height?
I tried a work around, shown below, where the panel sets all the contents to the max width but it had no effect.
val buttons = new BoxPanel(Orientation.Vertical) {
contents += new Button("Normal Button")
contents += new Button("small")
contents += new Button("Significantly larger button than the rest")
val maxWidth = contents map {
(button: Component) => button.preferredSize
} maxBy { _.width }
contents foreach {
(button: Component) => button.preferredSize = maxWidth
}
}
Is there a way to make the above workaround work or a way that isn't a workaround?
A s discussed in Box Layout Features, "if all the components have identical X alignment, then all components are made as wide as their container." Override the button's getMaximumSize() implementation as shown below to return an arbitrary width and the button's preferred hight. Change setHorizontalAlignment() and/or resize the frame to see the effect.
#Override
public Dimension getMaximumSize() {
return new Dimension(
Short.MAX_VALUE, getPreferredSize().height);
}
Code as shown:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/a/34443937/230513 */
public class ButtonBoxTest {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JPanel() { //arbitrary filler
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
});
f.add(createButtonPanel(), BorderLayout.EAST);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JPanel createButtonPanel() {
JPanel btnPanel = new JPanel();
btnPanel.setLayout(new BoxLayout(btnPanel, BoxLayout.Y_AXIS));
btnPanel.add(createButton("Button 1"));
btnPanel.add(createButton("Button 2"));
btnPanel.add(createButton("Long Button 3"));
btnPanel.add(createButton("Button 4"));
btnPanel.add(createButton("Button 5"));
return btnPanel;
}
private JButton createButton(String name) {
final JButton b = new JButton(name) {
#Override
public Dimension getMaximumSize() {
return new Dimension(
Short.MAX_VALUE, getPreferredSize().height);
}
};
b.setAlignmentX(0.5f);
b.setHorizontalAlignment(JButton.RIGHT);
return b;
}
public static void main(String[] args) {
EventQueue.invokeLater(new ButtonBoxTest()::display);
}
}
TrashGod is right but I figured I would post an answer translated to Scala as his is in Java. I got my original workaround to work by replacing preferredSize with maximumSize, thanks to reading trashgod's answer.
val buttons = new BoxPanel(Orientation.Vertical) {
contents += new Button("Normal Button")
contents += new Button("small")
contents += new Button("Significantly larger button than the rest")
val maxWidth = contents map {
(button: Component) => button.maximumSize
} maxBy { _.width }
contents foreach {
(button: Component) => button.maximumSize = maxWidth
}
}
I was also able to get a solution translated from trashgod's answer with just
private def equalButtons(name: String): Button = {
new Button(name) {
maximumSize = new Dimension(Short.MaxValue, maximumSize.height)
}
}
val buttons = new BoxPanel(Orientation.Vertical) {
contents += new equalButtons("Normal Button")
contents += new equalButtons("small")
contents += new equalButtons("Significantly larger button than the rest")
}
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 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()
}
}