Handle swing ScrollBar events in scala - swing

I am trying to add a ScrollBar. The ScrollBar will iterate through the documents displayed. However, I am having trouble receiving an event when the scroll bar changes. I'm not sure what I need to listen to, and I'm not sure what event I should be responding to. I tried the following and I get some events, but I don't think these are the ideal events to handle.
listenTo(scrollBar)
listenTo(scrollBar.keys)
listenTo(scrollBar.mouse.moves)
listenTo(scrollBar.mouse.wheel)
listenTo(scrollBar.mouse.clicks)
For example, I only get MouseClicked, MousePressed, and MouseReleased when I click inside the scrollbar--not when I click on the arrows to actually change the value.
I found this discussion about scroll bars not receiving events properly, but it's two years old. As far as I can tell, the author did not follow up an file a ticket. Maybe he found a workaround.
Any ideas?

Good question. Clicking on arrows isn't handled by ScrollBar, it's handled by ScrollBarUI. I believe that default implementation(or at least base class for majority of ScrollBarUI implementations) is BasicScrollBarUI.
If you'll take a look into source of javax.swing.plaf.basic.BasicScrollBarUI it has incrButton and decrButton buttons and they are components you want listen to.
P.S. I had similar need for having custom (key) listener for my Slider and having custom ui which exposes needed components/model(as you could see almost all components are protected so you easy could access them in subclasses and expose via public getters) worked great for me. I did that in plain java though, maybe in scala you can listen to buttons just by specyfying property name.

One more piece of Scala Swing being broken. The Adjustable trait seems to be completely hollow, nothing is wired.
The following works:
class ScrollBarAlive extends swing.ScrollBar {
me =>
peer.addAdjustmentListener(new java.awt.event.AdjustmentListener {
def adjustmentValueChanged(e: java.awt.event.AdjustmentEvent) {
publish(new swing.event.ValueChanged(me))
}
})
}
Test:
import swing._
object ScrollBarTest extends SimpleSwingApplication {
lazy val top = new Frame {
val label = new Label { text = "0" }
val scroll = new ScrollBarAlive {
orientation = Orientation.Horizontal
listenTo(this)
reactions += {
case event.ValueChanged(_) =>
label.text = value.toString + (if (valueIsAjusting) " A" else "")
}
}
contents = new BorderPanel {
add(label, BorderPanel.Position.North)
add(scroll, BorderPanel.Position.South)
}
pack().centerOnScreen()
open()
}
}
A proper implementation would also introduce a subtype of AdjustingEvent.

Related

Remove ViewController from stack

In our App we have a log-in ViewController A. On user log-in, a request navigate is automatically called to navigate to the next ViewController B. However when this is done we want to remove the log-in ViewController A from the stack so the user cannot "go back" to the log-in view but goes back the previous ViewController before the log-in instead.
We thought about removing the ViewController A from the stack when ViewController B is loaded, but is there a better way?
In the Android version of the App we've set history=no (if I recall correctly) and then it works.
Is there an similar way to achieve this in MonoTouch and MvvmCross?
I ended up with removing the unwanted viewcontroller from the navigation controller. In ViewDidDisappear() of my login ViewController I did the following:
public override void ViewDidDisappear (bool animated)
{
if (this.NavigationController != null) {
var controllers = this.NavigationController.ViewControllers;
var newcontrollers = new UIViewController[controllers.Length - 1];
int index = 0;
foreach (var item in controllers) {
if (item != this) {
newcontrollers [index] = item;
index++;
}
}
this.NavigationController.ViewControllers = newcontrollers;
}
base.ViewDidDisappear(animated);
}
This way I way remove the unwanted ViewController when it is removed from the view. I am not fully convinced if it is the right way, but it is working rather good.
This is quite a common scenario... so much so that we've included two mechanisms inside MvvmCross to allow this....
a ClearTop parameter available in all ViewModel navigations.
a RequestRemoveBackStep() call in all ViewModels - although this is currently NOT IMPLEMENTED IN iOS - sorry.
If this isn't enough, then a third technique might be to use a custom presenter to help with your display logic.
To use : 1. a ClearTop parameter available in all ViewModel navigations.
To use this, simply include the ClearTop flag when navigating.
This is a boolean flag - so to use it just change:
this.RequestNavigate<ChildViewModel>(new {arg1 = val1});
to
this.RequestNavigate<ChildViewModel>(new {arg1 = val1}, true);
For a standard simple navigation controller presenter, this will end up calling ClearBackStack before your new view is shown:
public override void ClearBackStack()
{
if (_masterNavigationController == null)
return;
_masterNavigationController.PopToRootViewController (true);
_masterNavigationController = null;
}
from https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Touch/Views/Presenters/MvxTouchViewPresenter.cs
If you are not using a standard navigation controller - e.g. if you had a tabbed, modal, popup or split view display then you will need to implement your own presentation logic to handle this.
You can't: 2. RequestRemoveBackStep().
Sadly it proved a bit awkward to implement this at a generic level for iOS - so currently that method is:
public bool RequestRemoveBackStep()
{
#warning What to do with ios back stack?
// not supported on iOS really
return false;
}
from https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Touch/Views/MvxTouchViewDispatcher.cs
Sorry! I've raised a bug against this - https://github.com/slodge/MvvmCross/issues/80
3. You can always... Custom ideas
If you need to implement something custom for your iOS app, the best way is to do this through some sort of custom Presenter logic.
There are many ways you could do this.
One example is:
for any View or ViewModel which needs to clear the previous view, you could decorate the View or ViewModel with a [Special] attribute
in Show in your custom Presenter in your app, you could watch for that attribute and do the special behaviour at that time
public override void Show(MvxShowViewModelRequest request)
{
if (request.ViewModelType.GetCustomAttributes(typeof(SpecialAttribute), true).Any())
{
// do custom behaviour here - e.g. pop current view controller
}
base.Show(request);
}
Obviously other mechanisms might be available - it's just C# and UIKit code at this stage
I don't know about mvvm but you can simply Pop the viewcontroller (AC A) without animation and then push the new viewcontoller (AC B) with animation
From within AC A:
NavigationController.PopViewControllerAnimated(false);
NavigationController.PushViewController(new ACb(), true);

