Controllers are the traffic cops of your application. They decide how to react to events, they coordinate interaction with other controllers and they define some of the overall characteristics of a view such as which events it will generate and how it should respond to things like the close button being pressed. For a general introduction to the idea of MVC and the role of a controller, please see: en.wikipedia.org/wiki/Model-view-controller. Monkeybars is not, strictly speaking, an MVC framework. However the general idea of the seperations of concerns that most people think of when you say ‘MVC’ is applicable.
The controller defines the model and view classes it is associated with. Most controllers will declare a view and a model that will be instantiated along with the controller and whose life cycle is managed by the controller. It is not required to declare a view or model but a controller is of questionable usefulness without it.
The controller is where you define any events you are interested in handling (see add_listener) as well as two special events, the pressing of the “close” button of the window (see close_action), and the updating of the MVC tuple (see update_method). Handlers are methods named according to a certain convention that are the recipient of events. Handlers are named <component_event>. No events are actually generated and sent to the controller unless a listener has been added for that component, however, component-specific handlers will automatically add a listener for that component when the class is instantiated. Therefore a method named ok_button_action_performed would be the equivalent of
add_listener :type => :action, :components => ["ok_button"]
These automatic listener registrations work for any component that can be resolved directly in your view. In the above example, the view could contain a component named ok_button, okButton or even OkButton and the listener would be added correctly. If you have a nested component such as text_field.document then you will need to use an explicit add_listener registration.
Handler methods can optionally take one parameter which is the event generated by Swing. This would look like
def some_component_event_name(swing_event)
Automatic listeners can also be decorated with a trailing ! to indicate that the update_view method should automatically be run on completion of the callback being run from a Swing event. In the previous example
def some_component_event_name! end
is the equivalent of
def some_component_event_name update_view end
While an event handler is running, the Swing Event Dispatch Thread (usually called the EDT) is blocked and as such, no repaint events will occur and no new events will be proccessed. If you have a process that is long running, but you don’t want to make asynchronous by spawning a new thread, you can use the repaint_while method which takes a block to execute while still allowing Swing to process graphical events (but not new interaction events like mouse clicking or typing).
def button_action_performed repaint_while do sleep(20) # the gui is still responsive while we're in here sleeping end model.text = "done sleeping!" update_view end
Example of a controller, this assumes the existance of a Ruby class named MyModel that has an attribute named user_name that is mapped to a field on a subclass of Monkeybars::View named MyView that has a button named “ok_button” and a text field called user_name
require 'monkeybars' class MyController < Monkeybars::Controller set_view :MyView set_model :MyModel close_action :exit def ok_button_mouse_released puts "The user's name is: #{view_state.model.user_name}" end end
It is important that you do not implement your own initialize and update methods, this will interfere with the operation of the Controller class (or if you do be sure to call super as the first line).
Returns a frozen hash of ControllerName => [instances] pairs. This is useful if you need to iterate over all active controllers to call update or to check for a status.
# NOTE: Controllers that have close called on them will not show up on this list, even if open is subsequently called. If you need a window to remain in the list but not be updated when not visible you can do:
Monkeybars::Controller.active_controllers.values.flatten.each{|c| c.update if c.visible? }
# File lib/monkeybars/controller.rb, line 219 def self.active_controllers @@instance_list.clone.freeze end
# File lib/monkeybars/controller.rb, line 227 def self.add_listener(details) original_add_listener(details) hide_protected_class_methods #workaround for JRuby bug #1283 end
Always returns a new instance of the controller.
controller1 = MyController.create_instance controller2 = MyController.create_instance ... controller32 = MyController.create_instance
# File lib/monkeybars/controller.rb, line 126 def self.create_instance new end
Controllers cannot be instantiated via a call to new, instead use instance to retrieve the instance of the view. Currently only one instance of a controller is created but in the near future a configurable limit will be available so that you can create n instances of a controller.
# File lib/monkeybars/controller.rb, line 108 def self.instance @@instance_lock[self].synchronize do controller = @@instance_list[self] unless controller.empty? controller.last else __new__ end end end
# File lib/monkeybars/controller.rb, line 245 def initialize @model_has_block = false @view_has_block = false @__model = create_new_model unless self.class.model_class.nil? self.class.model_class.last.call(@__model) if @model_has_block @__view = create_new_view unless self.class.view_class.nil? self.class.view_class.last.call(@__view) if @view_has_block @__transfer = {} @__view_state = nil setup_implicit_and_explicit_event_handlers action = close_action unless [:nothing, :close, :exit, :dispose, :hide].include?(action) raise "Unknown close action: #{action}. Only :nothing, :close, :exit, :dispose, and :hide are supported" end window_type = if @__view.instance_variable_get(:@main_view_component).kind_of? javax.swing.JInternalFrame "internalFrame" else "window" end unless @__view.nil? @__view.close_action(Monkeybars::View::CloseActions::METHOD, MonkeybarsWindowAdapter.new(:"#{window_type}Closing" => self.method(:built_in_close_method))) end @closed = true end
Valid close actions are
:nothing
:close (default)
:exit
:dispose
:hide
action :nothing - close action is ignored, this means you cannot close the window unless you provide another way to do this
action :close - calls the controller’s close method
action :exit - closes the application when the window’s close button is pressed
action :dispose - default action, calls Swing’s dispose method which will release the resources for the window and its components, can be brought back with a call to show
action :hide - sets the visibility of the window to false
# File lib/monkeybars/controller.rb, line 206 def self.set_close_action(action) self.send(:class_variable_set, :@@close_action, action) end
The declared model class is auto-required prior to the class being instantiated. It is not a requirement that you have a model, Monkeybars will operate without one as long as you do not attempt to use methods that interact with the model.
The set_model method may be used in 3 different ways.
The first and most common use is to pass the name of the model class as a string. Internally the controller instantiates this class and makes it available to the controller via a private method model. Note that this form of set_model does not allow passing any parameters to the model class, thus the model must implement a zero-argument constructor.
class FooController < ApplicationController set_model 'FooModel' set_view 'FooView' set_close_action :exit end
The second format passes a block to set_model. The block is executed via instance_eval with the result assigned as the model. The construction of the model is fully under your control.
class FooController < ApplicationController set_model { FooModel.new("arg1", "arg2", "arg3") } set_view 'FooView' set_close_action :exit end
The third format takes both a string with the model class name and a block for the purpose of setting initial values. The block will be passed the model object. No parameters may be passed to the constructor in this format.
class FooModel attr_accessor :some_value end class FooController < Monkeybars::Controller set_model "FooModel" do |model| model.some_value = 5 end end
# File lib/monkeybars/controller.rb, line 172 def self.set_model(model=nil, &block) self.model_class = [model,block] end
Declares a method to be called whenever the controller’s update method is called.
# File lib/monkeybars/controller.rb, line 184 def self.set_update_method(method) raise "Argument must be a symbol" unless method.kind_of? Symbol raise "'Update' is a reserved method name" if :update == method self.send(:class_variable_set, :@@update_method_name, method) end
See Controller.set_model. Uses same 3 options for declaring the view to use and for optionally supplying a block or both a class and a block.
# File lib/monkeybars/controller.rb, line 179 def self.set_view(view=nil, &block) self.view_class = [view,block] end
Nests a controller under this controller with the given key
def add_user_button_action_performed @controllers << UserController.create_instance add_nested_controller(:user_list, @controllers.last) @controllers.last.open end
This forces the view to perform its nesting. See also Monkeybars::Controller#remove_nested_controller
# File lib/monkeybars/controller.rb, line 329 def add_nested_controller(name, sub_controller) nested_view = sub_controller.instance_variable_get(:@__view) @__view.add_nested_view(name, nested_view, nested_view.instance_variable_get(:@main_view_component), model, transfer) end
Hides the view and unloads its resources
# File lib/monkeybars/controller.rb, line 377 def close @closed = true @__view.unload unless @__view.nil? unload @__view.dispose if @__view.respond_to? :dispose @@instance_lock[self.class].synchronize do @@instance_list[self.class].delete self end end
True if close has been called on the controller
# File lib/monkeybars/controller.rb, line 372 def closed? @closed end
Disposes the view
# File lib/monkeybars/controller.rb, line 362 def dispose @__view.dispose end
True if the controller’s view is focused. Focus mostly means this is the window where mouse clicks and keyboard presses are directed. There are also UI effects for focused components. For event driven notifications of focus, see the following: java.sun.com/j2se/1.4.2/docs/api/java/awt/event/FocusListener.html java.sun.com/docs/books/tutorial/uiswing/events/focuslistener.html java.sun.com/docs/books/tutorial/uiswing/misc/focus.html
# File lib/monkeybars/controller.rb, line 290 def focused? @__view.focused? end
Hides the view
# File lib/monkeybars/controller.rb, line 357 def hide @__view.hide end
Stub to be overriden in sub-class. This is where you put the code you would normally put in initialize, it will be called the first time open is called on the controller.
# File lib/monkeybars/controller.rb, line 411 def load(*args); end
Calls load if the controller has not been opened previously, then calls update_view and shows the view.
# File lib/monkeybars/controller.rb, line 389 def open(*args, &block) @@instance_lock[self.class].synchronize do unless @@instance_list[self.class].member? self @@instance_list[self.class] << self end end if closed? load(*args, &block) @__view.on_first_update(model, transfer) clear_view_state @closed = false end show self #allow var assignment off of open, i.e. screen = SomeScreen.instance.open end
Removes the nested controller with the given key This does not do any cleanup on the nested controller’s instance.
def remove_user_button_action_performed remove_nested_controller(:user_list, @controllers.last) UserController.destroy_instance @controllers.last @controllers.delete @controllers.last end
This performs the view’s nesting. See also Monkeybars::Controller#add_nested_controller
# File lib/monkeybars/controller.rb, line 346 def remove_nested_controller(name, sub_controller) nested_view = sub_controller.instance_variable_get(:@__view) @__view.remove_nested_view(name, nested_view, nested_view.instance_variable_get(:@main_view_component), model, transfer) end
Shows the view
# File lib/monkeybars/controller.rb, line 367 def show @__view.show end
Sends a signal to the view. The view will process the signal (if it is defined in the view via View.define_signal) and optionally invoke the
callback that is passed in as a block.
This is useful for communicating one off events such as a state transition def update signal(:red_alert) if model.threshold_exceeded? end
# File lib/monkeybars/controller.rb, line 316 def signal(signal_name, &callback) @__view.process_signal(signal_name, model, transfer, &callback) end
Stub to be overriden in sub-class. This is called whenever the controller is closed.
# File lib/monkeybars/controller.rb, line 414 def unload; end
Calls the method that was set using Controller.set_update_method. If no method has been set defined, this call is ignored.
# File lib/monkeybars/controller.rb, line 294 def update if self.class.class_variables.member?("@@update_method_name") method_name = self.class.send(:class_variable_get, :@@update_method_name) send(method_name) end end
Resets memoized view_state value. This is called automatically after each event so it would only need to be called if view_state is used outside of an event handler.
# File lib/monkeybars/controller.rb, line 497 def clear_view_state # :doc: @__view_state = nil end
Returns the model object. This is the object that is passed to the view when update_view is called. This model is not the same model that you get from view_state. Values that you want to propogate from the view_state model to this model can be done using update_model.
# File lib/monkeybars/controller.rb, line 429 def model #:doc: @__model end
Returns the transfer object which is a transient hash passed to the view whenever update_view is called. The transfer is cleared after each call to update_view. The transfer is used to pass data to and from the view that is not part of your model. For example, if you had a model that was an ActiveRecord object you would probably not want to put things like the currently selected item into your model. That data could instead be passed as a value in the transfer.
transfer[:selected_framework] = "monkeybars"
Then in your view, you could use that transfer value to select the correct value out of a list.
map :view => "framework_list.selected_item", :transfer => :selected_framework
See View#map for more details on the options for your mapping.
# File lib/monkeybars/controller.rb, line 449 def transfer #:doc: @__transfer end
This method is almost always used from within an event handler to propogate the view_state to the model. Updates the model from the source provided (typically from view_state). The list of properties defines what is modified on the model.
def ok_button_action_perfomed update_model(view_state.model, :user_name, :password) end
This would have the same effect as:
model.user_name = view_state.model.user_name model.password = view_state.model.password
# File lib/monkeybars/controller.rb, line 514 def update_model(source, *properties) # :doc: update_provided_model(source, @__model, *properties) end
This method works just like Controller#update_model except that the target is not implicitly the model. The second parameter is a target object for the properties to be propogated to. This is useful if you have a composite model or need to updated other controllers.
def ok_button_action_perfomed update_provided_model(view_state.model, model.user, :user_name, :password) end
This would have the same effect as:
model.user.user_name = view_state.model.user_name model.user.password = view_state.model.password
# File lib/monkeybars/controller.rb, line 531 def update_provided_model(source, destination, *properties) # :doc: properties.each do |property| destination.send("#{property}=", source.send(property)) end end
Equivalent to view_state.model
# File lib/monkeybars/controller.rb, line 485 def view_model # :doc: view_state.model end
Returns a ViewState object which contains a model and a transfer hash of the view’s current contents as defined by the view’s mappings. This is for use in event handlers. The contents of the model and transfer are not the same as the contents of the model and transfer in the controller, they are new objects created when view_state was called. If you wish to propogate the values from the view state’s model into the actual model, you must do this yourself. A helper method update_model is provided to make this easier. In an event handler this method is thread safe as Swing is single threaded and blocks any modification to the GUI while the handler is being proccessed.
The view state object has two properties, model and transfer.
def ok_button_action_performed if view_state.transfer[:foo] == :bar model.baz = view_model.baz end end
Any subsequent call to view_state will return the same object, that is, this method is memoized internally. At the end of each event (after all handlers have been called) the memoized view state is cleared. If you call view_state outside of an event handler it is important that you clear the view state yourself by calling clear_view_state.
# File lib/monkeybars/controller.rb, line 476 def view_state # :doc: return @__view_state unless @__view_state.nil? model = self.class.model_class.nil? ? nil : create_new_model transfer = {} @__view.write_state(model, transfer) @__view_state = ViewState.new(model, transfer) end
Generated with the Darkfish Rdoc Generator 2.