Scala Swing Popup menu on ListView

What would be the best way of adding a mouseListener to a Scala Swing ListView that what ever item in the list is clicked on it'll create a PopupMenu with options pertaining to that specific item that is clicked on?
Am I stuck with doing this with Java style code for now or has Scala Swing evolved a bit more since 2.8.1
A bit of what I got currently and maybe I'm listening to the wrong thing and am over looking the ScalaDocs on the ListView.
lazy val ui = new FlowPanel {
val listView = ListView(items) {
renderer = Renderer(_.name)
listenTo(this.mouse.clicks)
reactions += {
case e: MouseClicked =>
// How do I determine what item was clicked?
}
}
}
lazy val ui = new FlowPanel {
val listView = new ListView( Seq("spam", "eggs", "ham") )
listenTo(listView.selection)
reactions += {
case SelectionChanged(`listView`) => println(listView.selection.items(0))
}
contents += listView
}
This should produce output such as
spam
spam
eggs
eggs
ham
ham
as you click on the various items. I've never done this before but I had a look at the UIDemo example which can be found in the scala.swing.test package. To read the source, if you have IntelliJ, it's as simple as clicking on the relevant object in the scala-swing.jar in External Libraries in the Projects pane.
As for PopupMenu, I don't know - it doesn't look like that one has a scala-swing wrapper in 2.9.1, but I found one on GitHub here. Or you could just use the normal Swing version.
This is unacceptably late, but I feel compelled to offer the "java-style" solution for anyone who might be interested (leaving aside the specific details of my implementation, the essence is in the 'reactions'):
val listView = new ListView[Int] {
selection.intervalMode = ListView.IntervalMode.Single
listData = (1 to 20).toList
listenTo(mouse.clicks)
reactions += {
case m : MouseClicked if m.clicks == 2 =>
doSomethingWith( listData(selection.anchorIndex) )
//where doSomethingWith is your desired result of this event
}
}
Assuming single interval mode, the key to getting the list item that was just double-clicked is in simply using anchorIndex.

Scala swing newbie questions

This is my first experiment using Swing with Scala, and have a few questions about my code below. All it does is to produce a window with a coloured rectangle that changes colour. Please feel free to answer one or any of the questions.
1) I used a Java ActionListener below because I couldn't work out how to get javax.swing.Timer to work as a Publisher. Is there a way to use the Scala model, i.e. listenTo() - or is this the way to do it?
2) My overridden preferredSize value in the Panel doesn't seem to work: the window comes up minimized. In my Java version I override the getPreferredSize method, but there is no such method in Panel, so I assumed this is the way to do it, but why doesn't it work?
3) paintComponent isn't documented at all in the Scala API documentation. I assume this is because it is protected access in Java, but it seems like an oversight. Am I correct to override paintComponent or is it hidden because I'm supposed to use the documented paint method instead?
4) Scala doesn't seem to have getWidth() and getHeight() methods on components - is it standard to use size.width and size.height?
import swing._
import java.awt.{Graphics, Color}
import java.awt.event.{ActionEvent, ActionListener}
import javax.swing.Timer
object ColorPanel extends SimpleSwingApplication {
private var c: Color = new Color(0)
def top = new MainFrame {
title = "Flash!"
contents = p
}
val p = new Panel with ActionListener {
override val preferredSize = new Dimension(200, 200)
override def paintComponent(g: Graphics2D) {
g.setColor(c)
g.fillRect(0, 0, size.width, size.height)
}
def actionPerformed(e: ActionEvent) {
c = new Color((c.getRGB() + 1000) % 16777216)
repaint
}
}
val timer = new Timer(100, p)
timer.start()
}
No immediate answer. But your approach is certainly ok. I don't see though why your observer should be the panel. I would create an anonymous ActionListener directly with the timer, and instead add a specific method to that panel, like def animateColor() { ... }
You can use preferredSize = new Dimension(200, 200)
According to this quasi official document, yes : http://www.scala-lang.org/sid/8 (section 6 Custom Painting)
A bit stupid indeed to instantiate a new Dimension all the time. But if you look exactly at the example the SID, it does the same, uses size.height. In super high performance code, you may want to call directly into the underlying peer (peer.getWidth)

How to control scala swing listeners

This is a continuation to my last question. I used the MVC pattern with swing components and code goes like this
import scala.swing
import scala.swing.event._
case object MyBusinessEvent extends Event
class MyController extends Publisher {
val form = new MyForm
listenTo(form)
reactions += {
case MyBusinessEvent => //handle event code here
}
}
class MyForm extends Publisher {
val ui = new GridBagPanel {
val c = new Constraints
.... more code here
}
val button1 = new Button("Button 1")
//add button to panel
listenTo(button1)
reactions += {
case ButtonClicked(_) => publish(MyBusinessEvent)
}
}
However with multiple buttons the program hangs up and seems to stop publishing events. Is there any way to fix this? Thanks
Although from the truncated example it's not clear what the problem is, my guess is you are creating a cycle which unfortunately can happen quite easy with swing and MVC. that is, one model gets updated as part of listening to another model, and that again triggers an update in the other model. In java swing you have two choices,
a) temporarily remove the listener that is updating the model (e.g. do a removeActionListener before calling setSelectedItem on a JComboBox, and then afterwards re-register with addActionListener)
b) check for the event source (getSource on an java.util.EventObject) and ignore events in the model that originated from that very same model.
Now scala swing is more simplified, so you don't have event object and event sources. You can add though a source in your custom event case class. And it might be feasible to remove the reaction and re-add it after setting a model's state.
To avoid the cycles mentioned in 0__'s answer I have a trait like this:
trait Editable extends Publisher {
private var _editing = false
def editing = _editing
def editing_=(b: Boolean) = _editing = b
override def publish(e: Event) {
if(!editing) super.publish(e)
}
}
which I mix-in in the components I want to have more control about the events fired. So, when I want to change something without firing any event, I simply put the code between editing = true and editing = false.

Responding to key events in scala

I'm experimenting with a bit of Scala gui programming (my first project in scala, so I thought I'd start with something simple). But I seem to have got stuck at something that seems like it should be relatively trivial. I have a class that extends scala.swing.MainFrame, and I'd like to detect when a user presses a key when that window has focus. Funny thing is I don't seem to be able to find any way to get that event to fire.
I found an example of how someone else had got around the problem here: http://houseofmirrors.googlecode.com/svn/trunk/src/src/main/scala/HouseGui.scala but they seem to have reverted to using the Java Swing API, which is a little disappointing. Does anyone know if there's a more idiomatic way of intercepting events?
This seems to work with Scala 2.9
package fi.harjum.swing
import scala.swing._
import scala.swing.event._
import java.awt.event._
object KeyEventTest extends SimpleSwingApplication {
def top = new MainFrame {
val label = new Label {
text = "No click yet"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += label
border = Swing.EmptyBorder(30,30,10,10)
listenTo(keys)
reactions += {
case KeyPressed(_, Key.Space, _, _) =>
label.text = "Space is down"
case KeyReleased(_, Key.Space, _, _) =>
label.text = "Space is up"
}
focusable = true
requestFocus
}
}
}
In addition to listening to this.keys you should also call requestFocus on the component or set focusable=true, if it is Panel or derived class.
I expect you need to listen to this.keys (where this is the element of the GUI receiving the keyboard events). See the equivalent question about mouse event.
My solution for this required me to do the following:
class MyFrame extends MainFrame {
this.peer.addKeyListener(new KeyListener() {
def keyPressed(e:KeyEvent) {
println("key pressed")
}
def keyReleased(e:KeyEvent) {
println("key released")
}
def keyTyped(e:KeyEvent) {
println("key typed")
}
})
}
This only seemed to work though if there were no button objects attached to this component, or any of it's children.
Rather than falling back to java events all components have keys that publishes these events (so MainFrame does not). Not sure what the best solution is but it's always possible to wrap everything in the frame inside a Component and listen to its keys.